├── icon.png
├── CM.Text.Tests
├── Usings.cs
├── SerializationTests.cs
├── DeserializationTests.cs
├── CM.Text.Tests.csproj
├── BuilderTests.cs
├── BusinessMessagingApiTests.cs
└── SuggestionsTests.cs
├── CM.Text
├── BusinessMessaging
│ ├── Model
│ │ ├── MultiChannel
│ │ │ ├── ApplePayRequest.cs
│ │ │ ├── Dial.cs
│ │ │ ├── CarouselMessage.cs
│ │ │ ├── CarouselCardWidth.cs
│ │ │ ├── ViewLocationSuggestion.cs
│ │ │ ├── MessageContext.cs
│ │ │ ├── CalendarSuggestion.cs
│ │ │ ├── DialSuggestion.cs
│ │ │ ├── Carousel.cs
│ │ │ ├── RichCard.cs
│ │ │ ├── TemplateMessageContent.cs
│ │ │ ├── LocationPushMessage.cs
│ │ │ ├── OpenUrlSuggestion.cs
│ │ │ ├── LineItem.cs
│ │ │ ├── IRichMessage.cs
│ │ │ ├── TemplateMessage.cs
│ │ │ ├── ReplySuggestion.cs
│ │ │ ├── CalendarOptions.cs
│ │ │ ├── SuggestionBase.cs
│ │ │ ├── MediaMessage.cs
│ │ │ ├── MediaContent.cs
│ │ │ ├── ViewLocationOptions.cs
│ │ │ ├── TextMessage.cs
│ │ │ ├── RichContent.cs
│ │ │ ├── ApplePayConfiguration.cs
│ │ │ ├── ContactMessage.cs
│ │ │ ├── WhatsAppTemplate.cs
│ │ │ └── WhatsAppInteractiveMessage.cs
│ │ ├── Recipient.cs
│ │ ├── Request.cs
│ │ ├── HttpResponseBody.cs
│ │ ├── ResponseMessageDetail.cs
│ │ ├── Body.cs
│ │ ├── Channel.cs
│ │ └── Message.cs
│ ├── CarouselBuilder.cs
│ ├── BusinessMessagingApi.cs
│ └── MessageBuilder.cs
├── Common
│ └── Constant.cs
├── Identity
│ ├── OtpResult.cs
│ ├── OtpRequest.cs
│ └── OtpRequestBuilder.cs
├── TextClientResult.cs
├── TextClientMessageDetail.cs
├── TextClientFactory.cs
├── TextClientStatusCode.cs
├── Interfaces
│ └── ITextClient.cs
├── CM.Text.csproj
└── TextClient.cs
├── .github
└── workflows
│ ├── main.yml
│ └── nuget.yml
├── CM.Text.sln.DotSettings
├── LICENSE
├── CM.Text.sln
├── CHANGELOG.md
├── .editorconfig
├── .gitignore
└── README.md
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmdotcom/text-sdk-dotnet/HEAD/icon.png
--------------------------------------------------------------------------------
/CM.Text.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/ApplePayRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
4 | {
5 | ///
6 | /// ApplePay request rich message
7 | ///
8 | public class ApplePayRequest : IRichMessage
9 | {
10 | ///
11 | /// Gets or sets the apple pay configuration.
12 | ///
13 | [JsonPropertyName("payment")]
14 | public ApplePayConfiguration ApplePayConfiguration { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/Dial.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Contains information for a
8 | ///
9 | [PublicAPI]
10 | public class Dial
11 | {
12 | ///
13 | /// The number to call (in international format)
14 | ///
15 | [JsonPropertyName("phoneNumber")]
16 | public string PhoneNumber { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/Recipient.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model
5 | {
6 | ///
7 | /// A destination mobile number.
8 | ///
9 | [PublicAPI]
10 | public class Recipient
11 | {
12 | ///
13 | /// This value should be in international format.
14 | /// A single mobile number per request. Example: '00447911123456'
15 | ///
16 | [JsonPropertyName("number")]
17 | public string Number { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: .NET build & tests
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 | name: Build and test
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v4
15 | - name: Setup .NET
16 | uses: actions/setup-dotnet@v4
17 | with:
18 | dotnet-version: |
19 | 8.0.x
20 | - name: Restore dependencies
21 | run: dotnet restore
22 | - name: Build
23 | run: dotnet build --no-restore
24 | - name: Test
25 | run: dotnet test --no-build --verbosity normal
26 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/CarouselMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A message containing multiple carousels.
8 | /// Currently only supported by /
9 | ///
10 | [PublicAPI]
11 | public class CarouselMessage : IRichMessage
12 | {
13 | ///
14 | /// Contains the rich cards
15 | ///
16 | [JsonPropertyName("carousel")]
17 | public Carousel Carousel { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/CM.Text.Tests/SerializationTests.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using CM.Text.BusinessMessaging.Model.MultiChannel;
3 | using FluentAssertions;
4 |
5 | namespace CM.Text.Tests
6 | {
7 | [TestClass]
8 | public class SerializationTests
9 | {
10 | [TestMethod]
11 | public void SerializeTextTest()
12 | {
13 | var textMessage = new TextMessage("testing correct serialization");
14 | var serialized= JsonSerializer.Serialize(textMessage);
15 |
16 | serialized.Should().NotBeNullOrWhiteSpace();
17 | serialized.Should().Contain("\"$type\":\"TextMessage\"");
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/CarouselCardWidth.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Used by a to set the width
8 | ///
9 | [PublicAPI]
10 | [JsonConverter(typeof(JsonStringEnumConverter))]
11 | public enum CarouselCardWidth
12 | {
13 | ///
14 | /// Small cards
15 | ///
16 | Small,
17 |
18 | ///
19 | /// Medium sized cards
20 | ///
21 | Medium
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/ViewLocationSuggestion.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Opens the navigation app of the user with a pin at the specified location in .
8 | ///
9 | [PublicAPI]
10 | public class ViewLocationSuggestion : SuggestionBase
11 | {
12 | ///
13 | /// The location options
14 | ///
15 | [JsonPropertyName("viewLocation")]
16 | public ViewLocationOptions Location { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/MessageContext.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Contextual properties of the message. Currently only applicable to
8 | /// Docs: https://developers.cm.com/messaging/docs/whatsapp-inbound#mt-replies-mo
9 | ///
10 | public class MessageContext
11 | {
12 | ///
13 | /// Message ID to which the current message is a reply
14 | ///
15 | [JsonPropertyName("message_id")]
16 | [CanBeNull]
17 | public string MessageId { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/CalendarSuggestion.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A suggestion, used in .
8 | /// When the user clicks on the icon, it opens the calendar app of the user to add
9 | /// the new appointment.
10 | ///
11 | [PublicAPI]
12 | public class CalendarSuggestion : SuggestionBase
13 | {
14 | ///
15 | /// The options of the agenda item
16 | ///
17 | [JsonPropertyName("calendar")]
18 | public CalendarOptions Calendar { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/DialSuggestion.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A suggestion, used in
8 | /// When you want to enable the user can call you, or listen to a recorded spoken message,
9 | /// this suggestion can be applied. When clicked starts a new call.
10 | ///
11 | [PublicAPI]
12 | public class DialSuggestion : SuggestionBase
13 | {
14 | ///
15 | /// The dial options
16 | ///
17 | [JsonPropertyName("dial")]
18 | public Dial Dial { get; set; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/Carousel.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A carousel contains two or more s
8 | ///
9 | [PublicAPI]
10 | public class Carousel
11 | {
12 | ///
13 | /// The cards of the carousel
14 | ///
15 | [JsonPropertyName("cards")]
16 | public RichCard[] Cards { get; set; }
17 |
18 | ///
19 | /// The width for the items of the carousel
20 | ///
21 | [JsonPropertyName("cardWidth")]
22 | public CarouselCardWidth CarouselCardWidth { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/RichCard.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A rich card, which can be used for .
8 | ///
9 | [PublicAPI]
10 | public class RichCard : TextMessage
11 | {
12 | ///
13 | /// Optional: the header for a rich card
14 | ///
15 | [JsonPropertyName("header")]
16 | public string Header { get; set; }
17 |
18 | ///
19 | /// The image or video of the card.
20 | ///
21 | [JsonPropertyName("media")]
22 | public MediaContent Media { get; set; }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/TemplateMessageContent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Used to send a template message, at this moment only supported in .
8 | ///
9 | [PublicAPI]
10 | public class TemplateMessageContent
11 | {
12 | ///
13 | /// The WhatsApp template message
14 | ///
15 | /// Templates need to be configured by CM and approved by WhatsApp before it is possible
16 | /// to send these messages.
17 | ///
18 | [JsonPropertyName("whatsapp")]
19 | public WhatsappTemplate Whatsapp { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/CM.Text.Tests/DeserializationTests.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using CM.Text.BusinessMessaging.Model.MultiChannel;
3 | using FluentAssertions;
4 |
5 | namespace CM.Text.Tests
6 | {
7 | [TestClass]
8 | public class DeserializationTests
9 | {
10 | [TestMethod]
11 | public void DeserializeTextTest()
12 | {
13 | var textMessage = "{\"$type\":\"TextMessage\",\"text\":\"testing correct deserialization\",\"tag\":null,\"suggestions\":null}";
14 | var deserialized = JsonSerializer.Deserialize(textMessage);
15 |
16 | deserialized.Should().BeOfType();
17 | var deserializedTextMessage = deserialized as TextMessage;
18 | deserializedTextMessage!.Text.Should().Be("testing correct deserialization");
19 | }
20 | }
21 | }
22 |
23 |
24 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/Request.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace CM.Text.BusinessMessaging.Model
4 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
5 | {
6 | public class Request
7 | {
8 | public class MessagesEnvelope
9 | {
10 | [JsonPropertyName("authentication")]
11 | [JsonInclude]
12 | public Authentication Authentication { get; set; }
13 |
14 | [JsonPropertyName("msg")]
15 | public Message[] Messages { get; set; }
16 | }
17 |
18 | public class Authentication
19 | {
20 | [JsonPropertyName("producttoken")]
21 | public string ProductToken { get; set; }
22 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CM.Text/Common/Constant.cs:
--------------------------------------------------------------------------------
1 | namespace CM.Text.Common
2 | {
3 | internal static class Constant
4 | {
5 | internal static readonly string TextSdkReference = $"text-sdk-dotnet-{typeof(TextClient).Assembly.GetName().Version}";
6 |
7 | internal const string BusinessMessagingGatewayJsonEndpoint = "https://gw.messaging.cm.com/v1.0/message";
8 |
9 | internal const string OtpRequestEndpoint = "https://api.cm.com/otp/v2/otp";
10 | internal const string OtpVerifyEndpointFormatter = "https://api.cm.com/otp/v2/otp/{0}/verify";
11 |
12 | internal static readonly string BusinessMessagingGatewayMediaTypeJson = "application/json";
13 | internal static readonly string BusinessMessagingBodyTypeAuto = "AUTO";
14 | internal static readonly int BusinessMessagingMessagePartsMinDefault = 1;
15 | internal static readonly int BusinessMessagingMessagePartsMaxDefault = 8;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/HttpResponseBody.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace CM.Text.BusinessMessaging.Model
4 | {
5 |
6 | ///
7 | /// CM Messaging API response body containing API related info
8 | ///
9 | public class HttpResponseBody
10 | {
11 | ///
12 | /// API Request details
13 | ///
14 | [JsonInclude]
15 | public string details { get; private set; }
16 |
17 | ///
18 | /// JSON POST Error codes. Full description of each code available in the development documentation
19 | ///
20 | [JsonInclude]
21 | public int errorCode { get; private set; }
22 |
23 | ///
24 | /// Each message that was sent in the original request
25 | ///
26 | [JsonInclude]
27 | public ResponseMessageDetail[] messages { get; private set; }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/LocationPushMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Used to send a location, supported by (most) connections
8 | /// and .
9 | ///
10 | [PublicAPI]
11 | public class LocationPushMessage : IRichMessage
12 | {
13 | ///
14 | /// The location options to send.
15 | ///
16 | [JsonPropertyName("location")]
17 | public ViewLocationOptions Location { get; set; }
18 |
19 | ///
20 | /// Contextual properties of the message. Only applicable to
21 | ///
22 | [JsonPropertyName("context")]
23 | [CanBeNull]
24 | public MessageContext MessageContext { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/OpenUrlSuggestion.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// This is used to give the user an option to open a link.
8 | ///
9 | ///
10 | /// For this can be an in-app link,
11 | /// which will only be shown when the app is installed.
12 | ///
13 | [PublicAPI]
14 | public class OpenUrlSuggestion : SuggestionBase
15 | {
16 | ///
17 | /// The url the end user can open
18 | ///
19 | ///
20 | /// For this can be an in-app link,
21 | /// which will only be shown when the app is installed.
22 | ///
23 | [JsonPropertyName("url")]
24 | public string Url { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/CM.Text.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | RCS
3 | SMS
4 | True
5 | True
6 | True
7 | True
8 | True
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/LineItem.cs:
--------------------------------------------------------------------------------
1 |
2 | using System.Text.Json.Serialization;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A set of line items that explain recurring payments and additional charges and discounts.
8 | ///
9 | public class LineItem
10 | {
11 | ///
12 | /// (Required) A short, localized description of the line item.
13 | ///
14 | [JsonPropertyName("label")]
15 | public string Label { get; set; }
16 | ///
17 | /// A value that indicates whether the line item is final or pending.
18 | ///
19 | [JsonPropertyName("type")]
20 | public string Type { get; set; }
21 | ///
22 | /// (Required) The monetary amount of the line item.
23 | ///
24 | [JsonPropertyName("amount")]
25 | public decimal Amount { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/CM.Text.Tests/CM.Text.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | all
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/IRichMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// One element in a
8 | /// Requires a json derived type for serialization to work
9 | ///
10 | [PublicAPI]
11 | [JsonDerivedType(typeof(MediaMessage), nameof(MediaMessage))]
12 | [JsonDerivedType(typeof(ApplePayRequest), nameof(ApplePayRequest))]
13 | [JsonDerivedType(typeof(CarouselMessage), nameof(CarouselMessage))]
14 | [JsonDerivedType(typeof(ContactMessage), nameof(ContactMessage))]
15 | [JsonDerivedType(typeof(LocationPushMessage), nameof(LocationPushMessage))]
16 | [JsonDerivedType(typeof(TemplateMessage), nameof(TemplateMessage))]
17 | [JsonDerivedType(typeof(TextMessage), nameof(TextMessage))]
18 | [JsonDerivedType(typeof(WhatsAppInteractiveMessage), nameof(WhatsAppInteractiveMessage))]
19 | public interface IRichMessage
20 | {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/TemplateMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Used to send a template message, at this moment only supported in .
8 | ///
9 | [PublicAPI]
10 | public class TemplateMessage : IRichMessage
11 | {
12 | ///
13 | /// The Content of the WhatsApp template message
14 | ///
15 | /// Templates need to be configured by CM and approved by WhatsApp before it is possible
16 | /// to send these messages.
17 | ///
18 | [JsonPropertyName("template")]
19 | public TemplateMessageContent Content { get; set; }
20 |
21 | ///
22 | /// Contextual properties of the message
23 | ///
24 | [JsonPropertyName("context")]
25 | [CanBeNull]
26 | public MessageContext MessageContext { get; set; }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/nuget.yml:
--------------------------------------------------------------------------------
1 | name: .NET nuget publish
2 |
3 | on:
4 | release:
5 | types: released
6 |
7 | jobs:
8 | nugetpush:
9 | name: Build, test and push to nuget
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Setup .NET
15 | uses: actions/setup-dotnet@v4
16 | with:
17 | dotnet-version: |
18 | 8.0.x
19 | - name: Restore dependencies
20 | run: dotnet restore
21 | - name: Build
22 | run: dotnet build --no-restore
23 | - name: Test
24 | run: dotnet test --no-build --verbosity normal
25 | - name: Publish to Nuget.org
26 | run: dotnet nuget push **/*.nupkg --api-key ${{ secrets.nuget_apikey }} -s https://api.nuget.org/v3/index.json
27 | - name: Add Github Packages source
28 | run: dotnet nuget add source --username cmdotcom --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/cmdotcom/index.json"
29 | - name: Publish to Github Packages
30 | run: dotnet nuget push **/*.nupkg -s github
31 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/ReplySuggestion.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A suggestion to give the user the option to give a quick reply in
8 | ///
9 | [PublicAPI]
10 | public class ReplySuggestion : SuggestionBase
11 | {
12 | ///
13 | /// Description of the Reply suggestion
14 | ///
15 | ///
16 | /// For
17 | ///
18 | [JsonPropertyName("description")]
19 | public string Description { get; set; }
20 |
21 | ///
22 | /// The thumbnail image of the Reply suggestion
23 | ///
24 | ///
25 | /// For
26 | ///
27 | [JsonPropertyName("media")]
28 | public MediaContent Media { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 CM.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CM.Text/Identity/OtpResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace CM.Text.Identity
5 | {
6 | ///
7 | /// The result of an OTP request.
8 | ///
9 | public class OtpResult
10 | {
11 | ///
12 | /// The identifier of the OTP.
13 | ///
14 | [JsonPropertyName("id")]
15 | public string Id { get; set; }
16 | ///
17 | /// The channel used to send the code.
18 | ///
19 | [JsonPropertyName("channel")]
20 | public string Channel { get; set; }
21 | ///
22 | /// Indicates if the code was valid.
23 | ///
24 | [JsonPropertyName("verified")]
25 | public bool Verified { get; set; }
26 | ///
27 | /// The date the OTP was created.
28 | ///
29 | [JsonPropertyName("createdAt")]
30 | public DateTime CreatedAt { get; set; }
31 | ///
32 | /// The date the OTP will expire.
33 | ///
34 | [JsonPropertyName("expiresAt")]
35 | public DateTime ExpiresAt { get; set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/CalendarOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using JetBrains.Annotations;
4 |
5 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
6 | {
7 | ///
8 | /// Contains information for a .
9 | ///
10 | [PublicAPI]
11 | public class CalendarOptions
12 | {
13 | ///
14 | /// The end of the appointment.
15 | ///
16 | [JsonPropertyName("endTime")]
17 | public DateTime EndTime { get; set; }
18 |
19 | ///
20 | /// The start of the appointment.
21 | ///
22 | [JsonPropertyName("startTime")]
23 | public DateTime StartTime { get; set; }
24 |
25 | ///
26 | /// The description which will appear in the calendar app
27 | ///
28 | [JsonPropertyName("description")]
29 | public string Description { get; set; }
30 |
31 | ///
32 | /// The title of the appointment which will appear in the calendar app
33 | ///
34 | [JsonPropertyName("title")]
35 | public string Title { get; set; }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/CM.Text/TextClientResult.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.Text.Json.Serialization;
4 | using JetBrains.Annotations;
5 |
6 | namespace CM.Text
7 | {
8 | ///
9 | /// Data model that's returned after interaction with CM.com's Text interface.
10 | ///
11 | [PublicAPI]
12 | [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Json interface")]
13 | public class TextClientResult
14 | {
15 | ///
16 | /// Gets or sets the details for each message.
17 | ///
18 | ///
19 | /// The details.
20 | ///
21 | [JsonPropertyOrder(2)]
22 | public IEnumerable details { get; set; }
23 |
24 | ///
25 | /// Gets or sets the status code.
26 | ///
27 | ///
28 | /// The status code.
29 | ///
30 | [JsonPropertyOrder(1)]
31 | public TextClientStatusCode statusCode { get; set; }
32 |
33 | ///
34 | /// A message that describes the result.
35 | ///
36 | ///
37 | /// The status message.
38 | ///
39 | [JsonPropertyOrder(0)]
40 | public string statusMessage { get; set; }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/SuggestionBase.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Suggestions can be used in several channels, not all channels
8 | /// support all suggestions.
9 | ///
10 | /// Requires a json derived type for serialization to work
11 | ///
12 | [PublicAPI]
13 | [JsonPolymorphic(TypeDiscriminatorPropertyName = "action")]
14 | [JsonDerivedType(typeof(CalendarSuggestion), "CreateCalendarEvent")]
15 | [JsonDerivedType(typeof(DialSuggestion), "Dial")]
16 | [JsonDerivedType(typeof(OpenUrlSuggestion), "openUrl")]
17 | [JsonDerivedType(typeof(ReplySuggestion), "reply")]
18 | [JsonDerivedType(typeof(ViewLocationSuggestion), "viewLocation")]
19 | public abstract class SuggestionBase
20 | {
21 | ///
22 | /// The text the end user will see
23 | ///
24 | [JsonPropertyName("label")]
25 | public string Label { get; set; }
26 |
27 | ///
28 | /// When the item is selected and the postback data is set, then the Postback data will be
29 | /// sent in a MO instead of the .
30 | ///
31 | [JsonPropertyName("postbackdata")]
32 | public string PostbackData { get; set; }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/ResponseMessageDetail.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace CM.Text.BusinessMessaging.Model
4 | {
5 | ///
6 | /// CM Messaging API response containing message related info
7 | ///
8 | public class ResponseMessageDetail
9 | {
10 | ///
11 | /// Description of this message
12 | ///
13 | [JsonInclude]
14 | public string messageDetails { get; private set; }
15 |
16 | ///
17 | /// Error code specific to this message.
18 | ///
19 | [JsonInclude]
20 | public int messageErrorCode { get; private set; }
21 |
22 | ///
23 | /// How many parts this message was split in
24 | ///
25 | [JsonInclude]
26 | public int parts { get; private set; }
27 |
28 | ///
29 | /// The external reference sent to identify this message
30 | ///
31 | [JsonInclude]
32 | public string reference { get; private set; }
33 |
34 | ///
35 | /// Status of the message
36 | ///
37 | [JsonInclude]
38 | public string status { get; private set; }
39 |
40 | ///
41 | /// Recipient of this message
42 | ///
43 | [JsonInclude]
44 | public string to { get; private set; }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/CM.Text.Tests/BuilderTests.cs:
--------------------------------------------------------------------------------
1 | using CM.Text.BusinessMessaging;
2 | using CM.Text.BusinessMessaging.Model;
3 | using CM.Text.BusinessMessaging.Model.MultiChannel;
4 | using FluentAssertions;
5 |
6 | namespace CM.Text.Tests
7 | {
8 | [TestClass]
9 | public class BuilderTests
10 | {
11 | [TestMethod]
12 | public void BuildTest()
13 | {
14 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
15 |
16 | var mediaName = "cm.com";
17 | var mediaUri = "https://avatars3.githubusercontent.com/u/8234794?s=200&v=4";
18 | var mediaType = "image/png";
19 |
20 | builder
21 | .WithAllowedChannels(Channel.WhatsApp)
22 | .WithRichMessage(
23 | new MediaMessage(
24 | mediaName,
25 | mediaUri,
26 | mediaType
27 | )
28 | );
29 | var message = builder.Build();
30 |
31 | message.Should().NotBeNull();
32 | message.RichContent.Conversation.Should().NotBeNull();
33 | message.RichContent.Conversation.Length.Should().Be(1);
34 | var media = (MediaMessage) message.RichContent.Conversation.First();
35 | media.Media.MediaName.Should().Be(mediaName);
36 | media.Media.MediaUri.Should().Be(mediaUri);
37 | media.Media.MimeType.Should().Be(mediaType);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/CarouselBuilder.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using CM.Text.BusinessMessaging.Model.MultiChannel;
3 | using JetBrains.Annotations;
4 |
5 | namespace CM.Text.BusinessMessaging
6 | {
7 | ///
8 | /// Builder class to construct a CarouselMessage.
9 | ///
10 | [PublicAPI]
11 | public class CarouselBuilder
12 | {
13 | private readonly List _cards = new List();
14 | private readonly CarouselCardWidth _carouselCardWidth;
15 |
16 | ///
17 | /// Initializes the builder
18 | ///
19 | ///
20 | public CarouselBuilder(CarouselCardWidth carouselCardWidth)
21 | {
22 | _carouselCardWidth = carouselCardWidth;
23 | }
24 |
25 | ///
26 | /// Adds one card.
27 | ///
28 | ///
29 | ///
30 | public CarouselBuilder AddCard(RichCard card)
31 | {
32 | _cards.Add(card);
33 | return this;
34 | }
35 |
36 | ///
37 | /// Construct the carousel
38 | ///
39 | ///
40 | public CarouselMessage Build()
41 | {
42 | return new CarouselMessage
43 | {
44 | Carousel = new Carousel {Cards = _cards.ToArray(), CarouselCardWidth = _carouselCardWidth}
45 | };
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/MediaMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A message, which can be used for rich content channels such as
8 | /// , and .
9 | ///
10 | [PublicAPI]
11 | public class MediaMessage : IRichMessage
12 | {
13 | ///
14 | /// Default constructor.
15 | ///
16 | public MediaMessage()
17 | {
18 | }
19 |
20 | ///
21 | /// Constructor setting values.
22 | ///
23 | ///
24 | ///
25 | ///
26 | public MediaMessage(string mediaName, string mediaUri, string mimeType)
27 | {
28 | Media = new MediaContent(mediaName, mediaUri, mimeType);
29 | }
30 |
31 | ///
32 | /// The image or video of the message.
33 | ///
34 | [JsonPropertyName("media")]
35 | public MediaContent Media { get; set; }
36 |
37 | ///
38 | /// Contextual properties of the message. Only applicable to
39 | ///
40 | [JsonPropertyName("context")]
41 | [CanBeNull]
42 | public MessageContext MessageContext { get; set; }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/MediaContent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Object containing information about an image, a video or an audio file.
8 | ///
9 | [PublicAPI]
10 | public class MediaContent
11 | {
12 | ///
13 | /// Default constructor
14 | ///
15 | public MediaContent()
16 | {
17 | }
18 |
19 | ///
20 | /// Constructor which sets values
21 | ///
22 | ///
23 | ///
24 | ///
25 | public MediaContent(string mediaName, string mediaUri, string mimeType)
26 | {
27 | MediaName = mediaName;
28 | MediaUri = mediaUri;
29 | MimeType = mimeType;
30 | }
31 |
32 | ///
33 | /// The name of the image, audio or video.
34 | ///
35 | [JsonPropertyName("mediaName")]
36 | public string MediaName { get; set; }
37 |
38 | ///
39 | /// The location of the image, audio or video.
40 | ///
41 | [JsonPropertyName("mediaUri")]
42 | public string MediaUri { get; set; }
43 |
44 | ///
45 | /// The mimetype of the image, audio or video.
46 | ///
47 | [JsonPropertyName("mimeType")]
48 | public string MimeType { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/CM.Text/TextClientMessageDetail.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.Text.Json.Serialization;
3 | using JetBrains.Annotations;
4 |
5 | namespace CM.Text
6 | {
7 | ///
8 | /// Data model that contains detailed message information per recipient.
9 | ///
10 | [PublicAPI]
11 | [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Json interface")]
12 | public class TextClientMessageDetail
13 | {
14 | ///
15 | /// Message details.
16 | ///
17 | ///
18 | /// The details.
19 | ///
20 | [JsonPropertyOrder(4)]
21 | public string details { get; set; }
22 |
23 | ///
24 | /// The amount of parts a message is split up to.
25 | ///
26 | ///
27 | /// The parts.
28 | ///
29 | [JsonPropertyOrder(3)]
30 | public int parts { get; set; }
31 |
32 | ///
33 | /// The reference to a message.
34 | ///
35 | ///
36 | /// The reference.
37 | ///
38 | [JsonPropertyOrder(0)]
39 | public string reference { get; set; }
40 |
41 | ///
42 | /// The status of a message.
43 | ///
44 | ///
45 | /// The status.
46 | ///
47 | [JsonPropertyOrder(1)]
48 | public string status { get; set; }
49 |
50 | ///
51 | /// The recipient.
52 | ///
53 | ///
54 | /// To.
55 | ///
56 | [JsonPropertyOrder(2)]
57 | public string to { get; set; }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CM.Text/TextClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using CM.Text.Interfaces;
4 | using JetBrains.Annotations;
5 |
6 | namespace CM.Text
7 | {
8 | ///
9 | /// Interface to a factory to create s for different api keys
10 | ///
11 | public interface ITextClientFactory
12 | {
13 | ///
14 | /// Gets a client for a specific product token.
15 | ///
16 | /// The api key to use in the new client.
17 | ///
18 | ITextClient GetClient(Guid apiKey);
19 | }
20 |
21 | ///
22 | /// Factory to create s for different api keys
23 | ///
24 | ///
25 | [PublicAPI]
26 | public class TextClientFactory : ITextClientFactory
27 | {
28 | private readonly HttpClient _httpClient;
29 | [CanBeNull] private readonly Uri _endPointOverride;
30 |
31 | ///
32 | /// Initializes a new instance of the class.
33 | ///
34 | /// The HTTP client.
35 | /// (Optional) The end point to use, instead of the default "https://gw.messaging.cm.com/v1.0/message".
36 | public TextClientFactory(HttpClient httpClient, Uri endPointOverride = null)
37 | {
38 | _httpClient = httpClient;
39 | _endPointOverride = endPointOverride;
40 | }
41 |
42 | ///
43 | public ITextClient GetClient(Guid productToken)
44 | {
45 | return new TextClient(productToken, _httpClient, _endPointOverride);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/ViewLocationOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// The options for a in .
8 | ///
9 | [PublicAPI]
10 | public class ViewLocationOptions
11 | {
12 | ///
13 | /// Optional: The label to display at the pin
14 | ///
15 | [JsonPropertyName("label")]
16 | public string Label { get; set; }
17 |
18 | ///
19 | /// The latitude in degrees
20 | ///
21 | /// 51.603802
22 | /// Either Latitude and or is required
23 | [JsonPropertyName("latitude")]
24 | public string Latitude { get; set; }
25 |
26 | ///
27 | /// The longitude in degrees
28 | ///
29 | /// 4.770821
30 | /// Either and Longitude or is required
31 | [JsonPropertyName("longitude")]
32 | public string Longitude { get; set; }
33 |
34 | ///
35 | /// Search for this location instead of using the latitude/longitude.
36 | ///
37 | ///
38 | /// Either and or SearchQuery is required.
39 | /// For other connections both may be required.
40 | ///
41 | [JsonPropertyName("searchQuery")]
42 | public string SearchQuery { get; set; }
43 |
44 | ///
45 | /// Can be used in some connections to display a radius instead of only a pointer
46 | ///
47 | [JsonPropertyName("radius")]
48 | public int? Radius { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/TextMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// A regular text message, replaces the for channels that support rich content
8 | /// (all channels except , and at this moment)
9 | ///
10 | [PublicAPI]
11 | public class TextMessage : IRichMessage
12 | {
13 | ///
14 | /// Construct an empty text message.
15 | ///
16 | public TextMessage()
17 | {
18 | }
19 |
20 | ///
21 | /// Construct a text message and initialise the
22 | ///
23 | ///
24 | public TextMessage(string text)
25 | {
26 | Text = text;
27 | }
28 |
29 | ///
30 | /// A plain text message, when used it replaces the 'SMS' body text.
31 | /// In , when used in combination with an header and/or media this will be set as the text of a rich card.
32 | ///
33 | [JsonPropertyName("text")]
34 | public string Text { get; set; }
35 |
36 | ///
37 | /// Tag to send important and/or personally relevant 1:1 updates to recipients. E.g. to notify a recipient of an update on a recent purchase.
38 | ///
39 | [JsonPropertyName("tag")]
40 | public string Tag { get; set; }
41 |
42 | ///
43 | /// The suggestions of a text message.
44 | ///
45 | [JsonPropertyName("suggestions")]
46 | public SuggestionBase[] Suggestions { get; set; }
47 |
48 | ///
49 | /// Contextual properties of the message. Currently only applicable to
50 | ///
51 | [JsonPropertyName("context")]
52 | [CanBeNull]
53 | public MessageContext MessageContext { get; set; }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/Body.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model
5 | {
6 | ///
7 | /// Represents the Body element of the message.
8 | ///
9 | [PublicAPI]
10 | public class Body
11 | {
12 | ///
13 | /// The actual text body of the message.
14 | /// By default the CM gateway interprets messages as if sent with the standard 7 bit GSM encoding.
15 | /// If you want to send messages using e.g. Arabic, Cyrillic of Greek characters
16 | /// you will need to use the unicode UCS2 encoding.
17 | /// Set the to Auto to let the gateway do the encoding detection.
18 | /// Please note that there are a few limitations to using unicode encoded messages:
19 | /// Unicode messages can contain up to 70 characters. In the case of multipart messages, this becomes 66 characters per
20 | /// part.
21 | /// You will need to POST the XML or JSON file. A HTTP GET request cannot handle the Unicode characters
22 | /// Another note is that not all operators in the world are able to handle Unicode messages, so you will need to test
23 | /// for which operators it works.
24 | ///
25 | [JsonPropertyName("content")]
26 | public string Content { get; set; }
27 |
28 | ///
29 | /// When the type is set to 'auto' then the gateway will do the encoding detection.
30 | /// In case it detects characters that are not part of the GSM character set,
31 | /// the message will be delivered as Unicode.
32 | /// If the message contains more than 70 characters in Unicode format it will be split into a
33 | /// multipart message.
34 | /// You can limit the number of parts by setting the maximum number of message parts.
35 | ///
36 | ///
37 | [JsonPropertyName("type")]
38 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
39 | public string Type { get; set; }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/RichContent.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using JetBrains.Annotations;
4 |
5 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
6 | {
7 | ///
8 | /// Can be used by channels that support rich content (all channels except ,
9 | /// and at this moment)
10 | ///
11 | [PublicAPI]
12 | public class RichContent
13 | {
14 | ///
15 | /// Initializes a rich content object
16 | ///
17 | public RichContent()
18 | {
19 | Conversation = null;
20 | Suggestions = null;
21 | }
22 |
23 | ///
24 | /// The messages.
25 | ///
26 | [JsonPropertyName("conversation")]
27 | public IRichMessage[] Conversation { get; set; }
28 |
29 | ///
30 | /// The suggestions
31 | ///
32 | [JsonPropertyName("suggestions")]
33 | public SuggestionBase[] Suggestions { get; set; }
34 |
35 | ///
36 | /// Adds a message, such as a or .
37 | ///
38 | ///
39 | public void AddConversationPart(IRichMessage part)
40 | {
41 | if (Conversation == null)
42 | Conversation = new[] {part};
43 | else
44 | {
45 | var newArr = Conversation;
46 | Array.Resize(ref newArr, Conversation.Length + 1);
47 | newArr[newArr.Length - 1] = part;
48 | Conversation = newArr;
49 | }
50 | }
51 |
52 | ///
53 | /// Adds a suggestion
54 | ///
55 | ///
56 | public void AddSuggestion(SuggestionBase suggestion)
57 | {
58 | if (Suggestions == null)
59 | Suggestions = new[] {suggestion};
60 | else
61 | {
62 | var newArr = Suggestions;
63 | Array.Resize(ref newArr, Suggestions.Length + 1);
64 | newArr[newArr.Length - 1] = suggestion;
65 | Suggestions = newArr;
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/CM.Text/TextClientStatusCode.cs:
--------------------------------------------------------------------------------
1 | namespace CM.Text
2 | {
3 | ///
4 | /// TextClientResult status codes
5 | ///
6 | public enum TextClientStatusCode
7 | {
8 | ///
9 | /// Ok
10 | ///
11 | Ok = 0,
12 |
13 | ///
14 | /// Authentication failed.
15 | ///
16 | AuthenticationFailed = 101,
17 |
18 | ///
19 | /// Insufficient balance.
20 | ///
21 | BalanceInsufficient = 102,
22 |
23 | ///
24 | /// Incorrect API key.
25 | ///
26 | ApiKeyIncorrect = 103,
27 |
28 | ///
29 | /// This request has one or more errors in its messages. Some or all messages have not been sent.
30 | ///
31 | NotAllSent = 201,
32 |
33 | ///
34 | /// Malformed request
35 | ///
36 | RequestMalformed = 202,
37 |
38 | ///
39 | /// The MSG array is incorrect.
40 | ///
41 | MsgArrayIncorrect = 203,
42 |
43 | ///
44 | /// From field invalid
45 | ///
46 | FromFieldInvalid = 301,
47 |
48 | ///
49 | /// To field invalid
50 | ///
51 | ToFieldInvalid = 302,
52 |
53 | ///
54 | /// MSISDN invalid
55 | ///
56 | MsisdnInvalid = 303,
57 |
58 | ///
59 | /// Body field invalid
60 | ///
61 | BodyFieldInvalid = 304,
62 |
63 | ///
64 | /// Field invalid
65 | ///
66 | FieldInvalid = 305,
67 |
68 | ///
69 | /// Spam filtered
70 | ///
71 | SpamFiltered = 401,
72 |
73 | ///
74 | /// Black listed
75 | ///
76 | BlackListed = 402,
77 |
78 | ///
79 | /// Rejected
80 | ///
81 | Rejected = 403,
82 |
83 | ///
84 | /// Internal server error
85 | ///
86 | InternalServerError = 500,
87 |
88 | ///
89 | /// Unknown error, please contact CM support
90 | ///
91 | Unknown = 999
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/CM.Text.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32616.157
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CM.Text", "CM.Text\CM.Text.csproj", "{1EB6D5AC-4759-4A77-8D9D-F5B23C254B7A}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C893F008-8E43-4C38-ABF4-FA88F5549906}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | CHANGELOG.md = CHANGELOG.md
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CM.Text.Tests", "CM.Text.Tests\CM.Text.Tests.csproj", "{5389A890-EBC2-45F0-95E3-374B0BD239FF}"
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{8AC75C4D-CF2F-4E5F-85D2-4BB92C8ED1DE}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{83568C54-6C9B-4579-BC2A-E6739DE072D5}"
20 | ProjectSection(SolutionItems) = preProject
21 | .github\workflows\main.yml = .github\workflows\main.yml
22 | .github\workflows\nuget.yml = .github\workflows\nuget.yml
23 | EndProjectSection
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|Any CPU = Debug|Any CPU
28 | Release|Any CPU = Release|Any CPU
29 | EndGlobalSection
30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
31 | {1EB6D5AC-4759-4A77-8D9D-F5B23C254B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {1EB6D5AC-4759-4A77-8D9D-F5B23C254B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {1EB6D5AC-4759-4A77-8D9D-F5B23C254B7A}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {1EB6D5AC-4759-4A77-8D9D-F5B23C254B7A}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {5389A890-EBC2-45F0-95E3-374B0BD239FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {5389A890-EBC2-45F0-95E3-374B0BD239FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {5389A890-EBC2-45F0-95E3-374B0BD239FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {5389A890-EBC2-45F0-95E3-374B0BD239FF}.Release|Any CPU.Build.0 = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(SolutionProperties) = preSolution
41 | HideSolutionNode = FALSE
42 | EndGlobalSection
43 | GlobalSection(NestedProjects) = preSolution
44 | {83568C54-6C9B-4579-BC2A-E6739DE072D5} = {8AC75C4D-CF2F-4E5F-85D2-4BB92C8ED1DE}
45 | EndGlobalSection
46 | GlobalSection(ExtensibilityGlobals) = postSolution
47 | SolutionGuid = {33F57A4C-CDF2-44F4-8F81-45D5C71E3DE1}
48 | EndGlobalSection
49 | EndGlobal
50 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/BusinessMessagingApi.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text.Json;
5 | using CM.Text.BusinessMessaging.Model;
6 |
7 | namespace CM.Text.BusinessMessaging
8 | {
9 | internal static class BusinessMessagingApi
10 | {
11 | ///
12 | /// Gets the HTTP post body.
13 | ///
14 | /// The API key.
15 | /// The message text.
16 | /// From.
17 | /// To.
18 | /// The reference.
19 | ///
20 | internal static string GetHttpPostBody(
21 | Guid apiKey,
22 | string messageText,
23 | string from,
24 | IEnumerable to,
25 | string reference)
26 | {
27 | var message = new MessageBuilder(messageText, from, to.ToArray()).WithReference(reference)
28 | .Build();
29 | return GetHttpPostBody(apiKey, message);
30 | }
31 |
32 | ///
33 | /// Gets the HTTP post body.
34 | ///
35 | /// The API key.
36 | /// The message to send.
37 | ///
38 | internal static string GetHttpPostBody(Guid apiKey, Message message)
39 | {
40 | return JsonSerializer.Serialize(
41 | new
42 | {
43 | messages = new Request.MessagesEnvelope
44 | {
45 | Authentication = new Request.Authentication {ProductToken = apiKey.ToString()},
46 | Messages = new List {message}.ToArray()
47 | }
48 | }
49 | );
50 | }
51 |
52 | ///
53 | /// Transforms the BusinessMessageApi request result to TextClientResult.
54 | ///
55 | /// Content of the request result.
56 | ///
57 | internal static TextClientResult GetTextApiResult(string requestResultContent)
58 | {
59 | var deserializedResponse = JsonSerializer.Deserialize(requestResultContent);
60 |
61 | return new TextClientResult
62 | {
63 | statusMessage = deserializedResponse.details,
64 | statusCode = (TextClientStatusCode) deserializedResponse.errorCode,
65 | details = deserializedResponse.messages.Select(
66 | message => new TextClientMessageDetail
67 | {
68 | reference = message.reference,
69 | status = message.status,
70 | to = message.to,
71 | parts = message.parts,
72 | details = message.messageDetails
73 | }
74 | )
75 | .ToArray()
76 | };
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/CM.Text/Interfaces/ITextClient.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using CM.Text.BusinessMessaging.Model;
5 | using JetBrains.Annotations;
6 |
7 | namespace CM.Text.Interfaces
8 | {
9 | ///
10 | /// Interface to the client to send text messages.
11 | ///
12 | public interface ITextClient
13 | {
14 | ///
15 | /// Sends a message asynchronous.
16 | ///
17 | /// The message text.
18 | ///
19 | /// This is the sender name. The maximum length is 11 alphanumerical characters or 16 digits. Example: 'MyCompany'.
20 | /// For Twitter: use the Twitter Snowflake ID of the account you want to use as sender.
21 | /// For MobilePush: use the app key of the account you want to use as sender.
22 | /// For Facebook Messenger: use the Facebook Page ID of the account you want to use as sender.
23 | /// For Google Business Messages: use the Google Business Messages agent ID of the account you want to use as sender (without dashes).
24 | /// For Instagram: use the Instagram Account ID of the account you want to use as sender.
25 | /// For Telegram: use the Telegram Bot ID of the account you want to use as sender.
26 | ///
27 | ///
28 | /// These are the destination mobile numbers. Restrictions: this value should be in international format.
29 | /// Example: '00447911123456'.
30 | /// For Twitter: use the Twitter Snowflake ID.
31 | /// For Facebook Messenger: use the Facebook Page Scoped User ID (PSID).
32 | /// For Google Business Messages: use the Google Business Messages conversation ID (without dashes).
33 | /// For Instagram: use the Instagram Scoped User ID (IGSID).
34 | /// For Telegram: use the Telegram Chat ID.
35 | ///
36 | ///
37 | /// Here you can include your message reference. This information will be returned in a status
38 | /// report so you can match the message and it's status. Restrictions: 1 - 32 alphanumeric characters and reference
39 | /// will not work for demo accounts.
40 | ///
41 | /// The cancellation token.
42 | ///
43 | Task SendMessageAsync(
44 | string messageText,
45 | string from,
46 | IEnumerable to,
47 | [CanBeNull] string reference,
48 | CancellationToken cancellationToken = default);
49 |
50 | ///
51 | /// Sends a message asynchronous.
52 | ///
53 | /// The message to send.
54 | /// The cancellation token.
55 | ///
56 | Task SendMessageAsync(
57 | Message message,
58 | CancellationToken cancellationToken = default);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/ApplePayConfiguration.cs:
--------------------------------------------------------------------------------
1 |
2 | using System.Text.Json.Serialization;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Object that describes the Apple Pay configuration
8 | ///
9 | public class ApplePayConfiguration
10 | {
11 | ///
12 | /// (Required) A unique identifier that represents a merchant for Apple Pay.
13 | ///
14 | [JsonPropertyName("merchantName")]
15 | public string MerchantName { get; set; }
16 | ///
17 | /// (Required) Description of the item being bought.
18 | ///
19 | [JsonPropertyName("description")]
20 | public string Description { get; set; }
21 | ///
22 | /// (Required) A unique identifier that represents a order
23 | ///
24 | [JsonPropertyName("orderReference")]
25 | public string OrderReference { get; set; }
26 |
27 | ///
28 | /// An array of line items explaining payments and additional charges.
29 | ///
30 | /// Line items are not required. However, the array cannot be empty if the lineItems key is present.
31 | ///
32 | [JsonPropertyName("lineItems")]
33 | public LineItem[] LineItems { get; set; }
34 |
35 | ///
36 | /// A dictionary containing the total.
37 | ///
38 | /// The total amount must be greater than zero to pass validation.
39 | ///
40 | [JsonPropertyName("total")]
41 | public decimal Total { get; set; }
42 |
43 | ///
44 | /// Email address of the Apple Pay contact.
45 | ///
46 | [JsonPropertyName("recipientEmail")]
47 | public string RecipientEmail { get; set; }
48 | ///
49 | /// Value indicating the currency code of the apple pay request
50 | ///
51 | /// Value must be in upper case.
52 | ///
53 | [JsonPropertyName("currencyCode")]
54 | public string CurrencyCode { get; set; }
55 | ///
56 | /// Country of the Apple Pay contact.
57 | ///
58 | /// Value must be in upper case.
59 | ///
60 | [JsonPropertyName("recipientCountryCode")]
61 | public string RecipientCountryCode { get; set; }
62 | ///
63 | /// The Language of the Country of the Apple Pay Contact
64 | ///
65 | /// Value must be in lower case.
66 | ///
67 | [JsonPropertyName("languageCountryCode")]
68 | public string languageCountryCode { get; set; }
69 | ///
70 | /// Value indicating that a billing address is required
71 | ///
72 | [JsonPropertyName("billingAddressRequired")]
73 | public bool BillingAddressRequired { get; set; }
74 | ///
75 | /// Value indicating that a shipping contact is required
76 | ///
77 | [JsonPropertyName("shippingContactRequired")]
78 | public bool ShippingContactRequired { get; set; }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/CM.Text/Identity/OtpRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.Identity
5 | {
6 | ///
7 | /// A request to send an OTP towards an end-user.
8 | ///
9 | [PublicAPI]
10 | public class OtpRequest
11 | {
12 | ///
13 | /// Required: This is the sender name.
14 | /// The maximum length is 11 alphanumerical characters or 16 digits. Example: 'MyCompany'
15 | ///
16 | [JsonPropertyName("from")]
17 | public string From { get; set; }
18 |
19 | ///
20 | /// Required: The destination mobile numbers.
21 | /// This value should be in international format.
22 | /// A single mobile number per request. Example: '00447911123456'
23 | ///
24 | [JsonPropertyName("to")]
25 | public string To { get; set; }
26 |
27 | ///
28 | /// The length of the code (min 4, max 10). default: 5.
29 | ///
30 | [JsonPropertyName("digits")]
31 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
32 | public int? Digits { get; set; }
33 |
34 | ///
35 | /// The expiry in seconds (min 10, max 3600). default: 60 seconds.
36 | ///
37 | [JsonPropertyName("expiry")]
38 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
39 | public int? Expiry { get; set; }
40 |
41 | ///
42 | /// The channel to send the code.
43 | /// Supported values: auto, sms, push, whatsapp, voice, email.
44 | /// Channel auto is only available with a SOLiD subscription.
45 | ///
46 | [JsonPropertyName("channel")]
47 | public string Channel { get; set; } = "sms";
48 |
49 | ///
50 | /// The locale, for WhatsApp supported values: en, nl, fr, de, it, es.
51 | /// Default: en
52 | ///
53 | /// For Voice: the spoken language in the voice call,
54 | /// supported values: de-DE, en-AU, en-GB, en-IN, en-US, es-ES, fr-CA, fr-FR, it-IT, ja-JP, nl-NL
55 | /// Default: en-GB.
56 | ///
57 | /// For Email: The locale for the email template.
58 | ///
59 | [JsonPropertyName("locale")]
60 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
61 | [CanBeNull]
62 | public string Locale { get; set; }
63 |
64 | ///
65 | /// The app key, when is 'push'
66 | ///
67 | [JsonPropertyName("pushAppKey")]
68 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
69 | [CanBeNull]
70 | public string PushAppKey { get; set; }
71 |
72 | ///
73 | /// For WhatsApp, set a custom message. You can use the placeholder {code}, this will be replaced by the actual code.
74 | /// Example: Your code is: {code}. This is only used as a fallback in case the message could not be delivered via WhatsApp.
75 | ///
76 | /// For email, Set a custom message to be used in the email message. Do not include the {code} placeholder.
77 | ///
78 | [JsonPropertyName("message")]
79 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
80 | [CanBeNull]
81 | public string Message { get; set; }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CM.Text/Identity/OtpRequestBuilder.cs:
--------------------------------------------------------------------------------
1 |
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.Identity
5 | {
6 | ///
7 | /// Builder class to construct messages
8 | ///
9 | [PublicAPI]
10 | public class OtpRequestBuilder
11 | {
12 | private readonly OtpRequest _otpRequest;
13 |
14 | ///
15 | /// Creates a new OtpRequestBuilder
16 | ///
17 | ///
18 | ///
19 | public OtpRequestBuilder(string from, string to)
20 | {
21 | _otpRequest = new OtpRequest { From = from, To = to };
22 | }
23 |
24 | ///
25 | /// Constructs the request.
26 | ///
27 | ///
28 | public OtpRequest Build()
29 | {
30 | return _otpRequest;
31 | }
32 |
33 | ///
34 | /// Set the channel
35 | ///
36 | public OtpRequestBuilder WithChannel(string channel)
37 | {
38 | _otpRequest.Channel = channel;
39 | return this;
40 | }
41 |
42 | ///
43 | /// Sets The length of the code (min 4, max 10). default: 5.
44 | ///
45 | ///
46 | ///
47 | public OtpRequestBuilder WithDigits(int digits)
48 | {
49 | _otpRequest.Digits = digits;
50 | return this;
51 | }
52 |
53 | ///
54 | /// The expiry in seconds (min 10, max 3600). default: 60 seconds.
55 | ///
56 | public OtpRequestBuilder WithExpiry(int expiryInSeconds)
57 | {
58 | _otpRequest.Expiry = expiryInSeconds;
59 | return this;
60 | }
61 |
62 | ///
63 | /// The locale, for WhatsApp supported values: en, nl, fr, de, it, es.
64 | /// Default: en
65 | ///
66 | /// For Voice: the spoken language in the voice call,
67 | /// supported values: de-DE, en-AU, en-GB, en-IN, en-US, es-ES, fr-CA, fr-FR, it-IT, ja-JP, nl-NL
68 | /// Default: en-GB.
69 | ///
70 | /// For Email: The locale for the email template.
71 | ///
72 | ///
73 | ///
74 | public OtpRequestBuilder WithLocale(string locale)
75 | {
76 | _otpRequest.Locale = locale;
77 | return this;
78 | }
79 |
80 | ///
81 | /// The app key, when the channel is 'push'
82 | ///
83 | ///
84 | ///
85 | public OtpRequestBuilder WithPushAppKey(string pushAppKey)
86 | {
87 | _otpRequest.PushAppKey = pushAppKey;
88 | return this;
89 | }
90 |
91 | ///
92 | /// For WhatsApp, set a custom message. You can use the placeholder {code}, this will be replaced by the actual code.
93 | /// Example: Your code is: {code}. This is only used as a fallback in case the message could not be delivered via WhatsApp.
94 | ///
95 | /// For email, Set a custom message to be used in the email message. Do not include the {code} placeholder.
96 | ///
97 | ///
98 | ///
99 | public OtpRequestBuilder WithMessage(string message)
100 | {
101 | _otpRequest.Message = message;
102 | return this;
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/CM.Text.Tests/BusinessMessagingApiTests.cs:
--------------------------------------------------------------------------------
1 | using CM.Text.BusinessMessaging;
2 | using CM.Text.BusinessMessaging.Model;
3 | using CM.Text.BusinessMessaging.Model.MultiChannel;
4 | using FluentAssertions;
5 |
6 | namespace CM.Text.Tests
7 | {
8 | [TestClass]
9 | public class BusinessMessagingApiTests
10 | {
11 | [TestMethod]
12 | public void TestPostBody()
13 | {
14 | var guid = Guid.NewGuid();
15 | var message = "This is a unit test";
16 | var sender = "CM.com";
17 | var reference = "ReferenceForMeToFind";
18 | var number1 = "0031612345678";
19 | var number2 = "0031612345679";
20 |
21 | var data = BusinessMessagingApi.GetHttpPostBody(guid, message, sender,
22 | new[] {number1, number2}, reference);
23 |
24 | data.Should().NotBeNull();
25 | //Simple to check if all values survived our logic
26 | data.Should().Contain(guid.ToString());
27 | data.Should().Contain(message);
28 | data.Should().Contain(sender);
29 | data.Should().Contain(reference);
30 | data.Should().Contain(number1);
31 | data.Should().Contain(number2);
32 | }
33 |
34 | [TestMethod]
35 | public void TestRichPostBody()
36 | {
37 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
38 |
39 | var mediaName = "cm.com icon";
40 | var mediaUri = "https://avatars3.githubusercontent.com/u/8234794";
41 | var mediaType = "image/png";
42 |
43 | builder
44 | .WithAllowedChannels(Channel.WhatsApp)
45 | .WithRichMessage(
46 | new MediaMessage(
47 | mediaName,
48 | mediaUri,
49 | mediaType
50 | )
51 | );
52 | var message = builder.Build();
53 |
54 | Guid fakeApiKey = Guid.NewGuid();
55 | var data = BusinessMessagingApi.GetHttpPostBody(fakeApiKey, message);
56 |
57 | data.Should().NotBeNull();
58 | //Simple to check if all values survived our logic
59 | data.Should().Contain(fakeApiKey.ToString(), "the api key should be present in the body");
60 | data.Should().Contain(mediaName, "the media name needs to be sent");
61 | data.Should().Contain(mediaType, "the media type has to be sent");
62 | data.Should().Contain(mediaUri, "the media url has to be sent");
63 | }
64 |
65 |
66 | [TestMethod]
67 | public void TestResponseBody()
68 | {
69 | var guid = Guid.NewGuid();
70 | // Arrange
71 | string message = @"{
72 | ""messages"": [{
73 | ""to"": ""0031612345678"",
74 | ""parts"": 1,
75 | ""status"": ""Accepted"",
76 | ""reference"": ""test-reference-1"",
77 | ""messageErrorCode"": 0,
78 | ""messageDetails"": null
79 | }],
80 | ""details"": ""Created 1 message(s)"",
81 | ""errorCode"": 0
82 | }";
83 |
84 |
85 | var data = BusinessMessagingApi.GetTextApiResult(message);
86 |
87 | data.Should().NotBeNull();
88 | //Simple to check if all values survived our logic
89 | data.details.Should().NotBeNull();
90 | data.details.First().reference.Should().Be("test-reference-1");
91 | data.details.First().status.Should().Be("Accepted");
92 | data.details.First().to.Should().Be("0031612345678");
93 | data.details.First().parts.Should().Be(1);
94 | data.details.First().details.Should().BeNull();
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/CM.Text/CM.Text.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net8.0
5 | CM.com Messaging team
6 | CM.com
7 | CM Text SDK
8 | A software development kit to provide ways to interact with CM.com's Text/messaging service.
9 | messaging, text, sms, email, push, whatsapp, viber, wechat, rcs, voice, business messaging, telegram, conversational, sms chat, chat, sms 2.0, rbm, mms, rich sms, rich communication services, telegram
10 | https://github.com/cmdotcom/text-sdk-dotnet
11 | Github
12 | $([System.DateTime]::UtcNow.Year) CM.com
13 | LICENSE
14 | icon.png
15 | $([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/../CHANGELOG.md"))
16 | 2.12.0
17 | https://github.com/cmdotcom/text-sdk-dotnet
18 | en
19 | true
20 | 2.12.0
21 | 2.12.0
22 | True
23 | README.md
24 |
25 |
26 |
27 | True
28 |
29 |
30 |
31 | True
32 |
33 |
34 |
35 | 9999
36 | True
37 | 1701;1702;
38 |
39 |
40 |
41 |
42 | 9999
43 | True
44 | 1701;1702;
45 |
46 |
47 |
48 |
49 | 9999
50 | True
51 | 1701;1702;
52 |
53 |
54 |
55 |
56 | 9999
57 | True
58 | 1701;1702;
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | PreserveNewest
73 |
74 |
75 |
76 |
77 |
78 | True
79 | \
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [2.12.0] - 2025-08-01
8 | ### Updated
9 | - Updated dependencies and .NET version
10 |
11 | ## [2.11.1] - 2025-01-31
12 | ### Added
13 | - Derived type declarations on `SuggestionBase`
14 |
15 | ## [2.11.0] - 2025-01-10
16 | ### Added
17 | - Context.MessageId on RichMessages supporting WhatsApp for referencing a previous message
18 |
19 | ## [2.10.0] - 2024-12-30
20 | ### Added
21 | - Polymorphic type discriminators on IRichMessage for deserialization
22 |
23 | ### Changed
24 | - Updated `System.Text.Json` to 8.0.5 fixing a [high impact security vulnerability](https://github.com/advisories/GHSA-hh2w-p6rv-4g7w)
25 |
26 | ## [2.9.0] - 2024-06-20
27 | ### Changed
28 | - New global messaging endpoint
29 |
30 | ## [2.8.0] - 2024-01-08
31 | ### Added
32 | - Implementing OTP Request and verify
33 |
34 | ## [2.7.0] - 2023-06-26
35 | ### Added
36 | - Whatsapp Multi-Product Template Messages
37 |
38 | ## [2.6.0] - 2022-12-07
39 | ### Added
40 | - System.Text.Json, replaces Newtsonsoft, possibly breaking
41 | - Add Telegram as tag for the project
42 | - Multi target to .NET 7 and use .NET 7 included System.Text.Json
43 | ### Changed
44 | - Enable "Treat Warnings as Errors" to adhere to code guidelines
45 | - Give internal response body messagerrorcode the correct type
46 | - Some internal refactoring
47 | - Changed PackageIconUrl > PackageIcon and added icon manually as it PackageIconUrl was deprecated
48 | - Change RichContent interface to support derived types
49 | ### Removed
50 | - Newtsonsoft.Json dependency
51 |
52 | ## [2.5.2] - 2022-10-14
53 | - Enable nupkg generation in project
54 |
55 | ## [2.5.1] - 2022-10-12
56 | - Upped Newtonsoft.Json
57 |
58 | ## [2.5.0] - 2022-06-17
59 | - Add Telegram
60 |
61 | ## [2.4.1] - 2022-02-01
62 | - Fix support for a custom end point URI in second SendMessage method
63 |
64 | ## [2.4.0] - 2022-01-31
65 | - Fix failure on body.type set to null
66 | - Add support for a custom end point URI
67 |
68 | ## [2.3.0] - 2021-10-27
69 | - Add TextClientFactory
70 |
71 | ## [2.2.1] - 2021-09-10
72 | - Add support for reply buttons with media
73 |
74 | ## [2.2.0] - 2021-09-09
75 | - Add support for interactive whatsapp messages
76 |
77 | ## [2.1.0] - 2021-07-08
78 | - Ignore HybridAppKey when null instead of empty, to support Notifire flow of channel MobilePush
79 | - Add DefaultValueHandling to RichContent property of Message model.
80 | - Add Tag property to TextMessage model used within a rich message.
81 | - Add Suggestions property to TextMessage model used within a rich message.
82 | - Add AppleBusinessChat as channel type, and marked iMessage as obsolete.
83 |
84 | ## [2.0.6] - 2021-07-06
85 | - Add Instagram
86 |
87 | ## [2.0.5] - 2021-05-25
88 | - Correct JSON formatting of MediaContent
89 |
90 | ## [2.0.4] - 2021-05-20
91 | - Added media en description to ReplySuggestion
92 |
93 | ## [2.0.3] - 2021-04-08
94 | - Add Facebook Messenger and Google Business Messages channel
95 |
96 | ## [2.0.2] - 2020-12-23
97 | - Add MobilePush channel
98 |
99 | ## [2.0.1] - 2020-11-23
100 | - Add support for interactive templates
101 |
102 | ## [2.0.0] - 2020-08-26
103 | - Removed Localizable Params because it is Deprecated by Facebook,
104 | - Also updated the WhatsApp Template Signature based on Facebook spec updates.
105 |
106 | ## [1.8.0] - 2020-06-24
107 | - Add Twitter support
108 |
109 | ## [1.7.0] - 2020-02-14
110 | - Able to set validity period. (@GuidoNeele)
111 | - Fixed issue when adding suggestions.
112 | - Resolved typo's in documentation.
113 |
114 | ## [1.6.1] - 2020-02-14
115 | - Version is equal to 1.7.0, due to not working release flow.
116 |
117 | ## [1.6.0] - 2020-02-07
118 | - Add Rich templates + ApplePay
119 |
120 | ## [1.5.2] - 2019-12-04
121 | - Fix WhatsApp template signature (@Michel16867)
122 |
123 | ## [1.5.0] - 2019-11-01
124 | - Added several RichFeatures.
125 | 1. TemplateMessage
126 | 2. LocationPushMessage
127 | 3. ContactMessage
128 |
129 | ## [1.4.0] - 2019-03-05
130 | - Fix typo in conversation
131 |
132 | ## [1.3.0] - 2019-02-26
133 | - Add license information
134 |
135 | ## [1.2.0] - 2019-02-22
136 | - Add multichannel options
137 |
138 | ## [1.1.0] - 2019-02-22
139 | - Add option to pass http client to constructor
140 |
141 | ## [1.0.1] - 2018-11-08
142 | - Initial release
143 |
--------------------------------------------------------------------------------
/CM.Text.Tests/SuggestionsTests.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json;
2 | using CM.Text.BusinessMessaging.Model.MultiChannel;
3 | using FluentAssertions;
4 |
5 | namespace CM.Text.Tests
6 | {
7 | [TestClass]
8 | public class SuggestionsTests
9 | {
10 | [TestMethod]
11 | public void SerializationCalendarSuggestionTest()
12 | {
13 | var calenderSuggestion = new CalendarSuggestion()
14 | {
15 | Calendar = new CalendarOptions()
16 | {
17 | Title = "Appointment selection",
18 | Description = "Schedule an appointment with us",
19 | EndTime = DateTime.UtcNow,
20 | StartTime = DateTime.UtcNow,
21 | }
22 | };
23 |
24 | var serialized= JsonSerializer.Serialize(calenderSuggestion);
25 |
26 | serialized.Should().NotBeNullOrWhiteSpace();
27 |
28 | var deserialized = JsonSerializer.Deserialize(serialized);
29 |
30 | deserialized.Should().BeOfType();
31 | var deserializedCalendarSuggestion = deserialized as CalendarSuggestion;
32 | deserializedCalendarSuggestion.Should().BeEquivalentTo(calenderSuggestion);
33 | }
34 |
35 | [TestMethod]
36 | public void SerializationDialSuggestionTest()
37 | {
38 | var dialSuggestion = new DialSuggestion()
39 | {
40 | Dial = new Dial()
41 | {
42 | PhoneNumber = "0031612345678"
43 | }
44 | };
45 |
46 | var serialized= JsonSerializer.Serialize(dialSuggestion);
47 |
48 | serialized.Should().NotBeNullOrWhiteSpace();
49 |
50 | var deserialized = JsonSerializer.Deserialize(serialized);
51 |
52 | deserialized.Should().BeOfType();
53 | var deserializedDialSuggestion = deserialized as DialSuggestion;
54 | deserializedDialSuggestion.Should().BeEquivalentTo(dialSuggestion);
55 | }
56 |
57 | [TestMethod]
58 | public void SerializationOpenUrlSuggestionTest()
59 | {
60 | var openUrlSuggestion = new OpenUrlSuggestion()
61 | {
62 | Url = "https://www.cm.com"
63 | };
64 |
65 | var serialized= JsonSerializer.Serialize(openUrlSuggestion);
66 |
67 | serialized.Should().NotBeNullOrWhiteSpace();
68 |
69 | var deserialized = JsonSerializer.Deserialize(serialized);
70 |
71 | deserialized.Should().BeOfType();
72 | var deserializedOpenUrlSuggestion = deserialized as OpenUrlSuggestion;
73 | deserializedOpenUrlSuggestion.Should().BeEquivalentTo(openUrlSuggestion);
74 | }
75 |
76 | [TestMethod]
77 | public void SerializationViewLocationSuggestionTest()
78 | {
79 | var viewLocationSuggestion = new ViewLocationSuggestion()
80 | {
81 | Location = new ViewLocationOptions()
82 | {
83 | Label = "CM.com headquarters",
84 | Latitude = "51.602885",
85 | Longitude = "4.7683932",
86 | SearchQuery = "CM.com headquarters",
87 | Radius = 5
88 | }
89 | };
90 |
91 | var serialized= JsonSerializer.Serialize(viewLocationSuggestion);
92 |
93 | serialized.Should().NotBeNullOrWhiteSpace();
94 |
95 | var deserialized = JsonSerializer.Deserialize(serialized);
96 |
97 | deserialized.Should().BeOfType();
98 | var deserializedViewLocationSuggestion = deserialized as ViewLocationSuggestion;
99 | deserializedViewLocationSuggestion.Should().BeEquivalentTo(viewLocationSuggestion);
100 | }
101 |
102 | [TestMethod]
103 | public void SerializationReplySuggestionTest()
104 | {
105 | var replySuggestion = new ReplySuggestion()
106 | {
107 | Label = "Some label",
108 | PostbackData = "LABEL",
109 | Description = "Description of the label",
110 | Media = new MediaContent("Test image", "https://example.com", "image/jpg")
111 | };
112 | var serialized= JsonSerializer.Serialize(replySuggestion);
113 |
114 | serialized.Should().NotBeNullOrWhiteSpace();
115 |
116 | var deserialized = JsonSerializer.Deserialize(serialized);
117 |
118 | deserialized.Should().BeOfType();
119 | var deserializedReplySuggestion = deserialized as ReplySuggestion;
120 | deserializedReplySuggestion.Should().BeEquivalentTo(replySuggestion);
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/Channel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 | using System.Text.Json.Serialization;
4 | using JetBrains.Annotations;
5 |
6 | namespace CM.Text.BusinessMessaging.Model
7 | {
8 | ///
9 | /// Used by to force a message to only use certain routes.
10 | ///
11 | ///
12 | /// Note that for channels other than SMS, CM needs to configure the out going flows.
13 | /// For those flows to work, we need to be contacted.
14 | ///
15 | [PublicAPI]
16 | [JsonConverter(typeof(JsonStringEnumConverter))]
17 | public enum Channel
18 | {
19 | ///
20 | /// Messages will be sent as SMS text messages
21 | ///
22 | SMS,
23 |
24 | ///
25 | /// Send messages using WhatsApp for business
26 | ///
27 | ///
28 | /// Note that CM needs to configure this for you to work.
29 | ///
30 | WhatsApp,
31 |
32 | ///
33 | /// Sends messages to push using Hybrid messages.
34 | /// See also https://developers.cm.com/messaging/docs
35 | ///
36 | /// Works only when is set
37 | Push,
38 |
39 | ///
40 | /// Messages will be sent over RCS.
41 | ///
42 | ///
43 | /// Note that CM needs to configure this for you to work.
44 | ///
45 | RCS,
46 |
47 | ///
48 | /// Messages will be sent over Viber.
49 | ///
50 | ///
51 | /// Note that CM needs to configure this for you to work.
52 | ///
53 | Viber,
54 |
55 | ///
56 | /// Messages will be sent using text to speech.
57 | ///
58 | ///
59 | /// Note that CM needs to configure this for you to work.
60 | ///
61 | Voice,
62 |
63 | ///
64 | /// Messages will be sent over Apple Business Chat.
65 | ///
66 | ///
67 | /// Note that CM needs to configure this for you to work.
68 | ///
69 | [Obsolete("Use 'Channel.AppleBusinessChat' instead")]
70 | // ReSharper disable once InconsistentNaming
71 | iMessage,
72 |
73 | ///
74 | /// Messages will be sent over Apple Business Chat.
75 | ///
76 | ///
77 | /// Note that CM needs to configure this for you to work.
78 | ///
79 | [EnumMember(Value = "Apple Business Chat")]
80 | AppleBusinessChat,
81 |
82 | ///
83 | /// Messages will be sent over Line.
84 | ///
85 | ///
86 | /// Note that CM needs to configure this for you to work.
87 | ///
88 | Line,
89 |
90 | ///
91 | /// Messages will be sent over Twitter.
92 | ///
93 | ///
94 | /// Note that CM needs to configure this for you to work.
95 | ///
96 | Twitter,
97 |
98 | ///
99 | /// Messages will be sent over the new Push channel.
100 | ///
101 | ///
102 | /// This channel is the successor of the "Push" channel.
103 | /// Contact CM for information on how to migrate your current Push integration
104 | ///
105 | MobilePush,
106 |
107 | ///
108 | /// Messages will be sent over Facebook Messenger.
109 | ///
110 | ///
111 | /// Note that CM needs to configure this for you to work.
112 | ///
113 | [EnumMember(Value = "Facebook Messenger")]
114 | FacebookMessenger,
115 |
116 | ///
117 | /// Messages will be sent over Google Business Messages.
118 | ///
119 | ///
120 | /// Note that CM needs to configure this for you to work.
121 | ///
122 | [EnumMember(Value = "Google Business Messages")]
123 | GoogleBusinessMessages,
124 |
125 | ///
126 | /// Messages will be sent over Instagram.
127 | ///
128 | ///
129 | /// Note that CM needs to configure this for you to work.
130 | ///
131 | [EnumMember(Value = "Instagram")]
132 | Instagram,
133 |
134 | ///
135 | /// Messages will be sent over Telegram.
136 | ///
137 | ///
138 | /// Note that CM needs to configure this for you to work.
139 | ///
140 | [EnumMember(Value = "Telegram Messenger")]
141 | Telegram,
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | ###############################
2 | # Core EditorConfig Options #
3 | ###############################
4 |
5 | root = true
6 |
7 | # All files
8 | [*]
9 | indent_style = space
10 |
11 | # Code files
12 | [*.{cs,csx,vb,vbx}]
13 | indent_size = 4
14 | insert_final_newline = true
15 | charset = utf-8-bom
16 |
17 | ###############################
18 | # .NET Coding Conventions #
19 | ###############################
20 |
21 | [*.{cs,vb}]
22 | # Organize usings
23 | dotnet_sort_system_directives_first = true
24 | dotnet_separate_import_directive_groups = false
25 |
26 | # this. preferences
27 | dotnet_style_qualification_for_field = false:silent
28 | dotnet_style_qualification_for_property = false:silent
29 | dotnet_style_qualification_for_method = false:silent
30 | dotnet_style_qualification_for_event = false:silent
31 |
32 | # Language keywords vs BCL types preferences
33 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent
34 | dotnet_style_predefined_type_for_member_access = true:silent
35 |
36 | # Parentheses preferences
37 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
38 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
39 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
40 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
41 |
42 | # Modifier preferences
43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
44 | dotnet_style_readonly_field = true:suggestion
45 |
46 | # Expression-level preferences
47 | dotnet_style_object_initializer = true:suggestion
48 | dotnet_style_collection_initializer = true:suggestion
49 | dotnet_style_explicit_tuple_names = true:suggestion
50 | dotnet_style_null_propagation = true:suggestion
51 | dotnet_style_coalesce_expression = true:suggestion
52 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
53 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
54 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
55 | dotnet_style_prefer_auto_properties = true:silent
56 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
57 | dotnet_style_prefer_conditional_expression_over_return = true:silent
58 |
59 | ###############################
60 | # Naming Conventions #
61 | ###############################
62 |
63 | # Style Definitions
64 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
65 |
66 | # Use PascalCase for constant fields
67 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
68 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
69 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
70 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
71 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
72 | dotnet_naming_symbols.constant_fields.required_modifiers = const
73 |
74 | ###############################
75 | # C# Code Style Rules #
76 | ###############################
77 |
78 | [*.cs]
79 | # var preferences
80 | csharp_style_var_for_built_in_types = true:silent
81 | csharp_style_var_when_type_is_apparent = true:silent
82 | csharp_style_var_elsewhere = true:silent
83 |
84 | # Expression-bodied members
85 | csharp_style_expression_bodied_methods = false:silent
86 | csharp_style_expression_bodied_constructors = false:silent
87 | csharp_style_expression_bodied_operators = false:silent
88 | csharp_style_expression_bodied_properties = true:silent
89 | csharp_style_expression_bodied_indexers = true:silent
90 | csharp_style_expression_bodied_accessors = true:silent
91 |
92 | # Pattern-matching preferences
93 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
94 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
95 |
96 | # Null-checking preferences
97 | csharp_style_throw_expression = true:suggestion
98 | csharp_style_conditional_delegate_call = true:suggestion
99 |
100 | # Modifier preferences
101 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
102 |
103 | # Expression-level preferences
104 | csharp_prefer_braces = true:silent
105 | csharp_style_deconstructed_variable_declaration = true:suggestion
106 | csharp_prefer_simple_default_expression = true:suggestion
107 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
108 | csharp_style_inlined_variable_declaration = true:suggestion
109 |
110 | ###############################
111 | # C# Formatting Rules #
112 | ###############################
113 |
114 | # New line preferences
115 | csharp_new_line_before_open_brace = all
116 | csharp_new_line_before_else = true
117 | csharp_new_line_before_catch = true
118 | csharp_new_line_before_finally = true
119 | csharp_new_line_before_members_in_object_initializers = true
120 | csharp_new_line_before_members_in_anonymous_types = true
121 | csharp_new_line_between_query_expression_clauses = true
122 |
123 | # Indentation preferences
124 | csharp_indent_case_contents = true
125 | csharp_indent_switch_labels = true
126 | csharp_indent_labels = flush_left
127 |
128 | # Space preferences
129 | csharp_space_after_cast = false
130 | csharp_space_after_keywords_in_control_flow_statements = true
131 | csharp_space_between_method_call_parameter_list_parentheses = false
132 | csharp_space_between_method_declaration_parameter_list_parentheses = false
133 | csharp_space_between_parentheses = false
134 | csharp_space_before_colon_in_inheritance_clause = true
135 | csharp_space_after_colon_in_inheritance_clause = true
136 | csharp_space_around_binary_operators = before_and_after
137 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
138 | csharp_space_between_method_call_name_and_opening_parenthesis = false
139 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
140 | csharp_space_after_comma = true
141 | csharp_space_after_dot = false
142 |
143 | # Wrapping preferences
144 | csharp_preserve_single_line_statements = true
145 | csharp_preserve_single_line_blocks = true
146 |
147 | ##################################
148 | # Visual Basic Code Style Rules #
149 | ##################################
150 |
151 | [*.vb]
152 | # Modifier preferences
153 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 |
56 | # StyleCop
57 | StyleCopReport.xml
58 |
59 | # Files built by Visual Studio
60 | *_i.c
61 | *_p.c
62 | *_h.h
63 | *.ilk
64 | *.meta
65 | *.obj
66 | *.iobj
67 | *.pch
68 | *.pdb
69 | *.ipdb
70 | *.pgc
71 | *.pgd
72 | *.rsp
73 | *.sbr
74 | *.tlb
75 | *.tli
76 | *.tlh
77 | *.tmp
78 | *.tmp_proj
79 | *.log
80 | *.vspscc
81 | *.vssscc
82 | .builds
83 | *.pidb
84 | *.svclog
85 | *.scc
86 |
87 | # Chutzpah Test files
88 | _Chutzpah*
89 |
90 | # Visual C++ cache files
91 | ipch/
92 | *.aps
93 | *.ncb
94 | *.opendb
95 | *.opensdf
96 | *.sdf
97 | *.cachefile
98 | *.VC.db
99 | *.VC.VC.opendb
100 |
101 | # Visual Studio profiler
102 | *.psess
103 | *.vsp
104 | *.vspx
105 | *.sap
106 |
107 | # Visual Studio Trace Files
108 | *.e2e
109 |
110 | # TFS 2012 Local Workspace
111 | $tf/
112 |
113 | # Guidance Automation Toolkit
114 | *.gpState
115 |
116 | # ReSharper is a .NET coding add-in
117 | _ReSharper*/
118 | *.[Rr]e[Ss]harper
119 | *.DotSettings.user
120 |
121 | # JustCode is a .NET coding add-in
122 | .JustCode
123 |
124 | # TeamCity is a build add-in
125 | _TeamCity*
126 |
127 | # DotCover is a Code Coverage Tool
128 | *.dotCover
129 |
130 | # AxoCover is a Code Coverage Tool
131 | .axoCover/*
132 | !.axoCover/settings.json
133 |
134 | # Visual Studio code coverage results
135 | *.coverage
136 | *.coveragexml
137 |
138 | # NCrunch
139 | _NCrunch_*
140 | .*crunch*.local.xml
141 | nCrunchTemp_*
142 |
143 | # MightyMoose
144 | *.mm.*
145 | AutoTest.Net/
146 |
147 | # Web workbench (sass)
148 | .sass-cache/
149 |
150 | # Installshield output folder
151 | [Ee]xpress/
152 |
153 | # DocProject is a documentation generator add-in
154 | DocProject/buildhelp/
155 | DocProject/Help/*.HxT
156 | DocProject/Help/*.HxC
157 | DocProject/Help/*.hhc
158 | DocProject/Help/*.hhk
159 | DocProject/Help/*.hhp
160 | DocProject/Help/Html2
161 | DocProject/Help/html
162 |
163 | # Click-Once directory
164 | publish/
165 |
166 | # Publish Web Output
167 | *.[Pp]ublish.xml
168 | *.azurePubxml
169 | # Note: Comment the next line if you want to checkin your web deploy settings,
170 | # but database connection strings (with potential passwords) will be unencrypted
171 | *.pubxml
172 | *.publishproj
173 |
174 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
175 | # checkin your Azure Web App publish settings, but sensitive information contained
176 | # in these scripts will be unencrypted
177 | PublishScripts/
178 |
179 | # NuGet Packages
180 | *.nupkg
181 | # The packages folder can be ignored because of Package Restore
182 | **/[Pp]ackages/*
183 | # except build/, which is used as an MSBuild target.
184 | !**/[Pp]ackages/build/
185 | # Uncomment if necessary however generally it will be regenerated when needed
186 | #!**/[Pp]ackages/repositories.config
187 | # NuGet v3's project.json files produces more ignorable files
188 | *.nuget.props
189 | *.nuget.targets
190 |
191 | # Microsoft Azure Build Output
192 | csx/
193 | *.build.csdef
194 |
195 | # Microsoft Azure Emulator
196 | ecf/
197 | rcf/
198 |
199 | # Windows Store app package directories and files
200 | AppPackages/
201 | BundleArtifacts/
202 | Package.StoreAssociation.xml
203 | _pkginfo.txt
204 | *.appx
205 |
206 | # Visual Studio cache files
207 | # files ending in .cache can be ignored
208 | *.[Cc]ache
209 | # but keep track of directories ending in .cache
210 | !*.[Cc]ache/
211 |
212 | # Others
213 | ClientBin/
214 | ~$*
215 | *~
216 | *.dbmdl
217 | *.dbproj.schemaview
218 | *.jfm
219 | *.pfx
220 | *.publishsettings
221 | orleans.codegen.cs
222 |
223 | # Including strong name files can present a security risk
224 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
225 | #*.snk
226 |
227 | # Since there are multiple workflows, uncomment next line to ignore bower_components
228 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
229 | #bower_components/
230 |
231 | # RIA/Silverlight projects
232 | Generated_Code/
233 |
234 | # Backup & report files from converting an old project file
235 | # to a newer Visual Studio version. Backup files are not needed,
236 | # because we have git ;-)
237 | _UpgradeReport_Files/
238 | Backup*/
239 | UpgradeLog*.XML
240 | UpgradeLog*.htm
241 | ServiceFabricBackup/
242 | *.rptproj.bak
243 |
244 | # SQL Server files
245 | *.mdf
246 | *.ldf
247 | *.ndf
248 |
249 | # Business Intelligence projects
250 | *.rdl.data
251 | *.bim.layout
252 | *.bim_*.settings
253 | *.rptproj.rsuser
254 |
255 | # Microsoft Fakes
256 | FakesAssemblies/
257 |
258 | # GhostDoc plugin setting file
259 | *.GhostDoc.xml
260 |
261 | # Node.js Tools for Visual Studio
262 | .ntvs_analysis.dat
263 | node_modules/
264 |
265 | # Visual Studio 6 build log
266 | *.plg
267 |
268 | # Visual Studio 6 workspace options file
269 | *.opt
270 |
271 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
272 | *.vbw
273 |
274 | # Visual Studio LightSwitch build output
275 | **/*.HTMLClient/GeneratedArtifacts
276 | **/*.DesktopClient/GeneratedArtifacts
277 | **/*.DesktopClient/ModelManifest.xml
278 | **/*.Server/GeneratedArtifacts
279 | **/*.Server/ModelManifest.xml
280 | _Pvt_Extensions
281 |
282 | # Paket dependency manager
283 | .paket/paket.exe
284 | paket-files/
285 |
286 | # FAKE - F# Make
287 | .fake/
288 |
289 | # JetBrains Rider
290 | .idea/
291 | *.sln.iml
292 |
293 | # CodeRush
294 | .cr/
295 |
296 | # Python Tools for Visual Studio (PTVS)
297 | __pycache__/
298 | *.pyc
299 |
300 | # Cake - Uncomment if you are using it
301 | # tools/**
302 | # !tools/packages.config
303 |
304 | # Tabs Studio
305 | *.tss
306 |
307 | # Telerik's JustMock configuration file
308 | *.jmconfig
309 |
310 | # BizTalk build output
311 | *.btp.cs
312 | *.btm.cs
313 | *.odx.cs
314 | *.xsd.cs
315 |
316 | # OpenCover UI analysis results
317 | OpenCover/
318 |
319 | # Azure Stream Analytics local run output
320 | ASALocalRun/
321 |
322 | # MSBuild Binary and Structured Log
323 | *.binlog
324 |
325 | # NVidia Nsight GPU debugger configuration file
326 | *.nvuser
327 |
328 | # MFractors (Xamarin productivity tool) working folder
329 | .mfractor/
330 |
331 | # Local History for Visual Studio
332 | .localhistory/
333 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/MessageBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using CM.Text.BusinessMessaging.Model;
4 | using CM.Text.BusinessMessaging.Model.MultiChannel;
5 | using CM.Text.Common;
6 | using JetBrains.Annotations;
7 |
8 | namespace CM.Text.BusinessMessaging
9 | {
10 | ///
11 | /// Builder class to construct messages
12 | ///
13 | [PublicAPI]
14 | public class MessageBuilder
15 | {
16 | private readonly Message _message;
17 | private RichContent _richContent;
18 |
19 | ///
20 | /// Creates a new MessageBuilder
21 | ///
22 | ///
23 | ///
24 | ///
25 | public MessageBuilder(string messageText, string from, params string[] to)
26 | {
27 | _message = new Message
28 | {
29 | Body = new Body
30 | {
31 | Content = messageText,
32 | Type = Constant.BusinessMessagingBodyTypeAuto
33 | },
34 | Recipients = to.Select(toEntry => new Recipient { Number = toEntry })
35 | .ToArray(),
36 | From = from,
37 | CustomGrouping3 = Constant.TextSdkReference
38 | };
39 | }
40 |
41 | ///
42 | /// Constructs the message.
43 | ///
44 | ///
45 | public Message Build()
46 | {
47 | _message.RichContent = _richContent;
48 | return _message;
49 | }
50 |
51 | ///
52 | /// Adds the allowed channels field, which forces a message to only use certain routes.
53 | /// You can define a list of which channels you want your message to use.
54 | /// Not defining any channels will be interpreted as allowing all channels.
55 | ///
56 | ///
57 | /// Note that for channels other than SMS, CM needs to configure the out going flows.
58 | /// For those flows to work, we need to be contacted.
59 | ///
60 | public MessageBuilder WithAllowedChannels(params Channel[] channels)
61 | {
62 | _message.AllowedChannels = channels;
63 | return this;
64 | }
65 |
66 | ///
67 | /// Add a reference to the message.
68 | ///
69 | ///
70 | ///
71 | public MessageBuilder WithReference(string reference)
72 | {
73 | _message.Reference = reference;
74 | return this;
75 | }
76 |
77 | ///
78 | /// Add a validity period to the message.
79 | ///
80 | ///
81 | /// You can supply the time zone for the validity period using either of the following formats:
82 | ///
83 | /// Absolute date and time:
84 | ///
85 | /// 2017-04-20 11:50:05 GMT
86 | /// 2017-04-20 11:50:05+8
87 | /// 2017-04-20 11:55:05-07:00
88 | /// If no time zone was specified, CE(S)T will be used. (CM local time)
89 | ///
90 | /// Relative offset in hour(h) or minute(m)
91 | ///
92 | /// 2h
93 | /// 30m
94 | /// You can set the validity in either hours or minutes. A combination of both is not supported.
95 | ///
96 | ///
97 | ///
98 | public MessageBuilder WithValidityPeriod(string period)
99 | {
100 | _message.Validity = period;
101 | return this;
102 | }
103 |
104 | ///
105 | /// Adds a message that replaces the for channels that support
106 | /// rich content (all channels except ,
107 | /// and at this moment)
108 | ///
109 | ///
110 | ///
111 | public MessageBuilder WithRichMessage(IRichMessage richMessage)
112 | {
113 | if (_richContent == null)
114 | _richContent = new RichContent();
115 |
116 | _richContent.AddConversationPart(richMessage);
117 | return this;
118 | }
119 |
120 | ///
121 | /// Adds suggestions to the message. It is dependent on the channel that is used which
122 | /// suggestions are supported.
123 | ///
124 | ///
125 | ///
126 | public MessageBuilder WithSuggestions(params SuggestionBase[] suggestions)
127 | {
128 | if (_richContent == null)
129 | _richContent = new RichContent();
130 |
131 | _richContent.Suggestions = suggestions;
132 | return this;
133 | }
134 |
135 | ///
136 | /// Used for Hybrid messaging, see https://developers.cm.com/messaging/docs for more information
137 | /// Messages will be sent over the channel.
138 | ///
139 | public MessageBuilder WitHybridAppKey(Guid appKey)
140 | {
141 | _message.HybridAppKey = appKey;
142 | return this;
143 | }
144 |
145 | ///
146 | /// Adds a WhatsApp template message that replaces the for WhatsApp
147 | /// please note that you need to have approved wa templates.
148 | ///
149 | ///
150 | ///
151 | public MessageBuilder WithTemplate(TemplateMessage template)
152 | {
153 | if (_richContent == null)
154 | _richContent = new RichContent();
155 |
156 | _richContent.AddConversationPart(template);
157 | return this;
158 | }
159 |
160 | ///
161 | /// Adds a WhatsApp interactive message that replaces the for WhatsApp
162 | ///
163 | ///
164 | ///
165 | public MessageBuilder WithInteractive(WhatsAppInteractiveMessage interactive)
166 | {
167 | if (_richContent == null)
168 | _richContent = new RichContent();
169 |
170 | _richContent.AddConversationPart(interactive);
171 | return this;
172 | }
173 |
174 | ///
175 | /// Adds a ApplePay request .
176 | ///
177 | ///
178 | ///
179 | public MessageBuilder WithApplePay(ApplePayRequest applePayRequest)
180 | {
181 | if (_richContent == null)
182 | _richContent = new RichContent();
183 |
184 | _richContent.AddConversationPart(applePayRequest);
185 | return this;
186 | }
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/ContactMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// Used to send one or multiple contacts, for now only supported in .
8 | ///
9 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
10 | ///
11 | [PublicAPI]
12 | public class ContactMessage : IRichMessage
13 | {
14 | ///
15 | /// The contacts to send.
16 | ///
17 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
18 | ///
19 | [JsonPropertyName("contacts")]
20 | public Contact[] Contacts { get; set; }
21 |
22 | ///
23 | /// Contextual properties of the message
24 | ///
25 | [JsonPropertyName("context")]
26 | [CanBeNull]
27 | public MessageContext MessageContext { get; set; }
28 | }
29 |
30 | ///
31 | /// Represents 1 contact.
32 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
33 | ///
34 | public class Contact
35 | {
36 | ///
37 | /// Full contact address(es)
38 | ///
39 | [JsonPropertyName("addresses")]
40 | public ContactAddress[] ContactAddresses { get; set; }
41 |
42 | ///
43 | /// YYYY-MM-DD formatted string of the birthday of the contact
44 | ///
45 | [JsonPropertyName("birthday")]
46 | public string Birthday { get; set; }
47 |
48 | ///
49 | /// Contact email address(es)
50 | ///
51 | [JsonPropertyName("emails")]
52 | public ContactEmail[] EmailAddresses { get; set; }
53 |
54 | ///
55 | /// Full contact name
56 | ///
57 | [JsonPropertyName("name")]
58 | public ContactName Name { get; set; }
59 |
60 | ///
61 | /// Contact organization information
62 | ///
63 | [JsonPropertyName("org")]
64 | public ContactOrganization Organization { get; set; }
65 |
66 | ///
67 | /// Contact phone number(s)
68 | ///
69 | [JsonPropertyName("phones")]
70 | public ContactPhoneNumber[] PhoneNumbers { get; set; }
71 |
72 | ///
73 | /// Contact URL(s)
74 | ///
75 | [JsonPropertyName("urls")]
76 | public ContactUrl[] Urls { get; set; }
77 | }
78 |
79 | ///
80 | /// One address of a contact
81 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
82 | ///
83 | public class ContactAddress
84 | {
85 | ///
86 | /// City name
87 | ///
88 | [JsonPropertyName("city")]
89 | public string City { get; set; }
90 | ///
91 | /// Full country name
92 | ///
93 | [JsonPropertyName("country")]
94 | public string Country { get; set; }
95 | ///
96 | /// Two-letter country abbreviation
97 | ///
98 | [JsonPropertyName("country_code")]
99 | public string CountryCode { get; set; }
100 | ///
101 | /// State abbreviation
102 | ///
103 | [JsonPropertyName("state")]
104 | public string State { get; set; }
105 | ///
106 | /// Street number and name
107 | ///
108 | [JsonPropertyName("street")]
109 | public string Street { get; set; }
110 | ///
111 | /// Standard Values: HOME, WORK
112 | ///
113 | [JsonPropertyName("type")]
114 | public string Type { get; set; }
115 | ///
116 | /// ZIP code
117 | ///
118 | [JsonPropertyName("zip")]
119 | public string ZipCode { get; set; }
120 | }
121 |
122 | ///
123 | /// One email address of a contact.
124 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
125 | ///
126 | public class ContactEmail
127 | {
128 | ///
129 | /// Email address
130 | ///
131 | [JsonPropertyName("email")]
132 | public string EmailAddress { get; set; }
133 | ///
134 | /// Standard Values: HOME, WORK
135 | ///
136 | [JsonPropertyName("type")]
137 | public string Type { get; set; }
138 | }
139 |
140 | ///
141 | /// The name of a contact.
142 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
143 | ///
144 | public class ContactName
145 | {
146 | ///
147 | /// First name
148 | ///
149 | [JsonPropertyName("first_name")]
150 | public string FirstName { get; set; }
151 | ///
152 | /// Last name
153 | ///
154 | [JsonPropertyName("last_name")]
155 | public string LastName { get; set; }
156 | ///
157 | /// Middle name
158 | ///
159 | [JsonPropertyName("middle_name")]
160 | public string MiddleName { get; set; }
161 | ///
162 | /// Name prefix
163 | ///
164 | [JsonPropertyName("name_prefix")]
165 | public string NamePrefix { get; set; }
166 | ///
167 | /// Name suffix
168 | ///
169 | [JsonPropertyName("name_suffix")]
170 | public string NameSuffix { get; set; }
171 | ///
172 | /// Full name as it normally appears
173 | ///
174 | [JsonPropertyName("formatted_name")]
175 | public string FormattedName { get; set; }
176 | }
177 | ///
178 | /// The organization of a contact
179 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
180 | ///
181 | public class ContactOrganization
182 | {
183 | ///
184 | /// Name of the contact's company
185 | ///
186 | [JsonPropertyName("company")]
187 | public string Company { get; set; }
188 | ///
189 | /// Name of the contact's department
190 | ///
191 | [JsonPropertyName("department")]
192 | public string Department { get; set; }
193 | ///
194 | /// Contact's business title
195 | ///
196 | [JsonPropertyName("title")]
197 | public string Title { get; set; }
198 | }
199 |
200 | ///
201 | /// Phone number of a contact
202 | ///
203 | public class ContactPhoneNumber
204 | {
205 | ///
206 | /// The phone number of the contact
207 | ///
208 | [JsonPropertyName("phone")]
209 | public string Phone { get; set; }
210 | ///
211 | /// Standard Values: CELL, MAIN, IPHONE, HOME, WORK
212 | ///
213 | [JsonPropertyName("type")]
214 | public string Type { get; set; }
215 | }
216 |
217 | ///
218 | /// An Url for a contact
219 | /// See also https://developers.facebook.com/docs/whatsapp/api/messages/others#contacts
220 | ///
221 | public class ContactUrl
222 | {
223 | ///
224 | /// URL
225 | ///
226 | [JsonPropertyName("url")]
227 | public string Url { get; set; }
228 | ///
229 | /// Standard Values: HOME, WORK
230 | ///
231 | [JsonPropertyName("type")]
232 | public string Type { get; set; }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/CM.Text/TextClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Net.Http;
5 | using System.Text;
6 | using System.Text.Json;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using CM.Text.BusinessMessaging;
10 | using CM.Text.BusinessMessaging.Model;
11 | using CM.Text.Common;
12 | using CM.Text.Identity;
13 | using CM.Text.Interfaces;
14 | using JetBrains.Annotations;
15 |
16 | namespace CM.Text
17 | {
18 | ///
19 | /// This class provides methods to send text messages.
20 | ///
21 | [PublicAPI]
22 | public class TextClient : ITextClient
23 | {
24 | private static readonly Lazy ClientSingletonLazy = new Lazy();
25 | private readonly Guid _apiKey;
26 | [CanBeNull] private readonly Uri _endPointOverride;
27 | private readonly HttpClient _httpClient;
28 |
29 | ///
30 | /// Initializes a new instance of the class.
31 | ///
32 | /// The API key.
33 | [PublicAPI]
34 | [SuppressMessage("ReSharper", "IntroduceOptionalParameters.Global", Justification = "Backwards compatibility")]
35 | public TextClient(Guid apiKey) : this(apiKey, null)
36 | {
37 | }
38 |
39 | ///
40 | /// Initializes a new instance of the class.
41 | ///
42 | /// The API key.
43 | /// An optional HTTP client.
44 | [PublicAPI]
45 | [SuppressMessage("ReSharper", "IntroduceOptionalParameters.Global", Justification = "Binary backwards compatibility")]
46 | public TextClient(Guid apiKey, [CanBeNull] HttpClient httpClient): this(apiKey, httpClient, null)
47 | {
48 | }
49 |
50 | ///
51 | /// Initializes a new instance of the class.
52 | ///
53 | /// The API key.
54 | /// An optional HTTP client.
55 | /// (Optional) The end point to use, instead of the default "https://gw.messaging.cm.com/v1.0/message".
56 | [PublicAPI]
57 | public TextClient(Guid apiKey, [CanBeNull] HttpClient httpClient, [CanBeNull] Uri endPointOverride)
58 | {
59 | _apiKey = apiKey;
60 | _httpClient = httpClient ?? ClientSingletonLazy.Value;
61 | _endPointOverride = endPointOverride;
62 | }
63 |
64 | ///
65 | [PublicAPI]
66 | public async Task SendMessageAsync(
67 | string messageText,
68 | string from,
69 | IEnumerable to,
70 | string reference,
71 | CancellationToken cancellationToken = default(CancellationToken))
72 | {
73 | using (var request = new HttpRequestMessage(
74 | HttpMethod.Post,
75 | _endPointOverride ?? new Uri(Constant.BusinessMessagingGatewayJsonEndpoint)
76 | ))
77 | {
78 | request.Content = new StringContent(
79 | BusinessMessagingApi.GetHttpPostBody(_apiKey, messageText, from, to, reference),
80 | Encoding.UTF8,
81 | Constant.BusinessMessagingGatewayMediaTypeJson
82 | );
83 |
84 | using (var requestResult = await _httpClient.SendAsync(request, cancellationToken)
85 | .ConfigureAwait(false))
86 | {
87 | cancellationToken.ThrowIfCancellationRequested();
88 |
89 | return BusinessMessagingApi.GetTextApiResult(
90 | await requestResult.Content.ReadAsStringAsync()
91 | .ConfigureAwait(false)
92 | );
93 | }
94 | }
95 | }
96 |
97 | ///
98 | /// Sends a message asynchronously.
99 | ///
100 | /// The message to send.
101 | /// The cancellation token.
102 | ///
103 | [PublicAPI]
104 | public async Task SendMessageAsync(
105 | Message message,
106 | CancellationToken cancellationToken = default(CancellationToken))
107 | {
108 | using (var request = new HttpRequestMessage(
109 | HttpMethod.Post,
110 | _endPointOverride ?? new Uri(Constant.BusinessMessagingGatewayJsonEndpoint)
111 | ))
112 | {
113 | request.Content = new StringContent(
114 | BusinessMessagingApi.GetHttpPostBody(_apiKey, message),
115 | Encoding.UTF8,
116 | Constant.BusinessMessagingGatewayMediaTypeJson
117 | );
118 |
119 | using (var requestResult = await _httpClient.SendAsync(request, cancellationToken)
120 | .ConfigureAwait(false))
121 | {
122 | cancellationToken.ThrowIfCancellationRequested();
123 |
124 | return BusinessMessagingApi.GetTextApiResult(
125 | await requestResult.Content.ReadAsStringAsync()
126 | .ConfigureAwait(false)
127 | );
128 | }
129 | }
130 | }
131 |
132 | ///
133 | /// Sends an One Time Password asynchronously.
134 | ///
135 | /// The otp to send.
136 | /// The cancellation token.
137 | ///
138 | [PublicAPI]
139 | public async Task SendOtpAsync(
140 | OtpRequest otpRequest,
141 | CancellationToken cancellationToken = default(CancellationToken))
142 | {
143 | using (var request = new HttpRequestMessage(
144 | HttpMethod.Post,
145 | _endPointOverride ?? new Uri(Constant.OtpRequestEndpoint)
146 | ))
147 | {
148 | request.Content = new StringContent(
149 | JsonSerializer.Serialize(otpRequest),
150 | Encoding.UTF8,
151 | Constant.BusinessMessagingGatewayMediaTypeJson
152 | );
153 |
154 | return await SendOtpApiRequestAsync(request, cancellationToken);
155 | }
156 | }
157 |
158 | ///
159 | /// Checks an One Time Password asynchronously.
160 | ///
161 | /// id of the OTP to check.
162 | /// The code the end user used
163 | /// The cancellation token.
164 | ///
165 | [PublicAPI]
166 | public async Task VerifyOtpAsync(
167 | string id,
168 | string code,
169 | CancellationToken cancellationToken = default(CancellationToken))
170 | {
171 | using (var request = new HttpRequestMessage(
172 | HttpMethod.Post,
173 | _endPointOverride ?? new Uri(string.Format(Constant.OtpVerifyEndpointFormatter, id))
174 | ))
175 | {
176 | request.Content = new StringContent(
177 | JsonSerializer.Serialize(new { code = code } ),
178 | Encoding.UTF8,
179 | Constant.BusinessMessagingGatewayMediaTypeJson
180 | );
181 |
182 | return await SendOtpApiRequestAsync(request, cancellationToken);
183 | }
184 | }
185 |
186 | private async Task SendOtpApiRequestAsync(HttpRequestMessage request,
187 | CancellationToken cancellationToken)
188 | {
189 | request.Headers.Add("X-CM-ProductToken", _apiKey.ToString());
190 | using (var requestResult = await _httpClient.SendAsync(request, cancellationToken)
191 | .ConfigureAwait(false))
192 | {
193 | cancellationToken.ThrowIfCancellationRequested();
194 |
195 | return JsonSerializer.Deserialize(
196 | await requestResult.Content.ReadAsStringAsync()
197 | .ConfigureAwait(false)
198 | );
199 | }
200 | }
201 | }
202 | }
203 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/Message.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using CM.Text.BusinessMessaging.Model.MultiChannel;
4 | using JetBrains.Annotations;
5 |
6 | namespace CM.Text.BusinessMessaging.Model
7 | {
8 | ///
9 | /// Represents one message. One message can be sent to multiple recipients.
10 | ///
11 | [PublicAPI]
12 | public class Message
13 | {
14 | ///
15 | /// The allowed channels field forces a message to only use certain routes.
16 | /// In this field you can define a list of which channels you want your message to use.
17 | /// Not defining any channels will be interpreted as allowing all channels.
18 | ///
19 | ///
20 | /// Note that for channels other than SMS, CM needs to configure the out going flows.
21 | /// For those flows to work, we need to be contacted.
22 | ///
23 | [JsonPropertyName("allowedChannels")]
24 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
25 | public Channel[] AllowedChannels { get; set; }
26 |
27 | ///
28 | /// Required: The actual text body of the message.
29 | ///
30 | [JsonPropertyName("body")]
31 | public Body Body { get; set; }
32 |
33 | ///
34 | /// The custom grouping field is an optional field that can be used to tag messages.
35 | /// These tags are be used by CM products, like the Transactions API.
36 | /// Applying custom grouping names to messages helps filter your messages.
37 | /// With up to three levels of custom grouping fields that can be set, subsets of messages can be
38 | /// further broken down. The custom grouping name can be up to 100 characters of your choosing.
39 | /// It’s recommended to limit the number of unique custom groupings to 1000.
40 | /// Please contact support in case you would like to exceed this number.
41 | ///
42 | [JsonPropertyName("customGrouping")]
43 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
44 | public string CustomGrouping { get; set; }
45 |
46 | ///
47 | /// The custom grouping2 field, like is an optional field that can be used to tag
48 | /// messages.
49 | /// These tags are be used by CM products, like the Transactions API.
50 | /// Applying custom grouping names to messages helps filter your messages.
51 | /// With up to three levels of custom grouping fields that can be set, subsets of messages can be
52 | /// further broken down. The custom grouping name can be up to 100 characters of your choosing.
53 | /// It’s recommended to limit the number of unique custom groupings to 1000.
54 | /// Please contact support in case you would like to exceed this number.
55 | ///
56 | [JsonPropertyName("customGrouping2")]
57 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
58 | public string CustomGrouping2 { get; set; }
59 |
60 | ///
61 | /// The custom grouping3 field, like and is an optional
62 | /// field that can be used to tag messages.
63 | /// These tags are be used by CM products, like the Transactions API.
64 | /// Applying custom grouping names to messages helps filter your messages.
65 | /// With up to three levels of custom grouping fields that can be set, subsets of messages can be
66 | /// further broken down. The custom grouping name can be up to 100 characters of your choosing.
67 | /// It’s recommended to limit the number of unique custom groupings to 1000.
68 | /// Please contact support in case you would like to exceed this number.
69 | ///
70 | /// Default value within this SDK is
71 | [JsonPropertyName("customGrouping3")]
72 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
73 | public string CustomGrouping3 { get; set; }
74 |
75 | ///
76 | /// Required: This is the sender name.
77 | /// The maximum length is 11 alphanumerical characters or 16 digits. Example: 'MyCompany'
78 | ///
79 | [JsonPropertyName("from")]
80 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
81 | public string From { get; set; }
82 |
83 | ///
84 | /// Used for Hybrid messaging, see https://developers.cm.com/messaging/docs for more information
85 | /// Messages will be sent over the channel.
86 | ///
87 | [JsonPropertyName("appKey")]
88 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
89 | public Guid? HybridAppKey { get; set; }
90 |
91 | ///
92 | /// Used when sending multipart or concatenated SMS messages and always used together with
93 | /// .
94 | /// Indicate the minimum and maximum of message parts that you allow the gateway to send for this
95 | /// message.
96 | /// Technically the gateway will first check if a message is larger than 160 characters, if so, the
97 | /// message will be cut into multiple 153 characters parts limited by these parameters.
98 | ///
99 | [JsonPropertyName("maximumNumberOfMessageParts")]
100 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
101 | public int? MaximumNumberOfMessageParts { get; set; }
102 |
103 | ///
104 | /// Used when sending multipart or concatenated SMS messages and always used together with
105 | /// .
106 | /// Indicate the minimum and maximum of message parts that you allow the gateway to send for this
107 | /// message.
108 | /// Technically the gateway will first check if a message is larger than 160 characters, if so, the
109 | /// message will be cut into multiple 153 characters parts limited by these parameters.
110 | ///
111 | [JsonPropertyName("minimumNumberOfMessageParts")]
112 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
113 | public int? MinimumNumberOfMessageParts { get; set; }
114 |
115 | ///
116 | /// Required: The destination mobile numbers.
117 | /// This value should be in international format.
118 | /// A single mobile number per request. Example: '00447911123456'
119 | ///
120 | [JsonPropertyName("to")]
121 | public Recipient[] Recipients { get; set; }
122 |
123 | ///
124 | /// Optional: For each message you send, you can set a reference.
125 | /// The given reference will be used in the status reports and MO replies for the message,
126 | /// so you can link the messages to the sent batch.
127 | /// For more information on status reports, see:
128 | /// https://developers.cm.com/messaging/docs/incoming-status-report
129 | /// The given reference must be between 1 - 32 alphanumeric characters, and will not work using demo accounts.
130 | ///
131 | [JsonPropertyName("reference")]
132 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
133 | public string Reference { get; set; }
134 |
135 | ///
136 | /// Can be used by channels that support rich content (all channels except ,
137 | /// and at this moment)
138 | ///
139 | [JsonPropertyName("richContent")]
140 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
141 | public RichContent RichContent { get; set; }
142 |
143 | ///
144 | /// Optional: For each message you send, you can set a validity.
145 | /// Specify a time at which a delayed message can be considered irrelevant, you can supply an absolute date & time
146 | /// or a relative offset. A message is considered failed if it was not successfully delivered before that time.
147 | /// And via a Status Report we inform you this was the case.
148 | /// For more information on status reports, see:
149 | /// https://developers.cm.com/messaging/docs/incoming-status-report
150 | /// You can supply the time zone for the validity period using either of the following formats:
151 | ///
152 | /// Absolute date and time:
153 | ///
154 | /// 2017-04-20 11:50:05 GMT
155 | /// 2017-04-20 11:50:05+8
156 | /// 2017-04-20 11:55:05-07:00
157 | /// If no time zone was specified, CE(S)T will be used. (CM local time)
158 | ///
159 | /// Relative offset in hour(H) or minute(M)
160 | ///
161 | /// 2h
162 | /// 30m
163 | /// You can set the validity in either hours or minutes. A combination of both is not supported.
164 | ///
165 | [JsonPropertyName("validity")]
166 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
167 | public string Validity { get; set; }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/WhatsAppTemplate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.Json.Serialization;
3 | using JetBrains.Annotations;
4 |
5 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
6 | {
7 | ///
8 | /// WhatsApp template, see https://developers.facebook.com/docs/whatsapp/api/messages/message-templates for more information
9 | /// Used only in .
10 | ///
11 | /// Templates need to be configured by CM and approved by WhatsApp before it is possible
12 | /// to send these messages.
13 | ///
14 | [PublicAPI]
15 | public class WhatsappTemplate
16 | {
17 | ///
18 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
19 | /// The namespace that will be used
20 | ///
21 | [JsonPropertyName("namespace")]
22 | public string Namespace { get; set; }
23 |
24 | ///
25 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
26 | /// The element name that indicates which template to use within the namespace
27 | ///
28 | [JsonPropertyName("element_name")]
29 | public string Name { get; set; }
30 |
31 | ///
32 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
33 | /// Allows for the specification of a deterministic or fallback language.
34 | ///
35 | /// The language parameter sets the language policy for an Message Template;
36 | /// you can set it to either fallback or deterministic.
37 | ///
38 | [JsonPropertyName("language")]
39 | public Language Language { get; set; }
40 |
41 |
42 | ///
43 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
44 | /// This field is an array of values to apply to variables in the template
45 | ///
46 | public TemplateComponents[] Components { get; set; }
47 | }
48 |
49 | ///
50 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
51 | /// This field is an array of values to apply to variables in the template
52 | ///
53 | [PublicAPI]
54 | public class LocalizableParam
55 | {
56 | ///
57 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
58 | ///
59 | /// Default text if localization fails
60 | ///
61 | [JsonPropertyName("default")]
62 | public string Default { get; set; }
63 |
64 | ///
65 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
66 | ///
67 | /// If the currency object is used, it contains required parameters currency_code and amount_1000.
68 | ///
69 | [JsonPropertyName("currency")]
70 | public TemplateCurrency Currency { get; set; }
71 |
72 | ///
73 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
74 | ///
75 | /// If the date_time object is used, further definition of the date and time is required.
76 | ///
77 | [JsonPropertyName("date_time")]
78 | public TemplateDateTime DateTime { get; set; }
79 |
80 | }
81 |
82 | ///
83 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
84 | ///
85 | /// Object to make it possible to use a localized currency format.
86 | /// If the currency object is used, it contains required parameters currency_code and amount_1000.
87 | ///
88 | [PublicAPI]
89 | public class TemplateCurrency
90 | {
91 | ///
92 | /// The Fallback amount
93 | ///
94 | [JsonPropertyName("fallback_value")]
95 | public string FallbackValue { get; set; }
96 |
97 | ///
98 | /// Currency code, for example USD or EUR
99 | ///
100 | [JsonPropertyName("code")]
101 | public string CurrencyCode { get; set; }
102 |
103 | ///
104 | /// Amount in currency_code times 1000
105 | ///
106 | /// 50110 EUR becomes €50.11 in the message
107 | [JsonPropertyName("amount_1000")]
108 | public long Amount { get; set; }
109 | }
110 |
111 | ///
112 | /// Used to localize a date/time in a message based on the end-users settings.
113 | ///
114 | public class TemplateDateTime
115 | {
116 | ///
117 | /// The fallback date in UTC format
118 | ///
119 | /// There will be no checking whether this is correct,
120 | [JsonPropertyName("fallback_value")]
121 | public string FallbackValue { get; }
122 |
123 | ///
124 | /// The day of the week as specified in Facebook documentation
125 | /// Options: "MONDAY", 1, "TUESDAY", 2, "WEDNESDAY", 3, "THURSDAY", 4, "FRIDAY", 5, "SATURDAY", 6, "SUNDAY", 7
126 | /// see https://developers.facebook.com/docs/whatsapp/message-templates/localization
127 | ///
128 | ///
129 | /// There will be no checking whether this is correct,
130 | [JsonPropertyName("day_of_week")]
131 | public int DayOfWeek { get; }
132 | ///
133 | /// The day of the month.
134 | ///
135 | [JsonPropertyName("day_of_month")]
136 | public int DayOfMonth { get; }
137 | ///
138 | /// The year.
139 | ///
140 | [JsonPropertyName("year")]
141 | public int Year { get; }
142 | ///
143 | /// The month.
144 | ///
145 | [JsonPropertyName("month")]
146 | public int Month { get; }
147 | ///
148 | /// The hour (24 hour notation)
149 | ///
150 | [JsonPropertyName("hour")]
151 | public int Hour { get; }
152 | ///
153 | /// The minute of the hour.
154 | ///
155 | [JsonPropertyName("minute")]
156 | public int Minute { get; }
157 |
158 | ///
159 | /// Constructor initializing the component.
160 | ///
161 | ///
162 | public TemplateDateTime(DateTime moment)
163 | {
164 | FallbackValue = moment.ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
165 | Minute = moment.Minute;
166 | Hour = moment.Hour;
167 | Month = moment.Month;
168 | Year = moment.Year;
169 | DayOfMonth = moment.Day;
170 | DayOfWeek = moment.DayOfWeek == System.DayOfWeek.Friday ? 7 : (int)moment.DayOfWeek;
171 | }
172 | }
173 |
174 |
175 | ///
176 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
177 | /// The language parameter sets the language policy for an Message Template;
178 | /// you can set it to either fallback or deterministic.
179 | ///
180 | public class Language
181 | {
182 | ///
183 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
184 | /// The code of the language or locale to use — Accepts both language and language_locale formats (e.g., en and en_US).
185 | ///
186 | [JsonPropertyName("code")]
187 | public string Code { get; set; }
188 |
189 | ///
190 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
191 | /// Options: fallback, deterministic
192 | /// The language policy the message should follow
193 | ///
194 | [JsonPropertyName("policy")]
195 | public string Policy { get; set; }
196 | }
197 | ///
198 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
199 | /// Dynamic content of the message. Separated in in different sections.
200 | ///
201 | public class TemplateComponents
202 | {
203 | ///
204 | /// Required. describes the component type. Possible values: header, content, footer.
205 | ///
206 | [JsonPropertyName("type")]
207 | public string Type { get; set; }
208 |
209 | ///
210 | /// Can be empty. describes the Type of button being created. Possible values: quick_reply, url.
211 | ///
212 | [JsonPropertyName("sub_type")]
213 | public string SubType { get; set; }
214 | ///
215 | /// Can be empty. Position index of the button.
216 | /// You can have up to 3 buttons using index values of 0-2.
217 | ///
218 | [JsonPropertyName("index")]
219 | public int Index { get; set; }
220 |
221 | ///
222 | /// Can be empty. Array containing the dynamic content of the message.
223 | ///
224 | [JsonPropertyName("parameters")]
225 | public ComponentParameters[] ComponentParameters { get; set; }
226 | }
227 | ///
228 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
229 | /// Dynamic content of a media template message.
230 | ///
231 | public class ComponentParameters
232 | {
233 | ///
234 | /// Describes the parameter type. Possible values: text, currency, date_time, image, document.
235 | ///
236 | [JsonPropertyName("type")]
237 | public string Type { get; set; }
238 |
239 | ///
240 | /// Only to be filled in when `type` = `text`.
241 | ///
242 | [JsonPropertyName("text")]
243 | public string Text { get; set; }
244 | ///
245 | /// Only to be filled in when `type` = `document` or `image`.
246 | ///
247 | [JsonPropertyName("media")]
248 | public MediaContent Media { get; set; }
249 |
250 | ///
251 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
252 | ///
253 | /// Default text if localization fails
254 | ///
255 | [JsonPropertyName("default")]
256 | public string Default { get; set; }
257 |
258 | ///
259 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
260 | ///
261 | /// If the currency object is used, it contains required parameters currency_code and amount_1000.
262 | ///
263 | [JsonPropertyName("currency")]
264 | public TemplateCurrency Currency { get; set; }
265 |
266 | ///
267 | /// Source: https://developers.facebook.com/docs/whatsapp/api/messages/message-templates
268 | ///
269 | /// If the date_time object is used, further definition of the date and time is required.
270 | ///
271 | [JsonPropertyName("date_time")]
272 | public TemplateDateTime DateTime { get; set; }
273 |
274 | ///
275 | /// Developer-defined payload that will be returned when the button is clicked in addition to the display text on the button
276 | /// Required for quick_reply buttons
277 | ///
278 | [JsonPropertyName("payload")]
279 | public string Payload { get; set; }
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/CM.Text/BusinessMessaging/Model/MultiChannel/WhatsAppInteractiveMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using JetBrains.Annotations;
3 |
4 | namespace CM.Text.BusinessMessaging.Model.MultiChannel
5 | {
6 | ///
7 | /// WhatsApp interactive messages, see https://developers.facebook.com/docs/whatsapp/guides/interactive-messages for more information
8 | /// Used only in .
9 | ///
10 | [PublicAPI]
11 | public class WhatsAppInteractiveMessage : IRichMessage
12 | {
13 |
14 | ///
15 | /// Gets or sets the content of the WhatsApp interactive message
16 | ///
17 | [JsonPropertyName("interactive")]
18 | public WhatsAppInteractiveContent whatsAppInteractiveContent { get; set; }
19 |
20 | ///
21 | /// Contextual properties of the message
22 | ///
23 | [JsonPropertyName("context")]
24 | [CanBeNull]
25 | public MessageContext MessageContext { get; set; }
26 | }
27 |
28 | ///
29 | /// WhatsApp interactive content.
30 | /// Used only in .
31 | ///
32 |
33 | [PublicAPI]
34 | public class WhatsAppInteractiveContent
35 | {
36 | ///
37 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
38 | /// The Type that will be used,
39 | /// either list or button
40 | ///
41 | [JsonPropertyName("type")]
42 | public string Type { get; set; }
43 | ///
44 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
45 | /// Your message’s header.
46 | ///
47 | [JsonPropertyName("header")]
48 | public InteractiveHeader Header { get; set; }
49 | ///
50 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
51 | /// Required Your message’s body.
52 | ///
53 | [JsonPropertyName("body")]
54 | public InteractiveBody Body { get; set; }
55 | ///
56 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
57 | /// Required Your message’s footer.
58 | ///
59 | [JsonPropertyName("footer")]
60 | public InteractiveFooter Footer { get; set; }
61 | ///
62 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
63 | ///Required. Inside action, you must nest:
64 | ///a button field with your button’s content, and
65 | ///at least one section object (maximum of 10).
66 | ///
67 | [JsonPropertyName("action")]
68 | public InteractiveAction Action { get; set; }
69 | }
70 |
71 | ///
72 | /// WhatsApp interactive header.
73 | /// Used only in .
74 | ///
75 | public class InteractiveHeader
76 | {
77 | ///
78 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
79 | /// Required. The header type you would like to use.Supported values are:
80 | ///text: Used for List Messages and Reply Buttons.
81 | ///
82 | [JsonPropertyName("type")]
83 | public string Type { get; set; }
84 | ///
85 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
86 | /// Required if type is set to text.
87 | /// Text for the header.Formatting allows emojis, but not markdown.
88 | ///
89 | [JsonPropertyName("text")]
90 | public string Text { get; set; }
91 |
92 | ///
93 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
94 | /// Gets or sets the media.
95 | ///
96 | [JsonPropertyName("media")]
97 | public MediaContent Media { get; set; }
98 | }
99 |
100 | ///
101 | /// WhatsApp interactive body.
102 | /// Used only in .
103 | ///
104 | public class InteractiveBody
105 | {
106 | ///
107 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
108 | /// Required
109 | /// The body content of the message. Emojis and markdown are supported. Links are supported.
110 | /// Maximum length: 1024 characters.
111 | ///
112 | [JsonPropertyName("text")]
113 | public string Text { get; set; }
114 | }
115 |
116 | ///
117 | /// WhatsApp interactive footer.
118 | /// Used only in .
119 | ///
120 | public class InteractiveFooter
121 | {
122 | ///
123 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
124 | /// Required if the footer object is present.
125 | /// The footer content. Emojis and markdown are supported. Links are supported.
126 | /// Maximum length: 60 characters
127 | ///
128 | [JsonPropertyName("text")]
129 | public string Text { get; set; }
130 | }
131 |
132 | ///
133 | /// WhatsApp interactive action.
134 | /// Used only in .
135 | ///
136 | public class InteractiveAction
137 | {
138 | ///
139 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
140 | /// Required for List Messages.
141 | /// Button content. It cannot be an empty string and must be unique within the message
142 | /// Does not allow emojis or markdown.
143 | ///
144 | [JsonPropertyName("button")]
145 | public string Button { get; set; }
146 | ///
147 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
148 | /// Required for Reply Button Messages.
149 | ///
150 | [JsonPropertyName("buttons")]
151 | public InteractiveButton[] Buttons { get; set; }
152 | ///
153 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
154 | /// Required for List Messages.
155 | ///
156 | [JsonPropertyName("sections")]
157 | public InteractiveSection[] Sections { get; set; }
158 | ///
159 | /// Source: https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages#action-object
160 | /// Product’s unique identifier
161 | /// Required for Single-Product Messages and Multi-Product Messages.
162 | ///
163 | [JsonPropertyName("product_retailer_id")]
164 | public string ProductRetailerId { get; set; }
165 | ///
166 | /// Source: https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages#action-object
167 | /// Unique identifier of the Facebook catalog linked to your WhatsApp Business
168 | /// Required for Single-Product Messages and Multi-Product Messages.
169 | ///
170 | [JsonPropertyName("catalog_id")]
171 | public string CatalogId { get; set; }
172 | }
173 |
174 | ///
175 | /// WhatsApp interactive button.
176 | /// Used only in .
177 | ///
178 | public class InteractiveButton
179 | {
180 | ///
181 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
182 | /// type: only supported type is reply (for Reply Button Messages).
183 | ///
184 | [JsonPropertyName("type")]
185 | public string Type { get; set; }
186 | ///
187 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
188 | /// Button title.It cannot be an empty string and must be unique within the message.
189 | /// Does not allow emojis or markdown. Maximum length: 20 characters.
190 | ///
191 | [JsonPropertyName("title")]
192 | public string Title { get; set; }
193 | ///
194 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
195 | /// id: Unique identifier for your button. This ID is returned in the webhook when the button is clicked by the user.
196 | /// Maximum length: 256 characters.
197 | ///
198 | [JsonPropertyName("id")]
199 | public string Id { get; set; }
200 | ///
201 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
202 | /// Reply Message for your button.
203 | ///
204 | [JsonPropertyName("reply")]
205 | public ReplyMessage Reply { get; set; }
206 | }
207 |
208 | ///
209 | /// WhatsApp reply message.
210 | /// Used only in .
211 | ///
212 | public class ReplyMessage
213 | {
214 | ///
215 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
216 | /// id: Unique identifier for your reply message.
217 | ///
218 | [JsonPropertyName("id")]
219 | public string Id { get; set; }
220 | ///
221 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
222 | /// title: title for your reply button.
223 | ///
224 | [JsonPropertyName("title")]
225 | public string Title { get; set; }
226 | }
227 |
228 | ///
229 | /// WhatsApp interactive section.
230 | /// Used only in .
231 | ///
232 | public class InteractiveSection
233 | {
234 | ///
235 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
236 | /// Title of the section.
237 | /// Maximum length: 24 characters.
238 | ///
239 | [JsonPropertyName("title")]
240 | public string Title { get; set; }
241 | ///
242 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
243 | /// Contains a list of rows.
244 | ///
245 | [JsonPropertyName("rows")]
246 | public Rows[] Rows { get; set; }
247 | ///
248 | /// Source: https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages#action-object
249 | /// Array of product objects. There is a minimum of 1 product per section and a maximum of 30 products across all sections.
250 | /// Required for Multi-Product Messages.
251 | ///
252 | [JsonPropertyName("product_items")]
253 | public WhatsAppProduct[] ProductItems { get; set; }
254 | }
255 |
256 | ///
257 | /// WhatsApp interactive section row.
258 | /// Used only in .
259 | ///
260 | public class Rows
261 | {
262 | ///
263 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
264 | /// Title of the row.
265 | ///
266 | [JsonPropertyName("title")]
267 | public string Title { get; set; }
268 | ///
269 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
270 | /// Id of the row.
271 | ///
272 | [JsonPropertyName("id")]
273 | public string Id { get; set; }
274 | ///
275 | /// Source: https://developers.facebook.com/docs/whatsapp/guides/interactive-messages
276 | /// Description of the row.
277 | ///
278 | [JsonPropertyName("description")]
279 | public string Description { get; set; }
280 | }
281 |
282 | ///
283 | /// https://developers.facebook.com/docs/whatsapp/on-premises/reference/messages#action-object
284 | /// Required for Multi-Product Messages.
285 | ///
286 | public class WhatsAppProduct
287 | {
288 | ///
289 | /// Unique identifier of the product in a catalog
290 | ///
291 | [JsonPropertyName("product_retailer_id")]
292 | public string ProductRetailerId { get; set; }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/cmdotcom/text-sdk-dotnet/actions/workflows/main.yml)
2 | [](https://www.nuget.org/packages/CM.Text)
3 | [](https://www.nuget.org/packages/CM.Text)
4 |
5 | # CM Text SDK
6 | A software development kit to provide ways to interact with CM.com's Text service. API's used:
7 | - [Messaging](https://developers.cm.com/messaging/docs)
8 |
9 | # Usage
10 |
11 | ## Instantiate the client
12 | Using your unique `ApiKey` (or product token) which authorizes you on the CM platform.
13 | Always keep this key secret!
14 |
15 | The product token can be found in the [Channels](https://www.cm.com/app/channels) application on the platform, under the `Gateway` section.
16 |
17 | ```cs
18 | var client = new TextClient(new Guid(ConfigurationManager.AppSettings["ApiKey"]));
19 | ```
20 |
21 | ## Send a message
22 | By calling `SendMessageAsync` and providing message text, sender name, recipient phone number(s) and a reference (optional).
23 |
24 | ```cs
25 | var result = await client.SendMessageAsync("Message_Text", "Sender_Name", new List { "Recipient_PhoneNumber" }, "Your_Reference").ConfigureAwait(false);
26 | ```
27 |
28 | ## Get the result
29 | `SendMessageAsync` returns an object of type `TextClientResult`, example:
30 |
31 | ```cs
32 | {
33 | "statusMessage": "Created 1 message(s)",
34 | "statusCode": 201,
35 | "details": [
36 | {
37 | "reference": "Example_Reference",
38 | "status": "Accepted",
39 | "to": "Example_PhoneNumber",
40 | "parts": 1,
41 | "details": null
42 | },
43 | {
44 | "reference": "Example_Reference2",
45 | "status": "Rejected",
46 | "to": "Example_PhoneNumber2",
47 | "parts": 0,
48 | "details": "A body without content was found"
49 | }
50 | ]
51 | }
52 | ```
53 |
54 | ## Sending a rich message
55 | By using the `MessageBuilder` it is possible to create images with media for channels such as WhatsApp and RCS
56 | ```cs
57 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
58 | var client = new TextClient(apiKey);
59 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
60 | builder
61 | .WithAllowedChannels(Channel.WhatsApp)
62 | .WithRichMessage(
63 | new MediaMessage(
64 | "cm.com",
65 | "https://avatars3.githubusercontent.com/u/8234794?s=200&v=4",
66 | "image/png"
67 | )
68 | );
69 | var message = builder.Build();
70 | var result = await client.SendMessageAsync(message);
71 | ```
72 |
73 | ## Status codes
74 | For all possible status codes, please reference the `TextClientStatusCode` enum.
75 |
76 |
77 | ## Sending a WhatsApp template message
78 | By using the `MessageBuilder` it is possible to create template messages. Please note that this is WhatsApp only and your template needs to be approved before sending.
79 | For more info please check our documentation: https://www.cm.com/en-en/app/docs/api/business-messaging-api/1.0/index#whatsapp-template-message
80 | ```cs
81 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
82 | var client = new TextClient(apiKey);
83 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
84 | builder
85 | .WithAllowedChannels(Channel.WhatsApp)
86 | .WithTemplate(new TemplateMessage() {
87 | Content = new TemplateMessageContent() {
88 | Whatsapp = new WhatsappTemplate() {
89 | Name = "template-name",
90 | Namespace = "the-namespace-of-template",
91 | Language = new Language() {
92 | Code = "en",
93 | Policy = "deterministic"
94 | },
95 | Components = new TemplateComponents[] {
96 | new TemplateComponents() {
97 | Type = "body",
98 | ComponentParameters = new ComponentParameters[] {
99 | new ComponentParameters() {
100 | Type = "text",
101 | Text = "firstname"
102 | }
103 | }
104 | },
105 | }
106 | }
107 | }
108 | });
109 |
110 | var message = builder.Build();
111 | var result = await client.SendMessageAsync(message);
112 | ```
113 | ## Sending a rich WhatsApp template message
114 | It is also possible to send a rich template with an image!
115 |
116 | ```cs
117 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
118 | var client = new TextClient(apiKey);
119 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
120 | builder
121 | .WithAllowedChannels(Channel.WhatsApp)
122 | .WithTemplate(new TemplateMessage() {
123 | Content = new TemplateMessageContent() {
124 | Whatsapp = new WhatsappTemplate() {
125 | Name = "template-name",
126 | Namespace = "the-namespace-of-template",
127 | Language = new Language() {
128 | Code = "en",
129 | Policy = "deterministic"
130 | },
131 | Components = new TemplateComponents[] {
132 | new TemplateComponents() {
133 | Type = "header",
134 | ComponentParameters = new ComponentParameters[] {
135 | new ComponentParameters() {
136 | Type = "image",
137 | Media = new MediaContent() {
138 | MediaName = "cm.com",
139 | MediaUri = "https://avatars3.githubusercontent.com/u/8234794?s=200&v=4"
140 | }
141 | }
142 | }
143 | },
144 | new TemplateComponents() {
145 | Type = "body",
146 | ComponentParameters = new ComponentParameters[] {
147 | new ComponentParameters() {
148 | Type = "text",
149 | Text = "firstname"
150 | }
151 | }
152 | },
153 | }
154 | }
155 | }
156 | });
157 |
158 | var message = builder.Build();
159 | var result = await client.SendMessageAsync(message);
160 | ```
161 | ## Sending a WhatsApp template message with date and Currency
162 | It is also possible to send a rich template with an currency and an date!
163 | please note that the timezone is in UTC format
164 |
165 | ```cs
166 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
167 | var client = new TextClient(apiKey);
168 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
169 | builder
170 | .WithAllowedChannels(Channel.WhatsApp)
171 | .WithTemplate(new TemplateMessage() {
172 | Content = new TemplateMessageContent() {
173 | Whatsapp = new WhatsappTemplate() {
174 | Name = "template-name",
175 | Namespace = "the-namespace-of-template",
176 | Language = new Language()
177 | {
178 | Code = "en",
179 | Policy = "deterministic"
180 | },
181 | Components = new TemplateComponents[] {
182 | new TemplateComponents() {
183 | Type = "header",
184 | ComponentParameters = new ComponentParameters[] {
185 | new ComponentParameters() {
186 | Type = "image",
187 | Media = new MediaContent() {
188 | MediaName = "cm.com",
189 | MediaUri = "https://avatars3.githubusercontent.com/u/8234794?s=200&v=4"
190 | },
191 | }
192 | }
193 | },
194 | new TemplateComponents()
195 | {
196 | Type = "body",
197 | ComponentParameters = new ComponentParameters[]
198 | {
199 | new ComponentParameters()
200 | {
201 | Type = "currency",
202 | Currency = new TemplateCurrency()
203 | {
204 | FallbackValue = "$100.99",
205 | Amount = 100990,
206 | CurrencyCode = "USD"
207 | }
208 | },
209 | new ComponentParameters()
210 | {
211 | Type = "date_time",
212 | DateTime = new TemplateDateTime(DateTime.Now)
213 | }
214 | }}
215 | }
216 | }
217 | }
218 | });
219 |
220 | var message = builder.Build();
221 | var result = await client.SendMessageAsync(message);
222 | ```
223 | ## Sending interactive template messages
224 | Interactive templates allows you to send templates that include buttons.
225 | For more info please visit https://www.cm.com/app/docs/en/api/business-messaging-api/1.0/index#/whatsapp-template-message
226 | ```cs
227 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
228 | var client = new TextClient(apiKey);
229 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
230 | builder.WithAllowedChannels(Channel.WhatsApp).WithTemplate(new TemplateMessage() {
231 | Content = new TemplateMessageContent() {
232 | Whatsapp = new WhatsappTemplate() {
233 | Name = "Template name",
234 | Namespace = "whatsapp template id",
235 | Language = new Language() {
236 | Code = "en",
237 | Policy = "deterministic"
238 | },
239 | Components = new TemplateComponents[] {
240 | new TemplateComponents() {
241 | Type = "body",
242 | ComponentParameters = new ComponentParameters[] {
243 | new ComponentParameters() {
244 | Type = "text",
245 | Text = "your message here"
246 | }
247 | }
248 | },
249 | new TemplateComponents() {
250 | Type = "button",
251 | SubType = "quick_reply",
252 | Index = 0,
253 | ComponentParameters = new ComponentParameters[] {
254 | new ComponentParameters() {
255 | Type = "payload",
256 | Payload = "developer defined payload"
257 | }
258 | }
259 | }
260 | }
261 | }
262 | }
263 | });
264 |
265 | var message = builder.Build();
266 | var result = await client.SendMessageAsync(message);
267 | ```
268 | ## Sending an Apple Pay Request
269 | It is now possible to send an apple pay request only possible in Apple Business Chat
270 |
271 | ```cs
272 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
273 | var client = new TextClient(apiKey);
274 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
275 | builder
276 | .WithAllowedChannels(Channel.iMessage)
277 | .WithApplePay(new ApplePayRequest()
278 | {
279 | ApplePayConfiguration = new ApplePayConfiguration()
280 | {
281 | Total = 1,
282 | RecipientCountryCode = "recipient-country-code",
283 | CurrencyCode = "currency-code",
284 | Description = "product-description",
285 | RecipientEmail = "recipient-email",
286 | languageCountryCode = "language-country-code",
287 | OrderReference = "unique-order-guid",
288 | MerchantName = "merchant-name",
289 | LineItems = new LineItem[]
290 | {
291 | new LineItem()
292 | {
293 | Amount = 1,
294 | Label = "product-name",
295 | Type = "final-or-pending"
296 | },
297 | }
298 | }
299 | });
300 |
301 | var message = builder.Build();
302 | var result = await client.SendMessageAsync(message);
303 | ```
304 | ## Sending WhatsApp interactive messages
305 | It is now possible to send list messages and reply buttons without using templates
306 | only supported in WhatsApp
307 |
308 | ```cs
309 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
310 | var client = new TextClient(apiKey);
311 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
312 | builder.WithAllowedChannels(Channel.WhatsApp).WithInteractive(new CM.Text.BusinessMessaging.Model.MultiChannel.WhatsAppInteractiveMessage()
313 | {
314 | whatsAppInteractiveContent = new CM.Text.BusinessMessaging.Model.MultiChannel.WhatsAppInteractiveContent()
315 | {
316 | Type = "list",
317 | Header = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveHeader()
318 | {
319 | Type = "text",
320 | Text = "List message example"
321 | },
322 | Body = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveBody()
323 | {
324 | Text = "checkout our list message demo"
325 | },
326 | Action = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveAction()
327 | {
328 | Button = "button text",
329 | Sections = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveSection[]
330 | {
331 | new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveSection()
332 | {
333 | Title = "Select an option",
334 | Rows = new CM.Text.BusinessMessaging.Model.MultiChannel.Rows[]
335 | {
336 | new CM.Text.BusinessMessaging.Model.MultiChannel.Rows()
337 | {
338 | Id = "unique Id",
339 | Title = "unique title1",
340 | Description = "description text"
341 | },
342 | new CM.Text.BusinessMessaging.Model.MultiChannel.Rows()
343 | {
344 | Id = "unique Id2",
345 | Title = "unique title2",
346 | Description = "description text"
347 | },
348 | }
349 | }
350 | }
351 | },
352 | Footer = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveFooter()
353 | {
354 | Text = "footer text"
355 | }
356 | }
357 | });
358 |
359 | var message = builder.Build();
360 | var result = await client.SendMessageAsync(message);
361 | ```
362 |
363 | Only with Reply buttons you can send media like image,video or document
364 | see following example.
365 |
366 | ```cs
367 | var apiKey = new Guid(ConfigurationManager.AppSettings["ApiKey"]);
368 | var client = new TextClient(apiKey);
369 | var builder = new MessageBuilder("Message Text", "Sender_name", "Recipient_PhoneNumber");
370 | builder.WithAllowedChannels(Channel.WhatsApp).WithInteractive(new CM.Text.BusinessMessaging.Model.MultiChannel.WhatsAppInteractiveMessage()
371 | {
372 | whatsAppInteractiveContent = new CM.Text.BusinessMessaging.Model.MultiChannel.WhatsAppInteractiveContent()
373 | {
374 | Type = "button",
375 | Header = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveHeader()
376 | {
377 | Type = "image",
378 | Media = new CM.Text.BusinessMessaging.Model.MultiChannel.MediaContent()
379 | {
380 | MediaUri = "https://www.cm.com/cdn/web/blog/content/logo-cmcom.png"
381 | }
382 |
383 | },
384 | Body = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveBody()
385 | {
386 | Text = "checkout our reply message demo"
387 | },
388 | Action = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveAction()
389 | {
390 | Buttons = new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveButton[]
391 | {
392 | new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveButton()
393 | {
394 | Type = "reply",
395 | Reply = new CM.Text.BusinessMessaging.Model.MultiChannel.ReplyMessage()
396 | {
397 | Id = "unique-postback-id1",
398 | Title = "First Button"
399 | }
400 | },
401 | new CM.Text.BusinessMessaging.Model.MultiChannel.InteractiveButton()
402 | {
403 | Type = "reply",
404 | Reply = new CM.Text.BusinessMessaging.Model.MultiChannel.ReplyMessage()
405 | {
406 | Id = "unique-postback-id2",
407 | Title = "Second Button "
408 | }
409 | }
410 | }
411 | }
412 | }
413 | });
414 |
415 | var message = builder.Build();
416 | var result = await client.SendMessageAsync(message);
417 | ```
418 |
419 | ## Using the OTP API
420 | Send a simple OTP code
421 | ```cs
422 | var client = new TextClient(new Guid(ConfigurationManager.AppSettings["ApiKey"]));
423 | var otpBuilder = new OtpRequestBuilder("Sender_name", "Recipient_PhoneNumber");
424 | otpBuilder.WithMessage("Your otp code is {code}.");
425 | var result = await textClient.SendOtpAsync(otpBuilder.Build());
426 | ```
427 |
428 | Verify the response code
429 | ```cs
430 | var verifyResult = client.VerifyOtp("OTP-ID", "code");
431 | bool isValid = verifyResult.Verified;
432 | ```
433 |
434 | For more advanced scenarios see also https://developers.cm.com/identity/docs/one-time-password-create
--------------------------------------------------------------------------------