Skip to content

Instantly share code, notes, and snippets.

@StevenXL
Last active September 17, 2018 00:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save StevenXL/8605c206196db6475bfe4d356e8441fa to your computer and use it in GitHub Desktop.
Save StevenXL/8605c206196db6475bfe4d356e8441fa to your computer and use it in GitHub Desktop.
Twilio Grab
-- stack --resolver lts-12.5 script --package mtl --package aeson --package http-conduit --package bytestring --package cassava --package MissingH
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric #-}
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (ReaderT, ask, runReaderT)
import Control.Monad.State (StateT, evalStateT, get, put)
import Data.Aeson (FromJSON)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
import qualified Data.ByteString.Lazy as BL
import Data.Csv (DefaultOrdered, ToNamedRecord)
import qualified Data.Csv as Csv
import Data.String.Utils (replace)
import GHC.Generics
import Network.HTTP.Simple (Response, getResponseBody, httpJSON,
parseRequest_, setRequestBasicAuth)
import System.Environment (getEnv)
data Env = Env { envUserName :: B.ByteString , envPassword :: B.ByteString }
envSid :: Env -> B.ByteString
envSid = envUserName
data AppState = AppState { nextUrl :: String }
data Message = Message { sid :: String
, date_created :: String
, date_updated :: String
, date_sent :: String
, account_sid :: String
, to :: String
, from :: String
, message_service_sid :: Maybe String
, body :: String
, status :: String
, num_segments :: String
, num_media :: String
, direction :: String
, api_version :: String
, price :: String
, price_unit :: String
, error_code :: Maybe Integer
, error_message :: Maybe String
} deriving (Generic, Show)
instance FromJSON Message
instance ToNamedRecord Message
instance DefaultOrdered Message
data TwilioBody = TwilioBody { next_page_uri :: Maybe String
, messages :: [Message]
} deriving Generic
instance FromJSON TwilioBody
baseUrl :: String
baseUrl = "https://api.twilio.com"
createFirstUrl :: String -> String
createFirstUrl sid = baseUrl <> "/2010-04-01/Accounts" <> "/" <> sid <> "/Messages.json"
main :: IO ()
main = do
sid <- getEnv "TWILIO_SID"
password <- getEnv "TWILIO_PASSWORD"
let initialUrl = createFirstUrl sid
let env = Env (C.pack sid) (C.pack password)
messages <- evalStateT (runReaderT getMessages env) (AppState initialUrl)
saveMessages messages
getMessages :: ReaderT Env (StateT AppState IO) [Message]
getMessages = do
sid <- fmap envSid ask
url <- fmap nextUrl get
_ <- liftIO $ putStrLn $ "Getting MSGs for " <> scramble sid url
userName <- fmap envUserName ask
password <- fmap envPassword ask
response <- httpJSON $ setRequestBasicAuth userName password (parseRequest_ url)
case parse response of
(Nothing, msgs) -> pure msgs
(Just nextPageUri, msgs) -> do
_ <- put $ AppState { nextUrl = constructUrl nextPageUri}
newMsgs <- getMessages
return $ msgs ++ newMsgs
parse :: Response TwilioBody -> (Maybe String, [Message])
parse response = let twilioBody = getResponseBody response
in (next_page_uri twilioBody, messages twilioBody)
saveMessages :: [Message] -> IO ()
saveMessages = BL.writeFile "sms.csv" . Csv.encodeDefaultOrderedByName
constructUrl :: String -> String
constructUrl = (baseUrl <>)
scramble :: B.ByteString -> String -> String
scramble sid url = replace (C.unpack sid) "<REDACTED>" url
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment