Skip to content

Instantly share code, notes, and snippets.

@Thorium
Created July 2, 2024 09:46
Show Gist options
  • Save Thorium/2c00efd7b6160981f2495156334ac03c to your computer and use it in GitHub Desktop.
Save Thorium/2c00efd7b6160981f2495156334ac03c to your computer and use it in GitHub Desktop.
Send text messages with F# (FSharp)
[<RequireQualifiedAccess>]
module TextMagic
//https://www.textmagic.com/docs/api/send-sms/
// #r "System.Text.Json"
// #r "FSharp.Data"
open System
open System.IO
open System.Net
open System.Text.Json
let private smsClientUsername = "username"
let private smsClientApiKey = "key"
type SendMessageResponse = FSharp.Data.JsonProvider<"""[
{
"id": "49576009",
"href": "/api/v2/messages/49576009",
"type": "session",
"sessionId": "34436600",
"bulkId": "357122",
"messageId": "49576009",
"scheduleId": "313"
},
{
"id": "49576009",
"href": "/api/v2/messages/49576009",
"type": "session"
}
]""", SampleIsList = true>
let makePostRequestWithHeaders (reqType: PostRequestTypes) (url : string) (requestBody : string) (headers) =
let req = WebRequest.CreateHttp url
headers |> Seq.iter(fun (h:string,k:string) ->
if not (String.IsNullOrEmpty h) then
if h.ToLower() = "user-agent" then
req.UserAgent <- k
else
req.Headers.Add(h,k)
)
req.CookieContainer <- new CookieContainer()
req.Method <- "POST"
let timeout = 8000
req.Timeout <- timeout
req.ProtocolVersion <- HttpVersion.Version10
let postBytes = requestBody |> System.Text.Encoding.ASCII.GetBytes
req.ContentLength <- postBytes.LongLength
req.ContentType <- "application/json"
let asynccall =
async {
let! res =
async{
let! reqStream = req.GetRequestStreamAsync() |> Async.AwaitTask
do! reqStream.WriteAsync(postBytes, 0, postBytes.Length) |> Async.AwaitIAsyncResult |> Async.Ignore
reqStream.Close()
let! res =
async { // Async methods are not using req.Timeout
let! child = Async.StartChild(req.AsyncGetResponse(), timeout)
return! child
}
use stream = res.GetResponseStream()
use reader = new StreamReader(stream)
let! rdata = reader.ReadToEndAsync() |> Async.AwaitTask
return rdata
} |> Async.Catch
match res with
| Choice1Of2 x -> return x, None
| Choice2Of2 e ->
match e with
| :? WebException as wex when not(isNull wex.Response) ->
use stream = wex.Response.GetResponseStream()
use reader = new StreamReader(stream)
let err = reader.ReadToEnd()
return err, Some e
| :? TimeoutException as e ->
return failwith(e.ToString())
| _ ->
return failwith(e.ToString())
}
asynccall
let sendMessage (number:string, text:string) =
task {
if number.Contains "," || number.Length > 20 then
return Result.Error "No batch sending supported."
else
// The specs tells to URL-encode, but in real life if you do it, it breaks the text.
// So the specs has to be talking about something else than ApplicationJson,
// but changing the type also breaks the system.
//``text`` = HttpUtility.UrlEncode text
let json = JsonSerializer.Serialize({|
``text`` = text
``phones`` = number
|})
match! makePostRequestWithHeaders ApplicationJson ("https://rest.textmagic.com/api/v2/messages") json [ "X-TM-Key", smsClientApiKey; "X-TM-Username", smsClientUsername ] with
| responseJson, None ->
let response = SendMessageResponse.Parse responseJson
return Result.Ok response
| _ , Some error ->
return Result.Error (sprintf "Could not send SMS: %A" error)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment