Skip to content

Instantly share code, notes, and snippets.

@Orlys
Created July 26, 2022 14:57
Show Gist options
  • Save Orlys/6c4222d9142d455040c4022802830ff5 to your computer and use it in GitHub Desktop.
Save Orlys/6c4222d9142d455040c4022802830ff5 to your computer and use it in GitHub Desktop.
台灣簡訊商 Every8d 的發信 API 簡易封裝
// Copyright 2022 Orlys Ma
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
namespace Every8d
{
using Every8d.Abstractions;
using Every8d.Utilities;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.Web;
namespace Requests
{
public static class ShortMessage
{
public static Request WithShortMessage(this IRequestFactory _, string content, params string[] destination)
{
return new Request(content, destination);
}
public sealed class Request : IRequest<Request.Result>
{
internal Request(string content, IEnumerable<string> destinations)
{
Content = content;
Destinations = destinations?.ToArray() ?? Array.Empty<string>();
}
#region Required
public string Content { get; }
public IReadOnlyList<string> Destinations { get; }
#endregion
public string Subject { get; private set; }
public Request WithSubject(string subject)
{
Subject = subject;
return this;
}
public DateTime? Appointment { get; private set; }
public Request WithAppointment(DateTime? appointment)
{
Appointment = appointment;
return this;
}
public TimeSpan? TimeToLive { get; private set; }
public Request WithTimeToLive(TimeSpan? timeToLive)
{
TimeToLive = timeToLive;
return this;
}
private static readonly TimeSpan s_wholeDay = TimeSpan.FromDays(1);
private string ComposeQueryString(ISecret secret)
{
var lifetime = (int)Math.Floor((TimeToLive ?? s_wholeDay).TotalMinutes);
var subject = StringUtils.UrlEncode(Subject);
var content = StringUtils.UrlEncode(Content);
var destinations = StringUtils.UrlEncode(string.Join(",", Destinations));
var appointment = Appointment.HasValue ? Appointment.Value.ToString("yyyyMMddHHmmss") : string.Empty;
var queryString = string.Format("UID={0}&PWD={1}&SB={2}&MSG={3}&DEST={4}&ST={5}&RETRYTIME={6}",
secret.Uid,
secret.Password,
subject,
content,
destinations,
appointment,
lifetime);
return queryString;
}
HttpRequestMessage IRequest<Result>.CreateRequest(ISecret secret)
{
var urib = new UriBuilder
{
Scheme = "https",
Host = "biz2.every8d.com.tw",
Path = "evta/API21/HTTP/sendSMS.ashx",
Query = ComposeQueryString(secret)
};
return new HttpRequestMessage(HttpMethod.Get, urib.Uri);
}
async Task<Result> IRequest<Result>.ParseResponse(HttpResponseMessage responseMessage)
{
var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
var parts = response.Split(',');
return new Result(
credit: decimal.Parse(parts[0]),
sentCount: int.Parse(parts[1]),
cost: decimal.Parse(parts[2]),
unsentCount: int.Parse(parts[3]),
batchId: Guid.Parse(parts[4]));
}
public sealed class Result : IRequestResult
{
internal Result(decimal credit, int sentCount, decimal cost, int unsentCount, Guid batchId)
{
Credit = credit;
SentCount = sentCount;
Cost = cost;
UnsentCount = unsentCount;
BatchId = batchId;
}
public decimal Credit { get; }
public int SentCount { get; }
public decimal Cost { get; }
public int UnsentCount { get; }
public Guid BatchId { get; }
}
}
}
}
namespace Requests
{
public static class Balance
{
public static Request WithBalance(this IRequestFactory _)
{
return new Request();
}
public sealed class Request : IRequest<Request.Result>
{
public sealed class Result : IRequestResult
{
internal Result(decimal balance)
{
Balance = balance;
}
public decimal Balance { get; }
}
private string ComposeQueryString(ISecret secret)
{
var queryString = string.Format("UID={0}&PWD={1}",
secret.Uid,
secret.Password);
return queryString;
}
public HttpRequestMessage CreateRequest(ISecret secret)
{
var urib = new UriBuilder
{
Scheme = "https",
Host = "biz2.every8d.com.tw",
Path = "evta/API21/HTTP/getCredit.ashx",
Query = ComposeQueryString(secret)
};
return new HttpRequestMessage(HttpMethod.Get, urib.Uri);
}
public async Task<Result> ParseResponse(HttpResponseMessage responseMessage)
{
var response = await responseMessage.Content.ReadAsStringAsync();
return new Result(decimal.Parse(response));
}
}
}
}
namespace Abstractions
{
public interface IRequest<TResult> where TResult : IRequestResult
{
HttpRequestMessage CreateRequest(ISecret secret);
Task<TResult> ParseResponse(HttpResponseMessage responseMessage);
}
public interface ISecret
{
string Uid { get; }
string Password { get; }
}
public interface IDataStore
{
ISecret GetSecret();
}
public interface IRequestResult
{
}
public interface IRequestFactory { }
public interface IShortMessageService
{
IRequestFactory Requests { get; }
Task<TResult> Send<TResult>(IRequest<TResult> request) where TResult : IRequestResult;
}
}
namespace Utilities
{
public static class StringUtils
{
public static string UrlEncode(string str)
{
if (string.IsNullOrEmpty(str))
{
return string.Empty;
}
return HttpUtility.UrlEncode(str, Encoding.UTF8);
}
}
public sealed class InMemoryDataStore : IDataStore
{
private sealed class Secret : ISecret
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string Uid { get; set; }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string Password { get; set; }
}
private readonly Secret _secret;
private InMemoryDataStore(Secret secret)
{
_secret = secret;
}
public static IDataStore Create(string uid, string password)
{
return new InMemoryDataStore(new Secret
{
Uid = uid,
Password = password
});
}
ISecret IDataStore.GetSecret()
{
return _secret;
}
}
}
public class ShortMessageService : IShortMessageService
{
private sealed class RequestFactory : IRequestFactory
{
internal RequestFactory()
{
}
}
private static readonly Lazy<HttpClient> s_sharedClient = new Lazy<HttpClient>(delegate
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
return new HttpClient();
});
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly IDataStore _secretStore;
private readonly HttpClient _httpClient;
public ShortMessageService(HttpClient httpClient, IDataStore secretStore)
{
ArgumentNullException.ThrowIfNull(secretStore);
_secretStore = secretStore;
_httpClient = httpClient ?? s_sharedClient.Value;
}
public ShortMessageService(IDataStore secretStore)
: this(s_sharedClient.Value, secretStore)
{
}
public virtual IRequestFactory Requests { get; } = new RequestFactory();
public virtual async Task<TResult> Send<TResult>(IRequest<TResult> request)
where TResult : IRequestResult
{
var secret = _secretStore.GetSecret();
var responseMessage = await _httpClient
.SendAsync(request.CreateRequest(secret))
.ConfigureAwait(false)
;
var result = await request
.ParseResponse(responseMessage)
.ConfigureAwait(false);
return result;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment