├── twilio-haskell-move-to-stack.zip ├── Setup.hs ├── twilio-haskell-move-to-stack ├── Setup.hs ├── .gitignore ├── AUTHORS ├── src │ ├── Twilio.hs │ ├── test.hs │ ├── Twilio │ │ ├── Types │ │ │ ├── PriceUnit.hs │ │ │ ├── Capability.hs │ │ │ ├── AddressRequirement.hs │ │ │ ├── AuthToken.hs │ │ │ ├── ISOCountryCode.hs │ │ │ ├── Issue.hs │ │ │ ├── List.hs │ │ │ └── SID.hs │ │ ├── ShortCodes.hs │ │ ├── Conferences.hs │ │ ├── Addresses.hs │ │ ├── Queue │ │ │ ├── Members.hs │ │ │ └── Member.hs │ │ ├── Recordings.hs │ │ ├── ConnectApps.hs │ │ ├── Message │ │ │ ├── MediaList.hs │ │ │ ├── Feedback.hs │ │ │ └── Media.hs │ │ ├── UsageRecords.hs │ │ ├── UsageTriggers.hs │ │ ├── Transcriptions.hs │ │ ├── Conference │ │ │ ├── Participants.hs │ │ │ └── Participant.hs │ │ ├── IncomingPhoneNumbers.hs │ │ ├── OutgoingCallerIDs.hs │ │ ├── AuthorizedConnectApps.hs │ │ ├── APIKey.hs │ │ ├── AvailablePhoneNumbers.hs │ │ ├── UsageRecord.hs │ │ ├── APIKeys.hs │ │ ├── Applications.hs │ │ ├── AvailablePhoneNumber.hs │ │ ├── Address.hs │ │ ├── Messages.hs │ │ ├── OutgoingCallerID.hs │ │ ├── Recording.hs │ │ ├── Queues.hs │ │ ├── ShortCode.hs │ │ ├── Conference.hs │ │ ├── Queue.hs │ │ ├── Internal │ │ │ ├── Parser.hs │ │ │ ├── Request.hs │ │ │ └── Resource.hs │ │ ├── AuthorizedConnectApp.hs │ │ ├── UsageTrigger.hs │ │ ├── ConnectApp.hs │ │ ├── Tokens.hs │ │ ├── Calls │ │ │ └── FeedbackSummary.hs │ │ ├── Call │ │ │ └── Feedback.hs │ │ ├── Transcription.hs │ │ ├── IncomingPhoneNumber.hs │ │ ├── Accounts.hs │ │ ├── Calls.hs │ │ ├── Account.hs │ │ ├── Types.hs │ │ ├── Message.hs │ │ ├── Application.hs │ │ └── Call.hs │ └── Control │ │ └── Monad │ │ └── Twilio.hs ├── script │ └── publish_haddock.sh ├── LICENSE ├── README.md ├── stack.yaml ├── .travis.yml ├── twilio.cabal └── test │ └── Test.hs ├── .gitignore ├── redditbot.cabal ├── LICENSE ├── stack.yaml ├── Main.hs └── README.MD /twilio-haskell-move-to-stack.zip: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/.gitignore: -------------------------------------------------------------------------------- 1 | .cabal-sandbox 2 | cabal.sandbox.config 3 | dist 4 | .stack-work 5 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/AUTHORS: -------------------------------------------------------------------------------- 1 | Mark Andrus Roberts 2 | Connie Chen 3 | Ian Grant Jeffries 4 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio.hs: -------------------------------------------------------------------------------- 1 | module Twilio 2 | ( 3 | module Control.Monad.Twilio 4 | , module Twilio.Types 5 | ) where 6 | 7 | import Control.Monad.Twilio 8 | import Twilio.Types 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-* 3 | cabal-dev 4 | *.o 5 | *.hi 6 | *.chi 7 | *.chs.h 8 | *.dyn_o 9 | *.dyn_hi 10 | .hpc 11 | .hsenv 12 | .cabal-sandbox/ 13 | cabal.sandbox.config 14 | *.prof 15 | *.aux 16 | *.hp 17 | *.eventlog 18 | .stack-work/ 19 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/test.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module Test where 4 | 5 | import Control.Monad 6 | import Data.Aeson 7 | import Data.Text 8 | 9 | newtype Test = Test { getTest :: Maybe Text } 10 | deriving Show 11 | 12 | instance FromJSON Test where 13 | parseJSON = withObject "Test" (\v -> Test <$> v .:? "test") 14 | 15 | test1 :: Maybe Test 16 | test1 = decode "{\"test\":\"test\"}" 17 | 18 | test2 :: Maybe Test 19 | test2 = decode "{\"test\":null}" 20 | 21 | test3 :: Maybe Test 22 | test3 = decode "{}" 23 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/PriceUnit.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE OverloadedStrings #-} 2 | 3 | module Twilio.Types.PriceUnit where 4 | 5 | import Control.Monad 6 | import Data.Aeson 7 | import Data.Text (Text) 8 | import qualified Data.Text as T 9 | 10 | data PriceUnit 11 | = USD 12 | | EUR 13 | | JPY 14 | | OtherPriceUnit !Text 15 | deriving Eq 16 | 17 | instance Show PriceUnit where 18 | show USD = "USD" 19 | show EUR = "EUR" 20 | show JPY = "JPY" 21 | show (OtherPriceUnit pu) = T.unpack pu 22 | 23 | instance FromJSON PriceUnit where 24 | parseJSON (String "USD") = return USD 25 | parseJSON (String "EUR") = return EUR 26 | parseJSON (String "JPY") = return JPY 27 | parseJSON (String t) = return $ OtherPriceUnit t 28 | parseJSON _ = mzero 29 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/Capability.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE TypeSynonymInstances #-} 3 | 4 | module Twilio.Types.Capability where 5 | 6 | import Control.Monad 7 | import Data.Aeson 8 | import qualified Data.HashMap.Strict as HashMap 9 | import Data.Set (Set) 10 | import qualified Data.Set as Set 11 | import qualified Data.Text as T 12 | 13 | type Capabilities = Set Capability 14 | 15 | data Capability 16 | = Voice 17 | | SMS 18 | | MMS 19 | deriving (Bounded, Enum, Eq, Ord, Read, Show) 20 | 21 | instance {-# OVERLAPPING #-} FromJSON Capabilities where 22 | parseJSON (Object map) 23 | = let map' = fmap (\value -> case value of 24 | Bool bool -> bool 25 | _ -> False) map 26 | in return $ foldr (\capability set -> 27 | if HashMap.lookupDefault False (T.pack $ show capability) map' 28 | then Set.insert capability set 29 | else set 30 | ) Set.empty [Voice, SMS, MMS] 31 | parseJSON _ = mzero 32 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/AddressRequirement.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE LambdaCase #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Types.AddressRequirement where 5 | 6 | import Control.Monad 7 | import Data.Aeson 8 | 9 | data AddressRequirement 10 | = None 11 | | Any 12 | | Local 13 | | Foreign 14 | deriving (Bounded, Enum, Eq, Ord) 15 | 16 | instance Read AddressRequirement where 17 | readsPrec _ = \case 18 | "none" -> return (None, "none") 19 | "any" -> return (None, "any") 20 | "local" -> return (None, "local") 21 | "foreign" -> return (None, "foregin") 22 | _ -> mzero 23 | 24 | instance Show AddressRequirement where 25 | show None = "none" 26 | show Any = "any" 27 | show Local = "local" 28 | show Foreign = "foreign" 29 | 30 | instance FromJSON AddressRequirement where 31 | parseJSON (String "none") = return None 32 | parseJSON (String "any") = return Any 33 | parseJSON (String "local") = return Local 34 | parseJSON (String "foreign") = return Foreign 35 | parseJSON _ = mzero 36 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/ShortCodes.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.ShortCodes 5 | ( -- * Resource 6 | ShortCodes(..) 7 | , Twilio.ShortCodes.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | 14 | import Control.Monad.Twilio 15 | import Twilio.ShortCode 16 | import Twilio.Internal.Request 17 | import Twilio.Internal.Resource as Resource 18 | import Twilio.Types 19 | 20 | {- Resource -} 21 | 22 | data ShortCodes = ShortCodes 23 | { shortCodeList :: [ShortCode] 24 | } deriving (Show, Eq) 25 | 26 | instance List ShortCodes ShortCode where 27 | getListWrapper = wrap (const ShortCodes) 28 | getList = shortCodeList 29 | getPlural = Const "short_codes" 30 | 31 | instance FromJSON ShortCodes where 32 | parseJSON = parseJSONToList 33 | 34 | instance Get0 ShortCodes where 35 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 36 | "/SMS/ShortCodes.json" 37 | 38 | -- | Get 'ShortCodes'. 39 | get :: MonadThrow m => TwilioT m ShortCodes 40 | get = Resource.get 41 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Conferences.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | 6 | module Twilio.Conferences 7 | ( -- * Resource 8 | Conferences(..) 9 | ) where 10 | 11 | import Control.Applicative 12 | import Data.Aeson 13 | import Data.Data 14 | import Data.Maybe 15 | import GHC.Generics 16 | 17 | import Twilio.Conference 18 | import Twilio.Internal.Request 19 | import Twilio.Internal.Resource 20 | import Twilio.Types 21 | 22 | data Conferences = Conferences 23 | { conferencesPagingInformation :: PagingInformation 24 | , conferenceList :: [Conference] 25 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 26 | 27 | instance List Conferences Conference where 28 | getListWrapper = wrap (Conferences . fromJust) 29 | getList = conferenceList 30 | getPlural = Const "conferences" 31 | 32 | instance FromJSON Conferences where 33 | parseJSON = parseJSONToList 34 | 35 | instance Get0 Conferences where 36 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 37 | "/Conferences.json" 38 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Addresses.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Addresses 5 | ( -- * Resource 6 | Addresses(..) 7 | , Twilio.Addresses.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.Address 17 | import Twilio.Internal.Request 18 | import Twilio.Internal.Resource as Resource 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | 23 | data Addresses = Addresses 24 | { addressesPagingInformation :: PagingInformation 25 | , addressList :: [Address] 26 | } deriving (Show, Eq) 27 | 28 | instance List Addresses Address where 29 | getListWrapper = wrap (Addresses . fromJust) 30 | getList = addressList 31 | getPlural = Const "addresses" 32 | 33 | instance FromJSON Addresses where 34 | parseJSON = parseJSONToList 35 | 36 | instance Get0 Addresses where 37 | get0 = request parseJSONFromResponse =<< makeTwilioRequest "/Addresses.json" 38 | 39 | get :: MonadThrow m => TwilioT m Addresses 40 | get = Resource.get 41 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/script/publish_haddock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Description: This script is run by Travis CI on successful builds in order to 3 | # update the repo's GitHub pages with Haddock documentation. 4 | set -e 5 | 6 | user='markandrus' 7 | repo='twilio-haskell' 8 | package='twilio' 9 | 10 | if [[ "${TRAVIS_REPO_SLUG}" == "${user}/${repo}" \ 11 | && "${TRAVIS_PULL_REQUEST}" == 'false' \ 12 | && "${TRAVIS_BRANCH}" == 'master' ]] 13 | then 14 | 15 | echo "Generating Haddock documentation..." 16 | cabal haddock 17 | cp -R dist/doc/html/${package} ${HOME}/doc 18 | 19 | echo "Cloning repo..." 20 | cd ${HOME} 21 | git config --global user.email 'travis@travis-ci.org' 22 | git config --global user.name 'travis-ci' 23 | git clone --quiet --branch=gh-pages \ 24 | https://${HADDOCK_KEY}@github.com/${user}/${repo} gh-pages >/dev/null 25 | 26 | echo "Updating repo..." 27 | cd gh-pages 28 | rm -rf * 29 | mv ../doc/* . 30 | git add -f . 31 | git commit -m "Updating Haddock documentation (${TRAVIS_BUILD_NUMBER})" 32 | git push -fq origin gh-pages >/dev/null 33 | 34 | echo "Published Haddock documentation." 35 | 36 | fi 37 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Queue/Members.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Queue.Members 8 | ( -- * Resource 9 | Members(..) 10 | ) where 11 | 12 | import Control.Applicative 13 | import Data.Aeson 14 | import Data.Data 15 | import Data.Maybe 16 | import Data.Monoid 17 | import GHC.Generics 18 | 19 | import Twilio.Queue.Member 20 | import Twilio.Internal.Request 21 | import Twilio.Internal.Resource 22 | import Twilio.Types 23 | 24 | data Members = Members 25 | { membersPagingInformation :: PagingInformation 26 | , memberList :: [Member] 27 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 28 | 29 | instance List Members Member where 30 | getListWrapper = wrap (Members . fromJust) 31 | getList = memberList 32 | getPlural = Const "queue_members" 33 | 34 | instance FromJSON Members where 35 | parseJSON = parseJSONToList 36 | 37 | instance Get1 QueueSID Members where 38 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 39 | ("/Queues/" <> sid <> ".json") 40 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Recordings.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Recordings 5 | ( -- * Resource 6 | Recordings(..) 7 | , Twilio.Recordings.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.Internal.Request 17 | import Twilio.Internal.Resource as Resource 18 | import Twilio.Recording 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | data Recordings = Recordings 23 | { recordingsPagingInformation :: PagingInformation 24 | , recordingList :: [Recording] 25 | } deriving (Show, Eq) 26 | 27 | instance List Recordings Recording where 28 | getListWrapper = wrap (Recordings . fromJust) 29 | getList = recordingList 30 | getPlural = Const "recordings" 31 | 32 | instance FromJSON Recordings where 33 | parseJSON = parseJSONToList 34 | 35 | instance Get0 Recordings where 36 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 37 | "/Recordings.json" 38 | 39 | -- | Get 'Recordings'. 40 | get :: MonadThrow m => TwilioT m Recordings 41 | get = Resource.get 42 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/AuthToken.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE PatternGuards #-} 2 | 3 | module Twilio.Types.AuthToken 4 | ( -- * Authentication Token 5 | AuthToken 6 | , getAuthToken 7 | , parseAuthToken 8 | ) where 9 | 10 | import Control.Monad 11 | import Data.Aeson 12 | import Data.Char 13 | import Data.Text (Text) 14 | import qualified Data.Text as T 15 | 16 | -- | Your authentication token is used to make authenticated REST API requests 17 | -- to your Twilio account. 18 | newtype AuthToken = AuthToken { getAuthToken' :: Text } 19 | deriving (Show, Eq, Ord) 20 | 21 | -- | Get the 'Text' representation of an 'AuthToken'. 22 | getAuthToken :: AuthToken -> Text 23 | getAuthToken = getAuthToken' 24 | 25 | -- | Parse a 'Text' to an 'AuthToken'. 26 | parseAuthToken :: Text -> Maybe AuthToken 27 | parseAuthToken = parseAuthToken' 28 | 29 | parseAuthToken' :: MonadPlus m => Text -> m AuthToken 30 | parseAuthToken' token 31 | | T.length token == 32 32 | , T.all (\x -> isLower x || isNumber x) token 33 | = return $ AuthToken token 34 | | otherwise 35 | = mzero 36 | 37 | instance FromJSON AuthToken where 38 | parseJSON (String v) = parseAuthToken' v 39 | parseJSON _ = mzero 40 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/ConnectApps.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.ConnectApps 5 | ( -- * Resource 6 | ConnectApps(..) 7 | , Twilio.ConnectApps.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.ConnectApp 17 | import Twilio.Internal.Request 18 | import Twilio.Internal.Resource as Resource 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | 23 | data ConnectApps = ConnectApps 24 | { connectAppsPagingInformation :: PagingInformation 25 | , connectAppList :: [ConnectApp] 26 | } deriving (Show, Eq) 27 | 28 | instance List ConnectApps ConnectApp where 29 | getListWrapper = wrap (ConnectApps . fromJust) 30 | getList = connectAppList 31 | getPlural = Const "connect_apps" 32 | 33 | instance FromJSON ConnectApps where 34 | parseJSON = parseJSONToList 35 | 36 | instance Get0 ConnectApps where 37 | get0 = request parseJSONFromResponse =<< makeTwilioRequest "/ConnectApps.json" 38 | 39 | -- | Get 'ConnectApps'. 40 | get :: MonadThrow m => TwilioT m ConnectApps 41 | get = Resource.get 42 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Message/MediaList.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Message.MediaList 8 | ( -- * Resource 9 | MediaList(..) 10 | -- * Types 11 | ) where 12 | 13 | import Control.Applicative 14 | import Data.Aeson 15 | import Data.Data 16 | import Data.Maybe 17 | import Data.Monoid 18 | import GHC.Generics 19 | 20 | import Twilio.Internal.Request 21 | import Twilio.Internal.Resource as Resource 22 | import Twilio.Message.Media 23 | import Twilio.Types 24 | 25 | data MediaList = MediaList 26 | { mediaPagingInformation :: PagingInformation 27 | , mediaList :: [Media] 28 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 29 | 30 | instance List MediaList Media where 31 | getListWrapper = wrap (MediaList . fromJust) 32 | getList = mediaList 33 | getPlural = Const "media_list" 34 | 35 | instance FromJSON MediaList where 36 | parseJSON = parseJSONToList 37 | 38 | instance Get1 MessageSID MediaList where 39 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 40 | ("/Messages/" <> sid <> "/Media.json") 41 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/UsageRecords.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.UsageRecords 5 | ( -- * Resource 6 | UsageRecords(..) 7 | , Twilio.UsageRecords.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.Internal.Request 17 | import Twilio.Internal.Resource as Resource 18 | import Twilio.Types 19 | import Twilio.UsageRecord 20 | 21 | {- Resource -} 22 | 23 | data UsageRecords = UsageRecords 24 | { usageRecordsPagingInformation :: PagingInformation 25 | , usageRecordList :: [UsageRecord] 26 | } deriving (Show, Eq) 27 | 28 | instance List UsageRecords UsageRecord where 29 | getListWrapper = wrap (UsageRecords . fromJust) 30 | getList = usageRecordList 31 | getPlural = Const "usage_records" 32 | 33 | instance FromJSON UsageRecords where 34 | parseJSON = parseJSONToList 35 | 36 | instance Get0 UsageRecords where 37 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 38 | "/Usage/Records.json" 39 | 40 | -- | Get 'UsageRecords'. 41 | get :: MonadThrow m => TwilioT m UsageRecords 42 | get = Resource.get 43 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/UsageTriggers.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.UsageTriggers 5 | ( -- * Resource 6 | UsageTriggers(..) 7 | , Twilio.UsageTriggers.get 8 | ) where 9 | 10 | 11 | import Control.Applicative 12 | import Control.Monad.Catch 13 | import Data.Aeson 14 | import Data.Maybe 15 | 16 | import Control.Monad.Twilio 17 | import Twilio.Internal.Request 18 | import Twilio.Internal.Resource as Resource 19 | import Twilio.Types 20 | import Twilio.UsageTrigger 21 | 22 | {- Resource -} 23 | 24 | data UsageTriggers = UsageTriggers 25 | { usageTriggersPagingInformation :: PagingInformation 26 | , usageTriggerList :: [UsageTrigger] 27 | } deriving (Show, Eq) 28 | 29 | instance List UsageTriggers UsageTrigger where 30 | getListWrapper = wrap (UsageTriggers . fromJust) 31 | getList = usageTriggerList 32 | getPlural = Const "usage_triggers" 33 | 34 | instance FromJSON UsageTriggers where 35 | parseJSON = parseJSONToList 36 | 37 | instance Get0 UsageTriggers where 38 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 39 | "/Usage/Triggers.json" 40 | 41 | -- | Get 'UsageTriggers'. 42 | get :: MonadThrow m => TwilioT m UsageTriggers 43 | get = Resource.get 44 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Transcriptions.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Transcriptions 5 | ( -- * Resource 6 | Transcriptions(..) 7 | , Twilio.Transcriptions.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.Internal.Request 17 | import Twilio.Internal.Resource as Resource 18 | import Twilio.Transcription 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | data Transcriptions = Transcriptions 23 | { transcriptionsPagingInformation :: PagingInformation 24 | , transcriptionList :: [Transcription] 25 | } deriving (Show, Eq) 26 | 27 | instance List Transcriptions Transcription where 28 | getListWrapper = wrap (Transcriptions . fromJust) 29 | getList = transcriptionList 30 | getPlural = Const "transcriptions" 31 | 32 | instance FromJSON Transcriptions where 33 | parseJSON = parseJSONToList 34 | 35 | instance Get0 Transcriptions where 36 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 37 | "/Transcriptions.json" 38 | 39 | -- | Get 'Transcriptions'. 40 | get :: MonadThrow m => TwilioT m Transcriptions 41 | get = Resource.get 42 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Conference/Participants.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Conference.Participants 8 | ( -- * Resource 9 | Participants(..) 10 | ) where 11 | 12 | import Control.Applicative 13 | import Data.Aeson 14 | import Data.Data 15 | import Data.Maybe 16 | import Data.Monoid 17 | import GHC.Generics 18 | 19 | import Twilio.Conference.Participant 20 | import Twilio.Internal.Request 21 | import Twilio.Internal.Resource 22 | import Twilio.Types 23 | 24 | data Participants = Participants 25 | { participantsPagingInformation :: PagingInformation 26 | , participantList :: [Participant] 27 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 28 | 29 | instance List Participants Participant where 30 | getListWrapper = wrap (Participants . fromJust) 31 | getList = participantList 32 | getPlural = Const "participants" 33 | 34 | instance FromJSON Participants where 35 | parseJSON = parseJSONToList 36 | 37 | instance Get1 ConferenceSID Participants where 38 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 39 | ("/Conferences/" <> sid <> ".json") 40 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/IncomingPhoneNumbers.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.IncomingPhoneNumbers 5 | ( -- * Resource 6 | IncomingPhoneNumbers(..) 7 | , Twilio.IncomingPhoneNumbers.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | 14 | import Control.Monad.Twilio 15 | import Twilio.IncomingPhoneNumber 16 | import Twilio.Internal.Request 17 | import Twilio.Internal.Resource as Resource 18 | import Twilio.Types 19 | 20 | {- Resource -} 21 | 22 | data IncomingPhoneNumbers = IncomingPhoneNumbers 23 | { incomingPhoneNumberList :: [IncomingPhoneNumber] 24 | } deriving (Show, Eq) 25 | 26 | instance List IncomingPhoneNumbers IncomingPhoneNumber where 27 | getListWrapper = wrap (const IncomingPhoneNumbers) 28 | getList = incomingPhoneNumberList 29 | getPlural = Const "incoming_phone_numbers" 30 | 31 | instance FromJSON IncomingPhoneNumbers where 32 | parseJSON = parseJSONToList 33 | 34 | instance Get0 IncomingPhoneNumbers where 35 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 36 | "/IncomingPhoneNumbers.json" 37 | 38 | -- | Get 'IncomingPhoneNumbers' for a particular country. 39 | get :: MonadThrow m => TwilioT m IncomingPhoneNumbers 40 | get = Resource.get 41 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/OutgoingCallerIDs.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.OutgoingCallerIDs 5 | ( -- * Resource 6 | OutgoingCallerIDs(..) 7 | , Twilio.OutgoingCallerIDs.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.Internal.Request 17 | import Twilio.Internal.Resource as Resource 18 | import Twilio.OutgoingCallerID 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | 23 | data OutgoingCallerIDs = OutgoingCallerIDs 24 | { outgoingCallerIDsPagingInformation :: !PagingInformation 25 | , outgoingCallerIDList :: [OutgoingCallerID] 26 | } deriving (Show, Eq) 27 | 28 | instance List OutgoingCallerIDs OutgoingCallerID where 29 | getListWrapper = wrap (OutgoingCallerIDs . fromJust) 30 | getList = outgoingCallerIDList 31 | getPlural = Const "outgoing_caller_ids" 32 | 33 | instance FromJSON OutgoingCallerIDs where 34 | parseJSON = parseJSONToList 35 | 36 | instance Get0 OutgoingCallerIDs where 37 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 38 | "/OutgoingCallerIds.json" 39 | 40 | -- | Get 'OutgoingCallerIDs'. 41 | get :: MonadThrow m => TwilioT m OutgoingCallerIDs 42 | get = Resource.get 43 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Message/Feedback.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | 6 | module Twilio.Message.Feedback 7 | ( -- * Resource 8 | Feedback(..) 9 | -- * Types 10 | , Outcome(..) 11 | ) where 12 | 13 | import Control.Applicative 14 | import Control.Monad 15 | import Control.Monad.Catch 16 | import Data.Aeson 17 | import Data.Data 18 | import Data.Monoid 19 | import Data.Scientific 20 | import Data.Time.Clock 21 | import GHC.Generics 22 | 23 | import Control.Monad.Twilio 24 | import Twilio.Internal.Parser 25 | import Twilio.Internal.Request 26 | import Twilio.Internal.Resource as Resource 27 | import Twilio.Types 28 | 29 | {- Resource -} 30 | 31 | data Feedback = Feedback 32 | { outcome :: !Outcome 33 | } deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 34 | 35 | data Outcome 36 | = Unconfirmed 37 | | Confirmed 38 | deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable) 39 | 40 | instance ToJSON Outcome where 41 | toJSON Unconfirmed = String "unconfirmed" 42 | toJSON Confirmed = String "confirmed" 43 | 44 | instance FromJSON Outcome where 45 | parseJSON (String "unconfirmed") = pure Unconfirmed 46 | parseJSON (String "confirmed") = pure Confirmed 47 | parseJSON _ = mzero 48 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/AuthorizedConnectApps.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.AuthorizedConnectApps 5 | ( -- * Resource 6 | AuthorizedConnectApps(..) 7 | , Twilio.AuthorizedConnectApps.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.AuthorizedConnectApp 17 | import Twilio.Internal.Request 18 | import Twilio.Internal.Resource as Resource 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | 23 | data AuthorizedConnectApps = AuthorizedConnectApps 24 | { authorizedConnectAppsPagingInformation :: PagingInformation 25 | , authorizedConnectAppList :: [AuthorizedConnectApp] 26 | } deriving (Show, Eq) 27 | 28 | instance List AuthorizedConnectApps AuthorizedConnectApp where 29 | getListWrapper = wrap (AuthorizedConnectApps . fromJust) 30 | getList = authorizedConnectAppList 31 | getPlural = Const "authorized_connect_apps" 32 | 33 | instance FromJSON AuthorizedConnectApps where 34 | parseJSON = parseJSONToList 35 | 36 | instance Get0 AuthorizedConnectApps where 37 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 38 | "/AuthorizedConnectApps.json" 39 | 40 | -- | Get 'AuthorizedConnectApps'. 41 | get :: MonadThrow m => TwilioT m AuthorizedConnectApps 42 | get = Resource.get 43 | -------------------------------------------------------------------------------- /redditbot.cabal: -------------------------------------------------------------------------------- 1 | -- Initial redditbot.cabal generated by cabal init. For further 2 | -- documentation, see http://haskell.org/cabal/users-guide/ 3 | 4 | name: redditbot 5 | version: 0.1.0.0 6 | -- synopsis: 7 | -- description: 8 | license: BSD3 9 | license-file: LICENSE 10 | author: Elizabeth Siegle 11 | maintainer: lsiegle@twilio.com 12 | -- copyright: 13 | -- category: 14 | build-type: Simple 15 | extra-source-files: ChangeLog.md 16 | cabal-version: >=1.10 17 | 18 | executable redditbot 19 | main-is: Main.hs 20 | build-depends: base 21 | , HTTP 22 | , aeson 23 | , http-client 24 | , http-client-tls 25 | , http-types 26 | , linklater 27 | , semigroupoids 28 | , servant-client 29 | , text 30 | , tls 31 | , transformers 32 | , wai 33 | , warp 34 | , wreq 35 | , ghc-prim 36 | , bytestring 37 | , lens 38 | , twilio 39 | , reddit 40 | , containers 41 | -- hs-source-dirs: 42 | default-language: Haskell2010 -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/APIKey.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | {-#LANGUAGE ViewPatterns #-} 5 | 6 | module Twilio.APIKey 7 | ( -- * Resource 8 | APIKey(..) 9 | , APIKeySID 10 | , Twilio.APIKey.get 11 | ) where 12 | 13 | import Control.Applicative 14 | import Control.Monad 15 | import Control.Monad.Catch 16 | import Data.Aeson 17 | import Data.Monoid 18 | import Data.Text (Text) 19 | import Data.Time.Clock 20 | import Network.URI 21 | 22 | import Control.Monad.Twilio 23 | import Twilio.Internal.Parser 24 | import Twilio.Internal.Request 25 | import Twilio.Internal.Resource as Resource 26 | import Twilio.Types 27 | 28 | {- Resource -} 29 | 30 | data APIKey = APIKey 31 | { sid :: !APIKeySID 32 | , friendlyName :: !Text 33 | , secret :: !(Maybe Text) 34 | , dateCreated :: !UTCTime 35 | , dateUpdated :: !UTCTime 36 | } deriving (Show, Eq, Ord) 37 | 38 | instance FromJSON APIKey where 39 | parseJSON (Object v) = APIKey 40 | <$> v .: "sid" 41 | <*> v .: "friendly_name" 42 | <*> v .:? "secret" 43 | <*> (v .: "date_created" >>= parseDateTime) 44 | <*> (v .: "date_updated" >>= parseDateTime) 45 | parseJSON _ = mzero 46 | 47 | instance Get1 APIKeySID APIKey where 48 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 49 | ("/Keys/" <> sid <> ".json") 50 | 51 | get :: MonadThrow m => APIKeySID -> TwilioT m APIKey 52 | get = Resource.get 53 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/AvailablePhoneNumbers.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.AvailablePhoneNumbers 6 | ( -- * Resource 7 | AvailablePhoneNumbers(..) 8 | , Twilio.AvailablePhoneNumbers.get 9 | ) where 10 | 11 | import Control.Applicative 12 | import Control.Monad.Catch 13 | import Data.Aeson 14 | import Data.Monoid 15 | import qualified Data.Text as T 16 | 17 | import Control.Monad.Twilio 18 | import Twilio.AvailablePhoneNumber 19 | import Twilio.Internal.Request 20 | import Twilio.Internal.Resource as Resource 21 | import Twilio.Types 22 | 23 | {- Resource -} 24 | 25 | data AvailablePhoneNumbers = AvailablePhoneNumbers 26 | { availablePhoneNumberList :: [AvailablePhoneNumber] 27 | } deriving (Show, Eq) 28 | 29 | instance List AvailablePhoneNumbers AvailablePhoneNumber where 30 | getListWrapper = wrap (const AvailablePhoneNumbers) 31 | getList = availablePhoneNumberList 32 | getPlural = Const "available_phone_numbers" 33 | 34 | instance FromJSON AvailablePhoneNumbers where 35 | parseJSON = parseJSONToList 36 | 37 | instance Get1 ISOCountryCode AvailablePhoneNumbers where 38 | get1 (show -> isoCountryCode) = request parseJSONFromResponse =<< makeTwilioRequest 39 | ("/AvailablePhoneNumbers/" <> T.pack isoCountryCode <> "/Local.json") 40 | 41 | -- | Get 'AvailablePhoneNumbers' for a particular country. 42 | get :: MonadThrow m => ISOCountryCode -> TwilioT m AvailablePhoneNumbers 43 | get = Resource.get 44 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/ISOCountryCode.hs: -------------------------------------------------------------------------------- 1 | module Twilio.Types.ISOCountryCode where 2 | 3 | import Control.Error.Safe 4 | import Control.Monad 5 | import Data.Aeson 6 | import qualified Data.Text as T 7 | 8 | -- | Country codes in ISO 3166-1 alpha-2 format supported by Twilio. 9 | data ISOCountryCode 10 | = AU -- ^ Australia 11 | | AT -- ^ Austria 12 | | BH -- ^ Bahrain 13 | | BE -- ^ Belgium 14 | | BR -- ^ Brazil 15 | | BG -- ^ Bulgaria 16 | | CA -- ^ Canada 17 | | CY -- ^ Cyprus 18 | | CZ -- ^ Czech Republic 19 | | DK -- ^ Denmark 20 | | DO -- ^ Dominican Republic 21 | | SV -- ^ El Salvador 22 | | EE -- ^ Estonia 23 | | FI -- ^ Finland 24 | | FR -- ^ France 25 | | GR -- ^ Greece 26 | | HK -- ^ Hong Kong 27 | | IE -- ^ Ireland 28 | | IL -- ^ Israel 29 | | IT -- ^ Italy 30 | | JP -- ^ Japan 31 | | LV -- ^ Latvia 32 | | LT -- ^ Lithuania 33 | | LU -- ^ Luxembourg 34 | | MT -- ^ Malta 35 | | MX -- ^ Mexico 36 | | NL -- ^ The Netherlands 37 | | NO -- ^ Norway 38 | | NZ -- ^ New Zealand 39 | | PE -- ^ Peru 40 | | PL -- ^ Poland 41 | | PT -- ^ Portugal 42 | | PR -- ^ Puerto Rico 43 | | RO -- ^ Romania 44 | | SK -- ^ Slovakia 45 | | ZA -- ^ South Africa 46 | | ES -- ^ Spain 47 | | SE -- ^ Sweden 48 | | CH -- ^ Switzerland 49 | | GB -- ^ United Kingdom 50 | | US -- ^ United States 51 | deriving (Bounded, Enum, Eq, Ord, Read, Show) 52 | 53 | instance FromJSON ISOCountryCode where 54 | parseJSON (String v) = readZ $ T.unpack v 55 | parseJSON _ = mzero 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Lizzie Siegle 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Lizzie Siegle nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Queue/Member.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Queue.Member 8 | ( -- * Resource 9 | Member(..) 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad 14 | import Data.Aeson 15 | import Data.Data 16 | import Data.Monoid 17 | import Data.Time.Clock 18 | import GHC.Generics 19 | import Network.URI 20 | 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource 24 | import Twilio.Types 25 | 26 | data Member = Member 27 | { callSID :: !CallSID 28 | , dateEnqueued :: !UTCTime 29 | , waitTime :: !Int 30 | , position :: !Int 31 | , uri :: !URI 32 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 33 | 34 | instance FromJSON Member where 35 | parseJSON (Object v) = Member 36 | <$> v .: "call_sid" 37 | <*> (v .: "date_enqueud" >>= parseDateTime) 38 | <*> v .: "wait_time" 39 | <*> v .: "position" 40 | <*> (v .: "uri" <&> parseRelativeReference 41 | >>= maybeReturn) 42 | parseJSON _ = mzero 43 | 44 | instance Get1 QueueSID Member where 45 | get1 (getSID -> queueSID) = request parseJSONFromResponse =<< makeTwilioRequest 46 | ("/Queues/" <> queueSID <> "/Front.json") 47 | 48 | instance Get2 QueueSID CallSID Member where 49 | get2 (getSID -> queueSID) (getSID -> callSID) = request parseJSONFromResponse =<< makeTwilioRequest 50 | ("/Queues/" <> queueSID <> "/Members/" <> callSID <> ".json") 51 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/UsageRecord.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.UsageRecord 5 | ( -- * Resource 6 | UsageRecord(..) 7 | ) where 8 | 9 | import Control.Applicative 10 | import Control.Error.Safe 11 | import Control.Monad 12 | import Data.Aeson 13 | import Data.Text (Text) 14 | import Data.Time.Clock 15 | import Network.URI 16 | 17 | import Twilio.Types 18 | import Twilio.Internal.Parser 19 | 20 | {- Resource -} 21 | 22 | data UsageRecord = UsageRecord 23 | { category :: !Text 24 | , description :: !Text 25 | , accountSID :: !AccountSID 26 | , startDate :: !UTCTime 27 | , endDate :: !UTCTime 28 | , usage :: !Double 29 | , usageUnit :: !Text 30 | , count :: !(Maybe Double) 31 | , countUnit :: !(Maybe Text) 32 | , price :: !Double 33 | , priceUnit :: !PriceUnit 34 | , uri :: !URI 35 | } deriving (Show, Eq) 36 | 37 | instance FromJSON UsageRecord where 38 | parseJSON (Object v) = UsageRecord 39 | <$> v .: "category" 40 | <*> v .: "description" 41 | <*> v .: "account_sid" 42 | <*> (v .: "start_date" >>= parseDate) 43 | <*> (v .: "end_date" >>= parseDate) 44 | <*> (v .: "usage" >>= readZ) 45 | <*> v .: "usage_unit" 46 | <*> (v .: "count" <&> fmap readZ 47 | >>= maybeReturn') 48 | <*> (v .: "count_unit" <&> (=<<) filterEmpty) 49 | <*> (v .: "price" >>= readZ) 50 | <*> v .: "price_unit" 51 | <*> (v .: "uri" <&> parseRelativeReference 52 | >>= maybeReturn) 53 | parseJSON _ = mzero 54 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Mark Andrus Roberts 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Mark Andrus Roberts nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/APIKeys.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | {-#LANGUAGE RankNTypes #-} 5 | 6 | module Twilio.APIKeys 7 | ( -- * Resource 8 | APIKeys(..) 9 | , Twilio.APIKeys.get 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad.Catch 14 | import Data.Aeson 15 | import Data.Maybe 16 | import Data.Text (Text) 17 | import Data.Text.Encoding 18 | 19 | import Control.Monad.Twilio 20 | import Twilio.APIKey 21 | import Twilio.Internal.Request 22 | import Twilio.Internal.Resource as Resource 23 | import Twilio.Types 24 | 25 | {- Resource -} 26 | 27 | data APIKeys = APIKeys 28 | { pagingInformation :: !PagingInformation 29 | , list :: ![APIKey] 30 | } deriving (Show, Eq, Ord) 31 | 32 | instance List APIKeys APIKey where 33 | getListWrapper = wrap (APIKeys . fromJust) 34 | getList = list 35 | getPlural = Const "keys" 36 | 37 | instance FromJSON APIKeys where 38 | parseJSON = parseJSONToList 39 | 40 | instance Get0 APIKeys where 41 | get0 = request parseJSONFromResponse =<< makeTwilioRequest "/Keys.json" 42 | 43 | {- | Get 'APIKeys'. 44 | 45 | For example, you can fetch the 'APIKeys' resource in the 'IO' monad as follows: 46 | 47 | >module Main where 48 | > 49 | >import Control.Monad.IO.Class (liftIO) 50 | >import System.Environment (getEnv) 51 | >import Twilio.APIKeys as APIKeys 52 | >import Twilio.Types 53 | > 54 | >-- | Print API Keys. 55 | >main :: IO () 56 | >main = runTwilio' (getEnv "ACCOUNT_SID") 57 | > (getEnv "AUTH_TOKEN") 58 | > $ APIKeys.get >>= liftIO . print 59 | -} 60 | get :: MonadThrow m => TwilioT m APIKeys 61 | get = Resource.get 62 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Message/Media.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Message.Media 8 | ( -- * Resource 9 | Media(..) 10 | -- * Types 11 | ) where 12 | 13 | import Control.Applicative 14 | import Control.Monad 15 | import Data.Aeson 16 | import Data.Data 17 | import Data.Monoid 18 | import Data.Text (Text) 19 | import Data.Time.Clock 20 | import GHC.Generics 21 | import Network.URI 22 | 23 | import Twilio.Internal.Parser 24 | import Twilio.Internal.Request 25 | import Twilio.Internal.Resource as Resource 26 | import Twilio.Types 27 | 28 | {- Resource -} 29 | 30 | data Media = Media 31 | { sid :: !MediaSID 32 | , dateCreated :: !UTCTime 33 | , dateUpdated :: !UTCTime 34 | , accountSID :: !AccountSID 35 | , parentSID :: !MessageSID 36 | , contentType :: !Text 37 | , uri :: !URI 38 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 39 | 40 | instance FromJSON Media where 41 | parseJSON (Object v) = Media 42 | <$> v .: "sid" 43 | <*> (v .: "date_created" >>= parseDateTime) 44 | <*> (v .: "date_updated" >>= parseDateTime) 45 | <*> v .: "account_sid" 46 | <*> v .: "parent_sid" 47 | <*> v .: "content_type" 48 | <*> (v .: "uri" <&> parseRelativeReference 49 | >>= maybeReturn) 50 | parseJSON _ = mzero 51 | 52 | instance Get2 MessageSID MediaSID Media where 53 | get2 (getSID -> messageSID) (getSID -> mediaSID) = request parseJSONFromResponse =<< makeTwilioRequest 54 | ("/Messages/" <> messageSID <> "/Media/" <> mediaSID <> ".json") 55 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Applications.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Applications 5 | ( -- * Resource 6 | Applications(..) 7 | , Twilio.Applications.get 8 | ) where 9 | 10 | import Control.Applicative 11 | import Control.Monad.Catch 12 | import Data.Aeson 13 | import Data.Maybe 14 | 15 | import Control.Monad.Twilio 16 | import Twilio.Application 17 | import Twilio.Internal.Request 18 | import Twilio.Internal.Resource as Resource 19 | import Twilio.Types 20 | 21 | {- Resource -} 22 | 23 | data Applications = Applications 24 | { applicationsPagingInformation :: PagingInformation 25 | , applicationList :: [Application] 26 | } deriving (Show, Eq) 27 | 28 | instance List Applications Application where 29 | getListWrapper = wrap (Applications . fromJust) 30 | getList = applicationList 31 | getPlural = Const "applications" 32 | 33 | instance FromJSON Applications where 34 | parseJSON = parseJSONToList 35 | 36 | instance Get0 Applications where 37 | get0 = request parseJSONFromResponse =<< makeTwilioRequest "/Applications.json" 38 | 39 | {- | Get the 'Applications' for your account. 40 | 41 | For example, you can fetch the 'Applications' resource in the 'IO' monad as follows: 42 | 43 | >module Main where 44 | > 45 | >import Control.Monad.IO.Class (liftIO) 46 | >import System.Environment (getEnv) 47 | >import Twilio.Applications as Applications 48 | >import Twilio.Types 49 | > 50 | >-- | Print applications. 51 | >main :: IO () 52 | >main = runTwilio' (getEnv "ACCOUNT_SID") 53 | > (getEnv "AUTH_TOKEN") 54 | > $ Applications.get >>= liftIO . print 55 | -} 56 | get :: MonadThrow m => TwilioT m Applications 57 | get = Resource.get 58 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/AvailablePhoneNumber.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.AvailablePhoneNumber 5 | ( -- * Resource 6 | AvailablePhoneNumber(..) 7 | ) where 8 | 9 | import Control.Applicative 10 | import Control.Error.Safe 11 | import Control.Monad 12 | import Data.Aeson 13 | import Data.Text (Text) 14 | 15 | import Twilio.Types 16 | import Twilio.Internal.Parser 17 | 18 | {- Resource -} 19 | 20 | data AvailablePhoneNumber = AvailablePhoneNumber 21 | { friendlyName :: !Text 22 | , phoneNumber :: !Text 23 | , lata :: !(Maybe Integer) 24 | , rateCenter :: !(Maybe Text) 25 | , latitude :: !(Maybe Double) 26 | , longitude :: !(Maybe Double) 27 | , region :: !Text 28 | , postalCode :: !(Maybe Integer) 29 | , isoCountry :: !ISOCountryCode 30 | , addressRequirements :: !(Maybe AddressRequirement) 31 | , capabilities :: !Capabilities 32 | } deriving (Eq, Show) 33 | 34 | instance FromJSON AvailablePhoneNumber where 35 | parseJSON (Object v) = AvailablePhoneNumber 36 | <$> v .: "friendly_name" 37 | <*> v .: "phone_number" 38 | <*> (v .: "lata" <&> (=<<) readZ 39 | >>= maybeReturn') 40 | <*> v .: "rate_center" 41 | <*> (v .: "latitude" <&> (=<<) readZ 42 | >>= maybeReturn') 43 | <*> (v .: "longitude" <&> (=<<) readZ 44 | >>= maybeReturn') 45 | <*> v .: "region" 46 | <*> (v .: "postal_code" <&> (=<<) readZ 47 | >>= maybeReturn') 48 | <*> v .: "iso_country" 49 | <*> v .: "address_requirements" 50 | <*> (v .: "capabilities" >>= parseJSON) 51 | parseJSON _ = mzero 52 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Address.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | {-#LANGUAGE ViewPatterns #-} 5 | 6 | module Twilio.Address 7 | ( -- * Resource 8 | Address(..) 9 | , Twilio.Address.get 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Error.Safe 14 | import Control.Monad 15 | import Control.Monad.Catch 16 | import Data.Aeson 17 | import Data.Monoid 18 | import Data.Text (Text) 19 | 20 | import Control.Monad.Twilio 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource as Resource 24 | import Twilio.Types 25 | 26 | {- Resource -} 27 | 28 | data Address = Address 29 | { sid :: !AddressSID 30 | , accountSID :: !AccountSID 31 | , friendlyName :: !Text 32 | , customerName :: !Text 33 | , street :: !Text 34 | , city :: !Text 35 | , region :: !Text 36 | , postalCode :: !(Maybe Integer) 37 | , isoCountry :: !ISOCountryCode 38 | } deriving (Eq, Show) 39 | 40 | instance FromJSON Address where 41 | parseJSON (Object v) = Address 42 | <$> v .: "sid" 43 | <*> v .: "account_sid" 44 | <*> v .: "friendly_name" 45 | <*> v .: "customer_name" 46 | <*> v .: "street" 47 | <*> v .: "city" 48 | <*> v .: "region" 49 | <*> (v .: "postal_code" <&> (=<<) readZ 50 | >>= maybeReturn') 51 | <*> v .: "iso_country" 52 | parseJSON _ = mzero 53 | 54 | instance Get1 AddressSID Address where 55 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 56 | ("/Addresses" <> sid <> ".json") 57 | 58 | -- | Get an 'Address' by 'AddressSID'. 59 | get :: MonadThrow m => AddressSID -> TwilioT m Address 60 | get = Resource.get 61 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/README.md: -------------------------------------------------------------------------------- 1 | twilio 2 | ====== 3 | 4 | [![twilio on Hackage](https://img.shields.io/hackage/v/twilio.svg)](https://hackage.haskell.org/package/twilio) [![twilio-haskell on Travis CI](https://travis-ci.org/markandrus/twilio-haskell.svg)](https://travis-ci.org/markandrus/twilio-haskell) 5 | 6 | This package provides a library for interacting with 7 | [Twilio's API](www.twilio.com/docs/api). Install using 8 | 9 | ``` 10 | $ cabal install twilio 11 | ``` 12 | 13 | Documentation soon to be available on Hackage. For now, see [markandrus.github.io/twilio-haskell](http://markandrus.github.io/twilio-haskell). 14 | 15 | For TwiML, see [twiml-haskell](http://github.com/markandrus/twiml-haskell). 16 | 17 | Example 18 | ------- 19 | 20 | You can create a REST API client and fetch the calls resources as follows 21 | 22 | ```hs 23 | {-#LANGUAGE OverloadedStrings #-} 24 | 25 | module Main where 26 | 27 | import Control.Monad.IO.Class (liftIO) 28 | import System.Environment (getEnv) 29 | import Twilio 30 | import Twilio.Calls as Calls 31 | import Twilio.Messages 32 | 33 | main :: IO () 34 | main = runTwilio' (getEnv "ACCOUNT_SID") 35 | (getEnv "AUTH_TOKEN") $ do 36 | -- Print Calls. 37 | calls <- Calls.get 38 | liftIO $ print calls 39 | 40 | -- Send a Message. 41 | let body = PostMessage "+14158059869" "+14158059869" "Oh, hai" 42 | message <- post body 43 | liftIO $ print message 44 | ``` 45 | 46 | Contributing 47 | ------------ 48 | 49 | Feel free to contribute to any of the open [issues] 50 | (https://github.com/markandrus/twilio-haskell/issues), bugfixes, etc. When you 51 | think you're ready to merge, ensure the tests are passing and open a pull 52 | request. If you are adding new functionality, please include new tests as well. 53 | Finally, add yourself to the `AUTHORS` file. 54 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Messages.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Messages 5 | ( -- * Resource 6 | Messages(..) 7 | , PostMessage(..) 8 | , Twilio.Messages.get 9 | , Twilio.Messages.post 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad.Catch 14 | import Data.Aeson 15 | import Data.Text (Text) 16 | import Data.Text.Encoding 17 | import Data.Maybe 18 | 19 | import Control.Monad.Twilio 20 | import Twilio.Internal.Request 21 | import Twilio.Internal.Resource as Resource 22 | import Twilio.Message 23 | import Twilio.Types 24 | 25 | {- Resource -} 26 | 27 | data Messages = Messages 28 | { messagesPagingInformation :: PagingInformation 29 | , messageList :: [Message] 30 | } deriving (Show, Eq) 31 | 32 | data PostMessage = PostMessage 33 | { sendTo :: !Text 34 | , sendFrom :: !Text 35 | , sendBody :: !Text 36 | } deriving (Show, Eq) 37 | 38 | instance List Messages Message where 39 | getListWrapper = wrap (Messages . fromJust) 40 | getList = messageList 41 | getPlural = Const "messages" 42 | 43 | instance FromJSON Messages where 44 | parseJSON = parseJSONToList 45 | 46 | instance Get0 Messages where 47 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 48 | "/Messages.json" 49 | 50 | instance Post1 PostMessage Message where 51 | post1 msg = request parseJSONFromResponse =<< 52 | makeTwilioPOSTRequest "/Messages.json" 53 | [ ("To", encodeUtf8 $ sendTo msg) 54 | , ("From", encodeUtf8 $ sendFrom msg) 55 | , ("Body", encodeUtf8 $ sendBody msg) 56 | ] 57 | 58 | -- | Get 'Messages'. 59 | get :: MonadThrow m => TwilioT m Messages 60 | get = Resource.get 61 | 62 | -- | Send a text message. 63 | post :: MonadThrow m => PostMessage -> TwilioT m Message 64 | post = Resource.post 65 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/OutgoingCallerID.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.OutgoingCallerID 6 | ( -- * Resource 7 | OutgoingCallerID(..) 8 | , PhoneNumberSID 9 | , Twilio.OutgoingCallerID.get 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad 14 | import Control.Monad.Catch 15 | import Data.Aeson 16 | import Data.Monoid 17 | import Data.Text (Text) 18 | import Data.Time.Clock 19 | import Network.URI 20 | 21 | import Control.Monad.Twilio 22 | import Twilio.Internal.Parser 23 | import Twilio.Internal.Request 24 | import Twilio.Internal.Resource as Resource 25 | import Twilio.Types 26 | 27 | {- Resource -} 28 | 29 | data OutgoingCallerID = OutgoingCallerID 30 | { sid :: !PhoneNumberSID 31 | , dateCreated :: !UTCTime 32 | , dateUpdated :: !UTCTime 33 | , friendlyName :: !Text 34 | , accountSID :: !AccountSID 35 | , phoneNumber :: !Text 36 | , uri :: !URI 37 | } deriving (Show, Eq) 38 | 39 | instance FromJSON OutgoingCallerID where 40 | parseJSON (Object v) = OutgoingCallerID 41 | <$> v .: "sid" 42 | <*> (v .: "date_created" >>= parseDateTime) 43 | <*> (v .: "date_updated" >>= parseDateTime) 44 | <*> v .: "friendly_name" 45 | <*> v .: "account_sid" 46 | <*> v .: "phone_number" 47 | <*> (v .: "uri" <&> parseRelativeReference 48 | >>= maybeReturn) 49 | parseJSON _ = mzero 50 | 51 | instance Get1 PhoneNumberSID OutgoingCallerID where 52 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 53 | ("/OutgoingCallerIds/" <> sid <> ".json") 54 | 55 | -- | Get an 'OutgoingCallerID' by 'PhoneNumberSID' 56 | get :: MonadThrow m => PhoneNumberSID -> TwilioT m OutgoingCallerID 57 | get = Resource.get 58 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Recording.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.Recording 6 | ( -- * Resource 7 | Recording(..) 8 | , Twilio.Recording.get 9 | ) where 10 | 11 | import Control.Applicative 12 | import Control.Error.Safe 13 | import Control.Monad 14 | import Control.Monad.Catch 15 | import Data.Aeson 16 | import Data.Monoid 17 | import Data.Time.Clock 18 | import Network.URI 19 | 20 | import Control.Monad.Twilio 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource as Resource 24 | import Twilio.Types 25 | 26 | {- Resource -} 27 | data Recording = Recording 28 | { sid :: !RecordingSID 29 | , dateCreated :: !UTCTime 30 | , dateUpdated :: !UTCTime 31 | , accountSID :: !AccountSID 32 | , callSID :: !CallSID 33 | , duration :: !(Maybe Int) 34 | , apiVersion :: !APIVersion 35 | , uri :: !URI 36 | } deriving (Show, Eq) 37 | 38 | 39 | instance FromJSON Recording where 40 | parseJSON (Object v) = Recording 41 | <$> v .: "sid" 42 | <*> (v .: "date_created" >>= parseDateTime) 43 | <*> (v .: "date_updated" >>= parseDateTime) 44 | <*> v .: "account_sid" 45 | <*> v .: "call_sid" 46 | <*> (v .: "duration" <&> fmap readZ 47 | >>= maybeReturn') 48 | <*> v .: "api_version" 49 | <*> (v .: "uri" <&> parseRelativeReference 50 | >>= maybeReturn) 51 | parseJSON _ = mzero 52 | 53 | instance Get1 RecordingSID Recording where 54 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 55 | ("/Recordings/" <> sid <> ".json") 56 | 57 | -- | Get a 'Recording' by 'RecordingSID'. 58 | get :: MonadThrow m => RecordingSID -> TwilioT m Recording 59 | get = Resource.get 60 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Conference/Participant.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Conference.Participant 8 | ( -- * Resource 9 | Participant(..) 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad 14 | import Data.Aeson 15 | import Data.Data 16 | import Data.Monoid 17 | import Data.Time.Clock 18 | import GHC.Generics 19 | import Network.URI 20 | 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource 24 | import Twilio.Types 25 | 26 | data Participant = Participant 27 | { callSID :: !CallSID 28 | , conferenceSID :: !ConferenceSID 29 | , dateCreated :: !UTCTime 30 | , dateUpdated :: !UTCTime 31 | , accountSID :: !AccountSID 32 | , muted :: !Bool 33 | , startConferenceOnEnter :: !Bool 34 | , endConferenceOnExit :: !Bool 35 | , uri :: !URI 36 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 37 | 38 | instance FromJSON Participant where 39 | parseJSON (Object v) = Participant 40 | <$> v .: "call_sid" 41 | <*> v .: "conference_sid" 42 | <*> (v .: "date_created" >>= parseDateTime) 43 | <*> (v .: "date_updated" >>= parseDateTime) 44 | <*> v .: "account_sid" 45 | <*> v .: "muted" 46 | <*> v .: "start_conference_on_enter" 47 | <*> v .: "end_conference_on_exit" 48 | <*> (v .: "uri" <&> parseRelativeReference 49 | >>= maybeReturn) 50 | parseJSON _ = mzero 51 | 52 | instance Get2 ConferenceSID CallSID Participant where 53 | get2 (getSID -> conferenceSID) (getSID -> callSID) = request parseJSONFromResponse =<< makeTwilioRequest 54 | ("/Conferences/" <> conferenceSID <> "/Participants/" <> callSID <> ".json") 55 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Queues.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE TupleSections #-} 6 | 7 | module Twilio.Queues 8 | ( -- * Resource 9 | Queues(..) 10 | , Twilio.Queues.get 11 | , PostQueues(..) 12 | , Twilio.Queues.post 13 | ) where 14 | 15 | import Control.Applicative 16 | import Control.Monad.Catch 17 | import Data.Aeson 18 | import Data.Data 19 | import Data.Maybe 20 | import Data.Text 21 | import Data.Text.Encoding 22 | import GHC.Generics 23 | 24 | import Control.Monad.Twilio 25 | import Twilio.Queue 26 | import Twilio.Internal.Request 27 | import Twilio.Internal.Resource as Resource 28 | import Twilio.Types 29 | 30 | data Queues = Queues 31 | { queuesPagingInformation :: PagingInformation 32 | , queueList :: [Queue] 33 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 34 | 35 | instance List Queues Queue where 36 | getListWrapper = wrap (Queues . fromJust) 37 | getList = queueList 38 | getPlural = Const "queues" 39 | 40 | instance FromJSON Queues where 41 | parseJSON = parseJSONToList 42 | 43 | instance Get0 Queues where 44 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 45 | "/Queues.json" 46 | 47 | get :: MonadThrow m => TwilioT m Queues 48 | get = Resource.get 49 | 50 | data PostQueues = PostQueues 51 | { friendlyName :: !(Maybe Text) 52 | , maxSize :: !(Maybe Int) 53 | } deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 54 | 55 | instance Post1 PostQueues Queue where 56 | post1 postQueues = request parseJSONFromResponse =<< 57 | makeTwilioPOSTRequest "/Queues.json" (catMaybes 58 | [ ("FriendlyName",) . encodeUtf8 <$> Twilio.Queues.friendlyName postQueues 59 | , ("MaxSize",) . encodeUtf8 . pack . show <$> Twilio.Queues.maxSize postQueues 60 | ] ) 61 | 62 | post :: MonadThrow m => PostQueues -> TwilioT m Queue 63 | post = Resource.post 64 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/ShortCode.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.ShortCode 6 | ( -- * Resource 7 | ShortCode(..) 8 | , Twilio.ShortCode.get 9 | ) where 10 | 11 | import Control.Applicative 12 | import Control.Monad 13 | import Control.Monad.Catch 14 | import Data.Aeson 15 | import Data.Monoid 16 | import Data.Text (Text) 17 | import Data.Time.Clock 18 | import Network.URI 19 | 20 | import Control.Monad.Twilio 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource as Resource 24 | import Twilio.Types 25 | 26 | {- Resource -} 27 | 28 | data ShortCode = ShortCode 29 | { sid :: !ShortCodeSID 30 | , dateCreated :: !UTCTime 31 | , dateUpdated :: !UTCTime 32 | , friendlyName :: !Text 33 | , accountSID :: !AccountSID 34 | , shortCode :: !Text 35 | , smsURL :: !(Maybe Text) 36 | , smsMethod :: !Text 37 | , smsFallbackURL :: !(Maybe Text) 38 | , smsFallbackMethod :: !Text 39 | , uri :: !URI 40 | } deriving (Eq, Show) 41 | 42 | instance FromJSON ShortCode where 43 | parseJSON (Object v) = ShortCode 44 | <$> v .: "sid" 45 | <*> (v .: "date_created" >>= parseDateTime) 46 | <*> (v .: "date_updated" >>= parseDateTime) 47 | <*> v .: "friendly_name" 48 | <*> v .: "account_sid" 49 | <*> v .: "short_code" 50 | <*> (v .: "sms_url" <&> getNonEmptyText) 51 | <*> v .: "sms_method" 52 | <*> (v .: "sms_fallback_url" <&> getNonEmptyText) 53 | <*> v .: "sms_fallback_method" 54 | <*> (v .: "uri" <&> parseRelativeReference 55 | >>= maybeReturn) 56 | parseJSON _ = mzero 57 | 58 | instance Get1 ShortCodeSID ShortCode where 59 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 60 | ("/SMS/ShortCodes/" <> sid <> ".json") 61 | 62 | -- | Get a 'ShortCode' by 'ShortCodeSID'. 63 | get :: MonadThrow m => ShortCodeSID -> TwilioT m ShortCode 64 | get = Resource.get 65 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Conference.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Conference 8 | ( -- * Resource 9 | Conference(..) 10 | -- * Types 11 | , ConferenceStatus(..) 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Monad 16 | import Data.Aeson 17 | import Data.Data 18 | import Data.Monoid 19 | import Data.Text (Text) 20 | import Data.Time.Clock 21 | import GHC.Generics 22 | import Network.URI 23 | 24 | import Twilio.Internal.Parser 25 | import Twilio.Internal.Request 26 | import Twilio.Internal.Resource 27 | import Twilio.Types 28 | 29 | {- Resource -} 30 | 31 | data Conference = Conference 32 | { sid :: !ConferenceSID 33 | , friendlyName :: !Text 34 | , status :: !ConferenceStatus 35 | , dateCreated :: !UTCTime 36 | , dateUpdated :: !UTCTime 37 | , accountSID :: !AccountSID 38 | , uri :: !URI 39 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 40 | 41 | instance FromJSON Conference where 42 | parseJSON (Object v) = Conference 43 | <$> v .: "sid" 44 | <*> v .: "friendly_name" 45 | <*> v .: "status" 46 | <*> (v .: "date_created" >>= parseDateTime) 47 | <*> (v .: "date_updated" >>= parseDateTime) 48 | <*> v .: "account_sid" 49 | <*> (v .: "uri" <&> parseRelativeReference 50 | >>= maybeReturn) 51 | parseJSON _ = mzero 52 | 53 | {- Types -} 54 | 55 | data ConferenceStatus 56 | = Init 57 | | InProgress 58 | | Completed 59 | deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable) 60 | 61 | instance FromJSON ConferenceStatus where 62 | parseJSON (String "init") = return Init 63 | parseJSON (String "in-progress") = return InProgress 64 | parseJSON (String "completed") = return Completed 65 | parseJSON _ = mzero 66 | 67 | instance Get1 ConferenceSID Conference where 68 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 69 | ("/Conferences/" <> sid <> ".json") 70 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Queue.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Queue 8 | ( -- * Resource 9 | Queue(..) 10 | , Twilio.Queue.get 11 | , Twilio.Queue.delete 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Monad 16 | import Control.Monad.Catch 17 | import Data.Aeson 18 | import Data.Data 19 | import Data.Monoid 20 | import Data.Text (Text) 21 | import Data.Time.Clock 22 | import GHC.Generics 23 | import Network.URI 24 | 25 | import Control.Monad.Twilio 26 | import Twilio.Internal.Parser 27 | import Twilio.Internal.Request 28 | import Twilio.Internal.Resource as Resource 29 | import Twilio.Types 30 | 31 | {- Resource -} 32 | 33 | data Queue = Queue 34 | { sid :: !QueueSID 35 | , friendlyName :: !Text 36 | , currentSize :: !Int 37 | , maxSize :: !Int 38 | , averageWaitTime :: !Float 39 | , dateCreated :: !UTCTime 40 | , dateUpdated :: !UTCTime 41 | , uri :: !URI 42 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 43 | 44 | instance FromJSON Queue where 45 | parseJSON (Object v) = Queue 46 | <$> v .: "sid" 47 | <*> v .: "friendly_name" 48 | <*> v .: "current_size" 49 | <*> v .: "max_size" 50 | <*> v .: "average_wait_time" 51 | <*> (v .: "date_created" >>= parseDateTime) 52 | <*> (v .: "date_updated" >>= parseDateTime) 53 | <*> (v .: "uri" <&> parseRelativeReference 54 | >>= maybeReturn) 55 | parseJSON _ = mzero 56 | 57 | instance Get1 QueueSID Queue where 58 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 59 | ("/Queues/" <> sid <> ".json") 60 | 61 | get :: MonadThrow m => QueueSID -> TwilioT m Queue 62 | get = Resource.get 63 | 64 | instance Delete1 QueueSID where 65 | delete1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioDELETERequest 66 | ("/Queues/" <> sid <> ".json") 67 | 68 | delete :: MonadThrow m => QueueSID -> TwilioT m () 69 | delete = Resource.delete 70 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Internal/Parser.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE OverloadedStrings #-} 2 | 3 | module Twilio.Internal.Parser where 4 | 5 | import Control.Applicative 6 | import Control.Monad 7 | import Data.Aeson 8 | import Data.Aeson.Types 9 | import Data.Text (Text) 10 | import qualified Data.Text as T 11 | import Data.Time.Clock 12 | import Data.Time.Format 13 | 14 | (<&>) :: Functor f => f a -> (a -> b) -> f b 15 | (<&>) = flip fmap 16 | 17 | maybeReturn' :: Maybe (Maybe a) -> Parser (Maybe a) 18 | maybeReturn' Nothing = return Nothing 19 | maybeReturn' (Just Nothing) = mzero 20 | maybeReturn' (Just ma) = return ma 21 | 22 | maybeReturn'' :: Maybe (Maybe (Maybe a)) -> Parser (Maybe a) 23 | maybeReturn'' Nothing = return Nothing 24 | maybeReturn'' (Just Nothing) = return Nothing 25 | maybeReturn'' (Just (Just Nothing)) = mzero 26 | maybeReturn'' (Just (Just ma)) = return ma 27 | 28 | filterEmpty :: Text -> Maybe Text 29 | filterEmpty "" = Nothing 30 | filterEmpty t = Just t 31 | 32 | parseDate :: (Monad m, MonadPlus m) => Text -> m UTCTime 33 | parseDate s = 34 | case parseTimeM True defaultTimeLocale "%F" (T.unpack s) of 35 | Just date -> return date 36 | Nothing -> mzero 37 | 38 | parseDateTime :: (Monad m, MonadPlus m) => Text -> m UTCTime 39 | parseDateTime s = 40 | case parseTimeM True defaultTimeLocale "%a, %d %b %Y %T %z" (T.unpack s) of 41 | Just dateTime -> return dateTime 42 | Nothing -> mzero 43 | 44 | maybeReturn :: (Monad m, MonadPlus m) => Maybe a -> m a 45 | maybeReturn (Just a) = return a 46 | maybeReturn Nothing = mzero 47 | 48 | newtype NonEmptyText = NonEmptyText { getNonEmptyText :: Maybe Text } 49 | 50 | instance FromJSON NonEmptyText where 51 | parseJSON (String "") = return $ NonEmptyText Nothing 52 | parseJSON (String v) = return . NonEmptyText $ Just v 53 | parseJSON _ = mzero 54 | 55 | -- | Note that the parser only returns Nothing if the input 56 | -- is Nothing. If the input is an incorrectly formatted 57 | -- Text the parse will fail. 58 | parseMaybeDateTime :: Maybe Text -> Parser (Maybe UTCTime) 59 | parseMaybeDateTime (Just a) = Just <$> parseDateTime a 60 | parseMaybeDateTime Nothing = return Nothing 61 | 62 | valueToText :: Value -> Maybe Text 63 | valueToText (String v) = Just v 64 | valueToText _ = Nothing 65 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/AuthorizedConnectApp.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.AuthorizedConnectApp 6 | ( -- * Resource 7 | AuthorizedConnectApp(..) 8 | , ConnectAppSID 9 | , Twilio.AuthorizedConnectApp.get 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad 14 | import Control.Monad.Catch 15 | import Data.Aeson 16 | import Data.Monoid 17 | import Data.Text (Text) 18 | import qualified Data.Text as T 19 | import Data.Time.Clock 20 | import Network.URI 21 | 22 | import Control.Monad.Twilio 23 | import Twilio.Internal.Parser 24 | import Twilio.Internal.Request 25 | import Twilio.Internal.Resource as Resource 26 | import Twilio.Types 27 | 28 | {- Resource -} 29 | 30 | data AuthorizedConnectApp = AuthorizedConnectApp 31 | { dateCreated :: !UTCTime 32 | , dateUpdated :: !UTCTime 33 | , accountSID :: !AccountSID 34 | -- , permissions :: !Permissions 35 | , sid :: !ConnectAppSID 36 | , friendlyName :: !Text 37 | , description :: !Text 38 | , companyName :: !Text 39 | , homepageURL :: !(Maybe URI) 40 | , uri :: !URI 41 | } deriving (Show, Eq) 42 | 43 | instance FromJSON AuthorizedConnectApp where 44 | parseJSON (Object v) = AuthorizedConnectApp 45 | <$> (v .: "date_created" >>= parseDateTime) 46 | <*> (v .: "date_updated" >>= parseDateTime) 47 | <*> v .: "account_sid" 48 | -- <*> v .: "permissions" 49 | <*> v .: "connect_app_sid" 50 | <*> v .: "connect_app_friendly_name" 51 | <*> v .: "connect_app_description" 52 | <*> v .: "connect_app_company_name" 53 | <*> (v .: "connect_app_homepage_url" <&> fmap filterEmpty 54 | <&> fmap (fmap $ parseURI . T.unpack) 55 | >>= maybeReturn'') 56 | <*> (v .: "uri" <&> parseRelativeReference 57 | >>= maybeReturn) 58 | parseJSON _ = mzero 59 | 60 | instance Get1 ConnectAppSID AuthorizedConnectApp where 61 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 62 | ("/AuthorizedConnectApps/" <> sid <> ".json") 63 | 64 | -- | Get an 'AuthorizedConnectApp' by 'ConnectAppSID'. 65 | get :: MonadThrow m => ConnectAppSID -> TwilioT m AuthorizedConnectApp 66 | get = Resource.get 67 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/UsageTrigger.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.UsageTrigger 6 | ( -- * Resource 7 | UsageTrigger(..) 8 | , Twilio.UsageTrigger.get 9 | ) where 10 | 11 | import Control.Applicative 12 | import Control.Monad 13 | import Control.Monad.Catch 14 | import Data.Aeson 15 | import Data.Monoid 16 | import Data.Text (Text) 17 | import Data.Time.Clock 18 | import Network.URI 19 | 20 | import Control.Monad.Twilio 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource as Resource 24 | import Twilio.Types 25 | 26 | {- Resource -} 27 | 28 | data UsageTrigger = UsageTrigger 29 | { sid :: !UsageTriggerSID 30 | , dateCreated :: !UTCTime 31 | , dateUpdated :: !UTCTime 32 | , accountSID :: !AccountSID 33 | , friendlyName :: !Text 34 | , recurring :: !(Maybe Text) 35 | , usageCategory :: !Text 36 | , triggerBy :: !Text 37 | , triggerValue :: !Text 38 | , currentValue :: !Text 39 | , usageRecordURI :: !URI 40 | , callbackURL :: !(Maybe Text) 41 | , callbackMethod :: !Text 42 | , dateFired :: !(Maybe Text) 43 | , uri :: !URI 44 | } deriving (Eq, Show) 45 | 46 | instance FromJSON UsageTrigger where 47 | parseJSON (Object v) = UsageTrigger 48 | <$> v .: "sid" 49 | <*> (v .: "date_created" >>= parseDateTime) 50 | <*> (v .: "date_updated" >>= parseDateTime) 51 | <*> v .: "account_sid" 52 | <*> v .: "friendly_name" 53 | <*> v .: "recurring" 54 | <*> v .: "usage_category" 55 | <*> v .: "trigger_by" 56 | <*> v .: "trigger_value" 57 | <*> v .: "current_value" 58 | <*> (v .: "usage_record_uri" <&> parseRelativeReference 59 | >>= maybeReturn) 60 | <*> v .: "callback_url" 61 | <*> v .: "callback_method" 62 | <*> v .: "date_fired" 63 | <*> (v .: "uri" <&> parseRelativeReference 64 | >>= maybeReturn) 65 | parseJSON _ = mzero 66 | 67 | instance Get1 UsageTriggerSID UsageTrigger where 68 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 69 | ("/Usage/Triggers/" <> sid <> ".json") 70 | 71 | -- | Get a 'UsageTrigger' by 'UsageTriggerSID'. 72 | get :: MonadThrow m => UsageTriggerSID -> TwilioT m UsageTrigger 73 | get = Resource.get 74 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-8.19 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | # Dependency packages to be pulled from upstream that are not in the resolver 41 | # (e.g., acme-missiles-0.3) 42 | extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | flags: {} 46 | 47 | # Extra package databases containing global packages 48 | extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=1.3" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/Issue.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | 5 | module Twilio.Types.Issue 6 | ( Issue(..) 7 | ) where 8 | 9 | import Control.Monad 10 | import Data.Aeson 11 | import Data.Data 12 | import GHC.Generics 13 | 14 | -- | A list of issues experienced during the call. 15 | data Issue 16 | = ImperfectAudio -- ^ Imperfect audio quality: Choppy, echoed, or garbled audio during conversation. 17 | | DroppedCall -- ^ Dropped call: call initially connected but was dropped. 18 | | IncorrectCallerId -- ^ Incorrect caller ID: Call connected but caller ID displayed \'Unknown\' or an incorrect number. 19 | | PostDialDelay -- ^ Post dial delay: Call connected but there was a long delay between dialing the phone number and the start of ringing. 20 | | DigitsNotCaptured -- ^ DTMF tones not captured: Failed to capture digit input on phone menus. 21 | | UnsolicitedCall -- ^ Unsolicited call: Received telemarketer, wrong number, automated or other type of unsolicited call. 22 | | AudioLatency -- ^ Audio latency: Call participants can hear each other but with significant audio delay. 23 | | OneWayAudio -- ^ One way audio: Only one party could hear the audio during the conversation. 24 | deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable) 25 | 26 | instance FromJSON Issue where 27 | parseJSON (String "imperfect-audio") = return ImperfectAudio 28 | parseJSON (String "dropped-call") = return DroppedCall 29 | parseJSON (String "incorrect-caller-id") = return IncorrectCallerId 30 | parseJSON (String "post-dial-delay") = return PostDialDelay 31 | parseJSON (String "digits-not-captured") = return DigitsNotCaptured 32 | parseJSON (String "unsolicited-call") = return UnsolicitedCall 33 | parseJSON (String "audio-latency") = return AudioLatency 34 | parseJSON (String "one-way-audio") = return OneWayAudio 35 | parseJSON _ = mzero 36 | 37 | instance ToJSON Issue where 38 | toJSON ImperfectAudio = String "imperfect-audio" 39 | toJSON DroppedCall = String "dropped-call" 40 | toJSON IncorrectCallerId = String "incorrect-caller-id" 41 | toJSON PostDialDelay = String "post-dial-delay" 42 | toJSON DigitsNotCaptured = String "digits-not-captured" 43 | toJSON UnsolicitedCall = String "unsolicited-call" 44 | toJSON AudioLatency = String "audio-latency" 45 | toJSON OneWayAudio = String "one-way-audio" 46 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/ConnectApp.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.ConnectApp 6 | ( -- * Resource 7 | ConnectApp(..) 8 | , ConnectAppSID 9 | , Twilio.ConnectApp.get 10 | ) where 11 | 12 | import Control.Applicative 13 | import Control.Monad 14 | import Control.Monad.Catch 15 | import Data.Aeson 16 | import Data.Monoid 17 | import Data.Text (Text) 18 | import qualified Data.Text as T 19 | import Network.URI 20 | 21 | import Control.Monad.Twilio 22 | import Twilio.Internal.Parser 23 | import Twilio.Internal.Request 24 | import Twilio.Internal.Resource as Resource 25 | import Twilio.Types 26 | 27 | {- Resource -} 28 | 29 | data ConnectApp = ConnectApp 30 | { sid :: !ConnectAppSID 31 | , accountSID :: !AccountSID 32 | -- , permissions :: ! 33 | , friendlyName :: !Text 34 | , description :: !Text 35 | , companyName :: !Text 36 | , homepageURL :: !(Maybe URI) 37 | , authorizeRedirectURL :: !(Maybe URI) 38 | , deauthorizeCallbackURL :: !(Maybe URI) 39 | -- , deauthorizeCallbackMethod :: !Text 40 | , uri :: !URI 41 | } deriving (Show, Eq) 42 | 43 | instance FromJSON ConnectApp where 44 | parseJSON (Object v) = ConnectApp 45 | <$> v .: "sid" 46 | <*> v .: "account_sid" 47 | -- <*> v .: "permissions" 48 | <*> v .: "friendly_name" 49 | <*> v .: "description" 50 | <*> v .: "company_name" 51 | <*> (v .: "homepage_url" <&> fmap filterEmpty 52 | <&> fmap (fmap $ parseURI . T.unpack) 53 | >>= maybeReturn'') 54 | <*> (v .: "authorize_redirect_url" <&> fmap filterEmpty 55 | <&> fmap (fmap $ parseURI . T.unpack) 56 | >>= maybeReturn'') 57 | <*> (v .: "deauthorize_callback_url" <&> fmap filterEmpty 58 | <&> fmap (fmap $ parseURI . T.unpack) 59 | >>= maybeReturn'') 60 | -- <*> v .: "deauthorize_callback_method" 61 | <*> (v .: "uri" <&> parseRelativeReference 62 | >>= maybeReturn) 63 | parseJSON _ = mzero 64 | 65 | instance Get1 ConnectAppSID ConnectApp where 66 | get1 (getSID -> sid) = request parseJSONFromResponse =<< 67 | makeTwilioRequest ("/ConnectApps/" <> sid <> ".json") 68 | 69 | -- | Get a 'ConnectApp' by 'ConnectAppSID'. 70 | get :: MonadThrow m => ConnectAppSID -> TwilioT m ConnectApp 71 | get = Resource.get 72 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Tokens.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | {-#LANGUAGE ViewPatterns #-} 5 | 6 | module Twilio.Tokens 7 | ( -- * Resource 8 | Token(..) 9 | , IceServer(..) 10 | , Twilio.Tokens.post 11 | ) where 12 | 13 | import Control.Applicative 14 | import Control.Error.Safe 15 | import Control.Monad 16 | import Control.Monad.Catch 17 | import Data.Aeson 18 | import qualified Data.HashMap.Strict as HashMap 19 | import Data.Maybe 20 | import Data.Text (Text) 21 | import qualified Data.Text as T 22 | import Data.Text.Encoding 23 | import Data.Time.Clock 24 | import Network.URI 25 | 26 | import Control.Monad.Twilio 27 | import Twilio.Types 28 | import Twilio.Internal.Parser 29 | import Twilio.Internal.Request 30 | import Twilio.Internal.Resource as Resource 31 | 32 | {- Resource -} 33 | 34 | data Token = Token 35 | { accountSID :: !AccountSID 36 | , dateCreated :: !UTCTime 37 | , dateUpdated :: !UTCTime 38 | , iceServers :: [IceServer] 39 | , password :: !Text 40 | , ttl :: !Integer 41 | , username :: !Text 42 | } deriving (Eq, Show) 43 | 44 | instance FromJSON Token where 45 | parseJSON (Object v) = Token 46 | <$> v .: "account_sid" 47 | <*> (v .: "date_created" >>= parseDateTime) 48 | <*> (v .: "date_updated" >>= parseDateTime) 49 | <*> v .: "ice_servers" 50 | <*> v .: "password" 51 | <*> (v .: "ttl" >>= readZ) 52 | <*> v .: "username" 53 | parseJSON _ = mzero 54 | 55 | data IceServer 56 | = StunServer { stunURL :: !URI } 57 | | TurnServer { turnURL :: !URI 58 | , turnCredential :: !Text 59 | , turnUsername :: !Text } 60 | deriving (Eq, Show) 61 | 62 | instance FromJSON IceServer where 63 | parseJSON (Object map) = 64 | let url = HashMap.lookup "url" map >>= valueToText >>= parseAbsoluteURI . T.unpack 65 | in case url of 66 | Nothing -> mzero 67 | Just url' -> return . fromMaybe (StunServer url') $ TurnServer 68 | <$> url 69 | <*> (HashMap.lookup "credential" map >>= valueToText) 70 | <*> (HashMap.lookup "username" map >>= valueToText) 71 | parseJSON _ = mzero 72 | 73 | instance Post0 Token where 74 | post0 = request parseJSONFromResponse =<< 75 | makeTwilioPOSTRequest "/Tokens.json" [] 76 | 77 | instance Post1 Integer Token where 78 | post1 (show -> ttl) = request parseJSONFromResponse =<< 79 | makeTwilioPOSTRequest "/Tokens.json" 80 | [ ("Ttl", encodeUtf8 . T.pack $ ttl ) ] 81 | 82 | instance Post1 (Maybe Integer) Token where 83 | post1 Nothing = post0 84 | post1 (Just ttl) = post1 ttl 85 | 86 | post :: MonadThrow m => Maybe Integer -> TwilioT m Token 87 | post = Resource.post 88 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # http://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # resolver: ghcjs-0.1.0_ghc-7.10.2 15 | # resolver: 16 | # name: custom-snapshot 17 | # location: "./custom-snapshot.yaml" 18 | resolver: lts-8.12 19 | 20 | # User packages to be built. 21 | # Various formats can be used as shown in the example below. 22 | # 23 | # packages: 24 | # - some-directory 25 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 26 | # - location: 27 | # git: https://github.com/commercialhaskell/stack.git 28 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 29 | # - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a 30 | # extra-dep: true 31 | # subdirs: 32 | # - auto-update 33 | # - wai 34 | # 35 | # A package marked 'extra-dep: true' will only be built if demanded by a 36 | # non-dependency (i.e. a user package), and its test suites and benchmarks 37 | # will not be run. This is useful for tweaking upstream packages. 38 | packages: 39 | - '.' 40 | - location: twilio-haskell-move-to-stack 41 | # Dependency packages to be pulled from upstream that are not in the resolver 42 | # (e.g., acme-missiles-0.3) 43 | extra-deps: 44 | - linklater-3.2.0.0 45 | - base-4.9 46 | - api-builder-0.12.0.0 47 | - reddit-0.2.1.0 48 | - tightrope-0.1.0.1 49 | - wai-extra-3.0.19.1 50 | - wai-logger-2.3.0 51 | - http-client-0.5.7.0 52 | - wreq-0.5.0.1 53 | - http-client-tls-0.3.5.1 54 | - authenticate-oauth-1.6 55 | # This is so that linklater will build. 56 | allow-newer: true 57 | 58 | # Override default flag values for local packages and extra-deps 59 | flags: {} 60 | 61 | # Extra package databases containing global packages 62 | extra-package-dbs: [] 63 | 64 | # Control whether we use the GHC we find on the path 65 | # system-ghc: true 66 | # 67 | # Require a specific version of stack, using version ranges 68 | # require-stack-version: -any # Default 69 | # require-stack-version: ">=1.3" 70 | # 71 | # Override the architecture used by stack, especially useful on Windows 72 | # arch: i386 73 | # arch: x86_64 74 | # 75 | # Extra directories used by stack for building 76 | # extra-include-dirs: [/path/to/dir] 77 | # extra-lib-dirs: [/path/to/dir] 78 | # 79 | # Allow a newer minor version of GHC than the snapshot specifies 80 | # compiler-check: newer-minor 81 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Internal/Request.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DataKinds #-} 2 | {-#LANGUAGE DeriveDataTypeable #-} 3 | {-#LANGUAGE DeriveFunctor #-} 4 | {-#LANGUAGE DeriveGeneric #-} 5 | {-#LANGUAGE GeneralizedNewtypeDeriving #-} 6 | {-#LANGUAGE LambdaCase #-} 7 | {-#LANGUAGE OverloadedStrings #-} 8 | {-#LANGUAGE ScopedTypeVariables #-} 9 | {-#LANGUAGE TypeFamilies #-} 10 | 11 | module Twilio.Internal.Request where 12 | 13 | import Control.Applicative 14 | import Control.Exception.Base 15 | import Control.Monad.IO.Class 16 | import Control.Monad.Trans.Class 17 | import Control.Monad.Trans.Free 18 | import qualified Data.ByteString.Lazy as LBS 19 | import Data.Text (Text) 20 | import Data.Typeable 21 | import GHC.Generics 22 | import Network.HTTP.Client 23 | import Network.HTTP.Client.TLS 24 | import Network.HTTP.Types.Status 25 | import Network.HTTP.Types.Header 26 | import Prelude hiding (head) 27 | 28 | -- | 'RequestF' represents an HTTP request and stores a continuaton for the 29 | -- eventual 'Response' to the request. 30 | newtype RequestF a = RequestF (Request, Response LBS.ByteString -> a) 31 | deriving (Functor, Generic, Typeable) 32 | 33 | -- | @'RequestT' m a@ augments an existing monad @m@ with the ability to 34 | -- perform HTTP requests for 'Resource's. 35 | newtype RequestT m a = RequestT { runRequestT :: FreeT RequestF m a } 36 | deriving (Applicative, Functor, Generic, Monad, MonadIO, MonadTrans, Typeable) 37 | 38 | class Monad m => MonadRequest m where 39 | request :: (Response LBS.ByteString -> m a) -> Request -> m a 40 | 41 | instance Monad m => MonadRequest (RequestT m) where 42 | request go r = RequestT . FreeT . return . Free $ RequestF (r, runRequestT . go) 43 | 44 | -- | A dummy interpreter 45 | {- 46 | runRequest :: MonadIO m => RequestT m a -> m a 47 | runRequest (RequestT (FreeT m)) = m >>= \case 48 | Free f -> runRequest . RequestT $ run f 49 | Pure a -> return a 50 | where 51 | run (RequestF (_, go)) = undefined 52 | -} 53 | 54 | baseURL :: Text 55 | baseURL = "https://api.twilio.com/2010-04-01" 56 | 57 | runRequest' :: (Monad m, MonadIO m) => (Text, Text) -> RequestT m a -> m a 58 | runRequest' credentials (RequestT (FreeT m)) = m >>= \case 59 | Free f -> runRequest' credentials . RequestT =<< run (return <$> f) 60 | Pure a -> return a 61 | where 62 | run (RequestF (request, go)) = do 63 | manager <- liftIO (newManager tlsManagerSettings) 64 | liftIO $ withResponse request manager $ \response -> do 65 | let status = responseStatus response 66 | if statusCode status == 204 67 | then 68 | go $ const "[]" <$> response 69 | else do 70 | let body = responseBody response 71 | body' <- LBS.fromChunks <$> brConsume body 72 | print body' 73 | go $ const body' <$> response 74 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Calls/FeedbackSummary.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | 5 | module Twilio.Calls.FeedbackSummary 6 | ( -- * Resource 7 | FeedbackSummary(..) 8 | , FeedbackSummaryIssue(..) 9 | -- * Types 10 | , FeedbackSummarySID 11 | , Issue(..) 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Monad 16 | import Data.Aeson 17 | import Data.Data 18 | import Data.Text (Text) 19 | import Data.Time.Clock 20 | import GHC.Generics 21 | 22 | import Twilio.Internal.Parser 23 | import Twilio.Types 24 | 25 | data FeedbackSummary = FeedbackSummary 26 | { sid :: FeedbackSummarySID 27 | , startDate :: !Text 28 | , endDate :: !Text 29 | , accountSID :: !AccountSID 30 | , includeSubaccounts :: !Bool 31 | , status :: !FeedbackSummaryStatus 32 | , callCount :: !Int 33 | , callFeedbackCount :: !Int 34 | , qualityScoreAverage :: !Float 35 | , qualityScoreMedian :: !Int 36 | , qualityScoreStandardDeviation :: !Int 37 | , issues :: ![FeedbackSummaryIssue] 38 | , dateCreated :: !UTCTime 39 | , dateUpdated :: !UTCTime 40 | } deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 41 | 42 | instance FromJSON FeedbackSummary where 43 | parseJSON (Object v) = FeedbackSummary 44 | <$> v .: "sid" 45 | <*> v .: "start_date" 46 | <*> v .: "end_date" 47 | <*> v .: "account_sid" 48 | <*> v .: "include_subaccounts" 49 | <*> v .: "status" 50 | <*> v .: "call_count" 51 | <*> v .: "call_feedback_count" 52 | <*> v .: "quality_score_average" 53 | <*> v .: "quality_score_median" 54 | <*> v .: "quality_score_standard_deviation" 55 | <*> v .: "issues" 56 | <*> (v .: "date_created" >>= parseDateTime) 57 | <*> (v .: "date_updated" >>= parseDateTime) 58 | parseJSON _ = mzero 59 | 60 | data FeedbackSummaryIssue = FeedbackSummaryIssue 61 | { description :: !Issue 62 | , count :: !Int 63 | , percentageOfTotalCalls :: !Float 64 | } deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 65 | 66 | instance FromJSON FeedbackSummaryIssue where 67 | parseJSON (Object v) = FeedbackSummaryIssue 68 | <$> v .: "description" 69 | <*> v .: "count" 70 | -- FIXME(mroberts): Need to parse this. 71 | <*> v .: "percentage_of_total_calls" 72 | parseJSON _ = mzero 73 | 74 | data FeedbackSummaryStatus 75 | = Queued 76 | | InProgress 77 | | Completed 78 | | Failed 79 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 80 | 81 | instance FromJSON FeedbackSummaryStatus where 82 | parseJSON (String "queued") = return Queued 83 | parseJSON (String "in-progress") = return InProgress 84 | parseJSON (String "completed") = return Completed 85 | parseJSON (String "failed") = return Failed 86 | parseJSON _ = mzero 87 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Call/Feedback.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ViewPatterns #-} 6 | 7 | module Twilio.Call.Feedback 8 | ( -- * Resource 9 | Feedback(..) 10 | , Twilio.Call.Feedback.get 11 | -- * Types 12 | , Quality(..) 13 | , Issue(..) 14 | ) where 15 | 16 | import Control.Applicative 17 | import Control.Monad 18 | import Control.Monad.Catch 19 | import Data.Aeson 20 | import Data.Data 21 | import Data.Monoid 22 | import Data.Scientific 23 | import Data.Time.Clock 24 | import GHC.Generics 25 | 26 | import Control.Monad.Twilio 27 | import Twilio.Internal.Parser 28 | import Twilio.Internal.Request 29 | import Twilio.Internal.Resource as Resource 30 | import Twilio.Types 31 | 32 | {- Resource -} 33 | 34 | -- | 'Feedback' is a subresource of a 'Call' instance resource. It represents a call quality feedback entry for a given phone call. 35 | data Feedback = Feedback 36 | { sid :: !CallSID 37 | , accountSID :: !AccountSID 38 | , qualityScore :: !Quality 39 | , issues :: ![Issue] 40 | , dateCreated :: !UTCTime 41 | , dateUpdated :: !UTCTime 42 | } deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 43 | 44 | instance FromJSON Feedback where 45 | parseJSON (Object v) = Feedback 46 | <$> v .: "sid" 47 | <*> v .: "account_sid" 48 | <*> v .: "quality_score" 49 | <*> v .: "issues" 50 | <*> (v .: "date_created" >>= parseDateTime) 51 | <*> (v .: "date_updated" >>= parseDateTime) 52 | parseJSON _ = mzero 53 | 54 | instance Get1 CallSID Feedback where 55 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 56 | ("/Calls/" <> sid <> "/Feedback.json") 57 | 58 | -- | Get a 'Call''s 'Feedback' by 'CallSID'. 59 | get :: MonadThrow m => CallSID -> TwilioT m Feedback 60 | get = Resource.get 61 | 62 | {- Types -} 63 | 64 | -- | An integer 1 to 5 quality score where 1 represents very poor call quality and 5 represents a perfect call. 65 | data Quality 66 | = Q1 -- ^ Very poor call quality 67 | | Q2 68 | | Q3 69 | | Q4 70 | | Q5 -- ^ A perfect call 71 | deriving (Bounded, Data, Enum, Eq, Generic, Ord, Read, Show, Typeable) 72 | 73 | instance ToJSON Quality where 74 | toJSON Q1 = Number $ fromRational 1 75 | toJSON Q2 = Number $ fromRational 2 76 | toJSON Q3 = Number $ fromRational 3 77 | toJSON Q4 = Number $ fromRational 4 78 | toJSON Q5 = Number $ fromRational 5 79 | 80 | instance FromJSON Quality where 81 | parseJSON (numberToMaybeInt -> Just 1) = return Q1 82 | parseJSON (numberToMaybeInt -> Just 2) = return Q2 83 | parseJSON (numberToMaybeInt -> Just 3) = return Q3 84 | parseJSON (numberToMaybeInt -> Just 4) = return Q4 85 | parseJSON (numberToMaybeInt -> Just 5) = return Q5 86 | parseJSON _ = mzero 87 | 88 | numberToMaybeInt :: Value -> Maybe Int 89 | numberToMaybeInt (Number n) = toBoundedInteger n 90 | numberToMaybeInt _ = mzero 91 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Transcription.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.Transcription 6 | ( -- * Resource 7 | Transcription(..) 8 | , Twilio.Transcription.get 9 | -- * Types 10 | , PriceUnit(..) 11 | , TranscriptionStatus(..) 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Error.Safe 16 | import Control.Monad 17 | import Control.Monad.Catch 18 | import Data.Aeson 19 | import Data.Monoid 20 | import Data.Text (Text) 21 | import Data.Time.Clock 22 | import Network.URI 23 | 24 | import Control.Monad.Twilio 25 | import Twilio.Internal.Parser 26 | import Twilio.Internal.Request 27 | import Twilio.Internal.Resource as Resource 28 | import Twilio.Types 29 | 30 | {- Resource -} 31 | 32 | data Transcription = Transcription 33 | { sid :: !TranscriptionSID 34 | , dateCreated :: !UTCTime 35 | , dateUpdated :: !UTCTime 36 | , accountSID :: !AccountSID 37 | , status :: !TranscriptionStatus 38 | , recordingSID :: !RecordingSID 39 | , duration :: !(Maybe Int) 40 | , transcriptionText :: !Text 41 | , price :: !(Maybe Double) 42 | , priceUnit :: !PriceUnit 43 | , apiVersion :: !APIVersion 44 | , uri :: !URI 45 | } deriving (Show, Eq) 46 | 47 | instance FromJSON Transcription where 48 | parseJSON (Object v) = Transcription 49 | <$> v .: "sid" 50 | <*> (v .: "date_created" >>= parseDateTime) 51 | <*> (v .: "date_updated" >>= parseDateTime) 52 | <*> v .: "account_sid" 53 | <*> v .: "status" 54 | <*> v .: "recording_sid" 55 | <*> (v .: "duration" <&> fmap readZ 56 | >>= maybeReturn') 57 | <*> v .: "transcription_text" 58 | <*> (v .: "price" <&> fmap readZ 59 | >>= maybeReturn') 60 | <*> v .: "price_unit" 61 | <*> v .: "api_version" 62 | <*> (v .: "uri" <&> parseRelativeReference 63 | >>= maybeReturn) 64 | parseJSON _ = mzero 65 | 66 | instance Get1 TranscriptionSID Transcription where 67 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 68 | ("/Transcriptions/" <> sid <> ".json") 69 | 70 | -- | Get a 'Transcription' by 'TranscriptionSID'. 71 | get :: MonadThrow m => TranscriptionSID -> TwilioT m Transcription 72 | get = Resource.get 73 | 74 | {- Types -} 75 | 76 | data TranscriptionStatus 77 | = InProgress 78 | | Completed 79 | | Failed 80 | deriving Eq 81 | 82 | instance Show TranscriptionStatus where 83 | show InProgress = "in-progress" 84 | show Completed = "completed" 85 | show Failed = "failed" 86 | 87 | instance FromJSON TranscriptionStatus where 88 | parseJSON (String "in-progress") = return InProgress 89 | parseJSON (String "completed") = return Completed 90 | parseJSON (String "failed") = return Failed 91 | parseJSON _ = mzero 92 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/IncomingPhoneNumber.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.IncomingPhoneNumber 6 | ( -- * Resource 7 | IncomingPhoneNumber(..) 8 | , Twilio.IncomingPhoneNumber.get 9 | ) where 10 | 11 | import Control.Applicative 12 | import Control.Monad 13 | import Control.Monad.Catch 14 | import Data.Aeson 15 | import Data.Monoid 16 | import Data.Text (Text) 17 | import Data.Time.Clock 18 | import Network.URI 19 | 20 | import Control.Monad.Twilio 21 | import Twilio.Internal.Parser 22 | import Twilio.Internal.Request 23 | import Twilio.Internal.Resource as Resource 24 | import Twilio.Types 25 | 26 | {- Resource -} 27 | 28 | data IncomingPhoneNumber = IncomingPhoneNumber 29 | { sid :: !PhoneNumberSID 30 | , dateCreated :: !UTCTime 31 | , dateUpdated :: !UTCTime 32 | , friendlyName :: !Text 33 | , accountSID :: !AccountSID 34 | , phoneNumber :: !Text 35 | , apiVersion :: !APIVersion 36 | , voiceCallerIDLookup :: !Bool 37 | , voiceURL :: !(Maybe Text) 38 | , voiceMethod :: !Text 39 | , voiceFallbackURL :: !(Maybe Text) 40 | , voiceFallbackMethod :: !Text 41 | , statusCallback :: !(Maybe Text) 42 | , statusCallbackMethod :: !Text 43 | , voiceApplicationSID :: !(Maybe ApplicationSID) 44 | , smsURL :: !(Maybe Text) 45 | , smsMethod :: !Text 46 | , smsFallbackURL :: !(Maybe Text) 47 | , smsFallbackMethod :: !Text 48 | , smsApplicationSID :: !(Maybe ApplicationSID) 49 | , capabilities :: !Capabilities 50 | , addressRequirements :: !AddressRequirement 51 | , uri :: !URI 52 | } deriving (Eq, Show) 53 | 54 | instance FromJSON IncomingPhoneNumber where 55 | parseJSON (Object v) = IncomingPhoneNumber 56 | <$> v .: "sid" 57 | <*> (v .: "date_created" >>= parseDateTime) 58 | <*> (v .: "date_updated" >>= parseDateTime) 59 | <*> v .: "friendly_name" 60 | <*> v .: "account_sid" 61 | <*> v .: "phone_number" 62 | <*> v .: "api_version" 63 | <*> v .: "voice_caller_id_lookup" 64 | <*> (v .: "voice_url" <&> getNonEmptyText) 65 | <*> v .: "voice_method" 66 | <*> (v .: "voice_fallback_url" <&> (join . fmap getNonEmptyText)) 67 | <*> v .: "voice_fallback_method" 68 | <*> (v .: "status_callback" <&> getNonEmptyText) 69 | <*> v .: "status_callback_method" 70 | <*> (v .: "voice_application_sid" <&> (join . fmap parseSID . getNonEmptyText)) 71 | <*> (v .: "sms_url" <&> getNonEmptyText) 72 | <*> v .: "sms_method" 73 | <*> (v .: "sms_fallback_url" <&> getNonEmptyText) 74 | <*> v .: "sms_fallback_method" 75 | <*> (v .: "sms_application_sid" <&> (join . fmap parseSID . getNonEmptyText)) 76 | <*> (v .: "capabilities" >>= parseJSON) 77 | <*> v .: "address_requirements" 78 | <*> (v .: "uri" <&> parseRelativeReference 79 | >>= maybeReturn) 80 | parseJSON _ = mzero 81 | 82 | instance Get1 PhoneNumberSID IncomingPhoneNumber where 83 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 84 | ("/IncomingPhoneNumbers/" <> sid <> ".json") 85 | 86 | -- | Get an 'IncomingPhoneNumber' by 'PhoneNumberSID'. 87 | get :: MonadThrow m => PhoneNumberSID -> TwilioT m IncomingPhoneNumber 88 | get = Resource.get 89 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Accounts.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | {-#LANGUAGE RankNTypes #-} 5 | 6 | module Twilio.Accounts 7 | ( -- * Resource 8 | Accounts(..) 9 | , Twilio.Accounts.get 10 | , Twilio.Accounts.post 11 | , createSubAccount 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Monad.Catch 16 | import Data.Aeson 17 | import Data.Maybe 18 | import Data.Text (Text) 19 | import Data.Text.Encoding 20 | 21 | import Control.Monad.Twilio 22 | import Twilio.Account 23 | import Twilio.Internal.Request 24 | import Twilio.Internal.Resource as Resource 25 | import Twilio.Types 26 | 27 | {- Resource -} 28 | 29 | data Accounts = Accounts 30 | { pagingInformation :: !PagingInformation 31 | , list :: ![Account] 32 | } deriving (Show, Eq, Ord) 33 | 34 | instance List Accounts Account where 35 | getListWrapper = wrap (Accounts . fromJust) 36 | getList = list 37 | getPlural = Const "accounts" 38 | 39 | instance FromJSON Accounts where 40 | parseJSON = parseJSONToList 41 | 42 | instance Get0 Accounts where 43 | get0 = request parseJSONFromResponse =<< makeTwilioRequest' "/Accounts.json" 44 | 45 | {- | Get 'Accounts'. 46 | 47 | For example, you can fetch the 'Accounts' resource in the 'IO' monad as follows: 48 | 49 | >module Main where 50 | > 51 | >import Control.Monad.IO.Class (liftIO) 52 | >import System.Environment (getEnv) 53 | >import Twilio.Accounts as Accounts 54 | >import Twilio.Types 55 | > 56 | >-- | Print accounts. 57 | >main :: IO () 58 | >main = runTwilio' (getEnv "ACCOUNT_SID") 59 | > (getEnv "AUTH_TOKEN") 60 | > $ Accounts.get >>= liftIO . print 61 | -} 62 | get :: MonadThrow m => TwilioT m Accounts 63 | get = Resource.get 64 | 65 | instance Post0 Account where 66 | post0 67 | = request parseJSONFromResponse =<< makeTwilioPOSTRequest' "/Accounts.json" [] 68 | 69 | instance Post1 Text Account where 70 | post1 friendlyName 71 | = request parseJSONFromResponse =<< makeTwilioPOSTRequest' "/Accounts.json" 72 | [ ("FriendlyName", encodeUtf8 friendlyName ) ] 73 | 74 | instance Post1 (Maybe Text) Account where 75 | post1 Nothing = post0 76 | post1 (Just friendlyName) = post1 friendlyName 77 | 78 | post :: MonadThrow m => Maybe Text -> TwilioT m Account 79 | post = Resource.post 80 | 81 | {- | Create a new 'Account' instance resource as a subaccount of the one used 82 | to make the request. 83 | 84 | For example, you can create a subaccount, "foo", as follows: 85 | 86 | >module Main where 87 | > 88 | >import Control.Monad.IO.Class (liftIO) 89 | >import System.Environment (getEnv) 90 | >import Twilio.Accounts (createSubAccount) 91 | >import Twilio.Types 92 | > 93 | >-- | Create and print a subaccount, "foo". 94 | >main :: IO () 95 | >main = runTwilio' (getEnv "ACCOUNT_SID") 96 | > (getEnv "AUTH_TOKEN") 97 | > $ createSubAccount (Just "foo") >>= liftIO . print 98 | -} 99 | createSubAccount :: MonadThrow m 100 | => Maybe Text -- ^ A human readable description of the new 101 | -- subaccount, up to 64 characters. Defaults 102 | -- to "SubAccount Created at {YYYY-MM-DD 103 | -- HH:MM meridian}". 104 | -> TwilioT m Account 105 | createSubAccount = Twilio.Accounts.post 106 | -------------------------------------------------------------------------------- /Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE ExtendedDefaultRules #-} 5 | {-# LANGUAGE CPP #-} 6 | {-# LANGUAGE RankNTypes #-} 7 | {-# LANGUAGE PatternGuards #-} 8 | {-# OPTIONS_GHC -fno-warn-deprecations #-} 9 | module Main where 10 | 11 | import Control.Exception.Base 12 | import Control.Lens hiding ((.=)) 13 | import Control.Monad 14 | import Data.Aeson (encode) 15 | import Data.Bool 16 | import Data.ByteString 17 | import Data.Char(toUpper) 18 | import Data.Maybe 19 | import Data.Monoid ((<>)) 20 | import qualified Data.Text as T 21 | import GHC.Types (IO (..)) 22 | import Network.HTTP.Base 23 | import Network.HTTP.Client 24 | import Network.HTTP.Types 25 | import Network.HTTP.Types.Status (statusCode) 26 | import Network.Linklater 27 | import Network.TLS 28 | import Network.Wai.Handler.Warp (run) 29 | import Network.Wreq 30 | import Prelude 31 | import Reddit 32 | import Reddit.Types.Post 33 | import Reddit.Types.SearchOptions (Order (..)) 34 | import Servant.Client (ClientEnv(ClientEnv), runClientM) 35 | import Servant.Common.Req 36 | import System.Environment (getEnv, setEnv) 37 | 38 | readSlackFile :: FilePath -> IO T.Text 39 | readSlackFile filename = 40 | T.filter (/= '\n') . T.pack <$> Prelude.readFile filename 41 | 42 | configIO :: IO Config 43 | configIO = 44 | Config <$> (readSlackFile "hook") 45 | 46 | parseText :: T.Text -> Maybe T.Text 47 | parseText text = case T.strip text of 48 | "" -> Nothing 49 | x -> Just x 50 | 51 | liftMaybe :: Maybe a -> IO a 52 | liftMaybe = maybe mzero return 53 | 54 | printPost :: Post -> T.Text 55 | printPost post = do 56 | title post <> "\n" <> (T.pack . show . created $ post) <> "\n" <> "http://reddit.com"<> permalink post <> "\n" <> "Score: " <> (T.pack . show . score $ post) 57 | 58 | --searches for Hot programming posts given input (ie haskell) 59 | findQPosts:: T.Text -> RedditT IO PostListing 60 | findQPosts c = search (Just $ R "programming") (Reddit.Options Nothing (Just 0)) Hot c 61 | 62 | --gets Reddit posts, and creates the message that will be 63 | --posted to Slack. 64 | messageOfCommandReddit :: Command -> IO Network.Linklater.Message 65 | messageOfCommandReddit (Command "redditbot" user channel (Just text)) = do 66 | query <- liftMaybe (parseText text) 67 | posts <- runRedditAnon (findQPosts query) 68 | case posts of 69 | Right posts' -> 70 | return (messageOf [FormatAt user, FormatString (T.intercalate "\n\n". Prelude.map printPost $ contents posts')]) where 71 | messageOf = 72 | FormattedMessage(EmojiIcon "gift") "redditbot" channel 73 | 74 | --calls *messageOfCommandReddit*, which actually posts the 75 | --message to Slack after running *stack build*, then *stack 76 | --exec redditbot*, and then opening up a Ngrok tunnel at the same port. 77 | redditify :: Maybe Command -> IO T.Text 78 | redditify Nothing = 79 | return "Unrecognized Slack request!" 80 | 81 | redditify(Just command) = do 82 | Prelude.putStrLn ("+ Incoming command: " <> show command) 83 | message <- (messageOfCommandReddit) command 84 | config <- configIO 85 | --putStrLn ("+ Outgoing message: " <> show (message)) 86 | case (debug, message) of 87 | (False, m) -> do 88 | _ <- say m config 89 | return "" 90 | _ -> 91 | return "" 92 | where 93 | debug = False 94 | 95 | main :: IO () 96 | main = do 97 | Prelude.putStrLn ("+ Listening on port " <> show port) 98 | run port (slashSimple redditify) 99 | where 100 | port = 3000 101 | 102 | 103 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Calls.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Calls 5 | ( -- * Resource 6 | Calls(..) 7 | , Twilio.Calls.get 8 | , Twilio.Calls.post 9 | , PostCalls(..) 10 | , IfMachine(..) 11 | ) where 12 | 13 | import Control.Applicative 14 | import Control.Monad 15 | import Control.Monad.Catch 16 | import Data.Aeson 17 | import Data.Maybe 18 | import Data.Text (Text, pack) 19 | import Data.Text.Encoding 20 | import Network.URI 21 | 22 | import Control.Monad.Twilio 23 | import Twilio.Call 24 | import Twilio.Internal.Request 25 | import Twilio.Internal.Resource as Resource 26 | import Twilio.Types 27 | 28 | {- Resource -} 29 | 30 | data Calls = Calls 31 | { callsPagingInformation :: PagingInformation 32 | , callList :: [Call] 33 | } deriving (Show, Eq) 34 | 35 | instance List Calls Call where 36 | getListWrapper = wrap (Calls . fromJust) 37 | getList = callList 38 | getPlural = Const "calls" 39 | 40 | instance FromJSON Calls where 41 | parseJSON = parseJSONToList 42 | 43 | instance Get0 Calls where 44 | get0 = request parseJSONFromResponse =<< makeTwilioRequest 45 | "/Calls.json" 46 | 47 | {- | Get 'Calls'. 48 | 49 | For example, you can fetch the 'Calls' resource in the 'IO' monad as follows: 50 | 51 | >module Main where 52 | > 53 | >import Control.Monad.IO.Class (liftIO) 54 | >import System.Environment (getEnv) 55 | >import Twilio.Calls as Calls 56 | >import Twilio.Types 57 | > 58 | >-- | Print calls. 59 | >main :: IO () 60 | >main = runTwilio' (getEnv "ACCOUNT_SID") 61 | > (getEnv "AUTH_TOKEN") 62 | > $ Calls.get >>= liftIO . print 63 | -} 64 | get :: MonadThrow m => TwilioT m Calls 65 | get = Resource.get 66 | 67 | data PostCalls = PostCalls 68 | { from :: !Text 69 | , to :: !Text 70 | , urlOrApplicationSID :: !(Either URI ApplicationSID) 71 | , method :: !(Maybe Text) 72 | , fallbackURL :: !(Maybe URI) 73 | , fallbackMethod :: !(Maybe Text) 74 | , statusCallback :: !(Maybe URI) 75 | , statusCallbackMethod :: !(Maybe Text) 76 | , sendDigits :: !(Maybe Text) 77 | , ifMachine :: !(Maybe IfMachine) 78 | , timeout :: !(Maybe Integer) 79 | , record :: !(Maybe Bool) 80 | } deriving (Show, Eq) 81 | 82 | instance Post1 PostCalls Call where 83 | post1 postCalls = request parseJSONFromResponse =<< 84 | makeTwilioPOSTRequest "/Calls.json" 85 | [ ("To", encodeUtf8 $ Twilio.Calls.to postCalls) 86 | , ("From", encodeUtf8 $ Twilio.Calls.from postCalls) 87 | , ("Url", encodeUtf8 . pack $ show url) 88 | ] 89 | where 90 | url = let Left url = urlOrApplicationSID postCalls in url 91 | 92 | instance Post3 Text Text URI Call where 93 | post3 to from url = request parseJSONFromResponse =<< 94 | makeTwilioPOSTRequest "/Calls.json" 95 | [ ("To", encodeUtf8 to) 96 | , ("From", encodeUtf8 from) 97 | , ("Url", encodeUtf8 . pack $ show url) 98 | ] 99 | 100 | post :: MonadThrow m => PostCalls -> TwilioT m Call 101 | post = Resource.post 102 | 103 | data IfMachine 104 | = Continue 105 | | Hangup 106 | deriving (Bounded, Enum, Eq, Ord, Read, Show) 107 | 108 | instance FromJSON IfMachine where 109 | parseJSON (String "Continue") = return Continue 110 | parseJSON (String "Hangup") = return Hangup 111 | parseJSON _ = mzero 112 | 113 | instance ToJSON IfMachine where 114 | toJSON Continue = String "Continue" 115 | toJSON Hangup = String "Hangup" 116 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/.travis.yml: -------------------------------------------------------------------------------- 1 | # This file has been generated -- see https://github.com/hvr/multi-ghc-travis 2 | language: c 3 | sudo: false 4 | 5 | cache: 6 | directories: 7 | - $HOME/.cabsnap 8 | - $HOME/.cabal/packages 9 | 10 | before_cache: 11 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log 12 | - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar 13 | 14 | matrix: 15 | include: 16 | - env: CABALVER=1.18 GHCVER=7.8.4 17 | compiler: ": #GHC 7.8.4" 18 | addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}} 19 | - env: CABALVER=1.22 GHCVER=7.10.1 20 | compiler: ": #GHC 7.10.1" 21 | addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1], sources: [hvr-ghc]}} 22 | - env: CABALVER=1.22 GHCVER=7.10.2 23 | compiler: ": #GHC 7.10.2" 24 | addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.2], sources: [hvr-ghc]}} 25 | - env: CABALVER=1.24 GHCVER=8.0.1 26 | compiler: ": #GHC 8.0.1" 27 | addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}} 28 | 29 | before_install: 30 | - unset CC 31 | - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH 32 | 33 | install: 34 | - cabal --version 35 | - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" 36 | - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ]; 37 | then 38 | zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz > 39 | $HOME/.cabal/packages/hackage.haskell.org/00-index.tar; 40 | fi 41 | - travis_retry cabal update -v 42 | - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config 43 | - cabal install --only-dependencies --enable-tests --enable-benchmarks --dry -v > installplan.txt 44 | - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt 45 | 46 | # check whether current requested install-plan matches cached package-db snapshot 47 | - if diff -u installplan.txt $HOME/.cabsnap/installplan.txt; 48 | then 49 | echo "cabal build-cache HIT"; 50 | rm -rfv .ghc; 51 | cp -a $HOME/.cabsnap/ghc $HOME/.ghc; 52 | cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/; 53 | else 54 | echo "cabal build-cache MISS"; 55 | rm -rf $HOME/.cabsnap; 56 | mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin; 57 | cabal install --only-dependencies --enable-tests --enable-benchmarks; 58 | fi 59 | 60 | # snapshot package-db on cache miss 61 | - if [ ! -d $HOME/.cabsnap ]; 62 | then 63 | echo "snapshotting package-db to build-cache"; 64 | mkdir $HOME/.cabsnap; 65 | cp -a $HOME/.ghc $HOME/.cabsnap/ghc; 66 | cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/; 67 | fi 68 | 69 | # Here starts the actual work to be performed for the package under test; 70 | # any command which exits with a non-zero exit code causes the build to fail. 71 | script: 72 | - if [ -f configure.ac ]; then autoreconf -i; fi 73 | - cabal configure --enable-tests --enable-benchmarks -v2 # -v2 provides useful information for debugging 74 | - cabal build # this builds all libraries and executables (including tests/benchmarks) 75 | - cabal test 76 | - cabal check 77 | - cabal sdist # tests that a source-distribution can be generated 78 | 79 | # Check that the resulting source distribution can be built & installed. 80 | # If there are no other `.tar.gz` files in `dist`, this can be even simpler: 81 | # `cabal install --force-reinstalls dist/*-*.tar.gz` 82 | - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && 83 | (cd dist && cabal install --force-reinstalls "$SRC_TGZ") 84 | 85 | after_success: 86 | - script/publish_haddock.sh 87 | 88 | # EOF 89 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Account.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverloadedStrings #-} 4 | {-#LANGUAGE ViewPatterns #-} 5 | 6 | module Twilio.Account 7 | ( -- * Resource 8 | Account(..) 9 | , AccountSID 10 | , Twilio.Account.get 11 | , suspend 12 | , unsuspend 13 | , close 14 | -- * Types 15 | , Status(..) 16 | , Type(..) 17 | ) where 18 | 19 | import Control.Applicative 20 | import Control.Monad 21 | import Control.Monad.Catch 22 | import Data.Aeson 23 | import Data.Monoid 24 | import Data.Text (Text) 25 | import Data.Time.Clock 26 | import Network.URI 27 | 28 | import Control.Monad.Twilio 29 | import Twilio.Internal.Parser 30 | import Twilio.Internal.Request 31 | import Twilio.Internal.Resource as Resource 32 | import Twilio.Types 33 | 34 | {- Resource -} 35 | 36 | data Account = Account 37 | { sid :: !AccountSID 38 | , dateCreated :: !UTCTime 39 | , dateUpdated :: !UTCTime 40 | , friendlyName :: !Text 41 | , type' :: !Type 42 | , status :: !Status 43 | , authToken :: !AuthToken 44 | , uri :: !URI 45 | , ownerAccountSID :: !(Maybe AccountSID) 46 | } deriving (Show, Eq, Ord) 47 | 48 | instance FromJSON Account where 49 | parseJSON (Object v) = Account 50 | <$> v .: "sid" 51 | <*> (v .: "date_created" >>= parseDateTime) 52 | <*> (v .: "date_updated" >>= parseDateTime) 53 | <*> v .: "friendly_name" 54 | <*> v .: "type" 55 | <*> v .: "status" 56 | <*> v .: "auth_token" 57 | <*> (v .: "uri" <&> parseRelativeReference 58 | >>= maybeReturn) 59 | <*> v .: "owner_account_sid" 60 | parseJSON _ = mzero 61 | 62 | instance Get1 AccountSID Account where 63 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest' 64 | ("/Accounts/" <> sid <> ".json") 65 | {- 66 | get1 (getSID -> sid) = 67 | let twilioRequest = makeTwilioRequest' $ "/Accounts/" ++ sid ++ ".json" 68 | in twilioRequest >>= request parseJSONFromResponse 69 | -} 70 | 71 | get :: MonadThrow m => AccountSID -> TwilioT m Account 72 | get = Resource.get 73 | 74 | -- instance Post2 AccountSID () () where 75 | -- post2 accountSID () = return () 76 | 77 | -- | Suspends a subaccount by POST-ing the parameter 'status' with the value 78 | -- 'Suspended'. 79 | suspend :: Monad m => AccountSID -> TwilioT m () 80 | suspend = undefined -- flip Resource.post () 81 | 82 | -- Reactivates a suspended subaccount by POST-ing the parameter 'status' with 83 | -- the value 'Active'. 84 | unsuspend :: Monad m => AccountSID -> TwilioT m () 85 | unsuspend = undefined -- flip Resource.post () 86 | 87 | -- Closes a subaccount by POST-ing the parameter 'status' with the value 88 | -- 'Closed'. 89 | close :: Monad m => AccountSID -> TwilioT m () 90 | close = undefined -- flip Resource.post () 91 | 92 | {- Types -} 93 | 94 | data Status 95 | = Active 96 | | Suspended 97 | | Closed 98 | deriving (Show, Read, Eq, Ord, Enum, Bounded) 99 | 100 | {- 101 | instance Show Status where 102 | show Active = "active" 103 | show Suspended = "suspended" 104 | show Closed = "closed" 105 | -} 106 | 107 | instance FromJSON Status where 108 | parseJSON (String "active") = return Active 109 | parseJSON (String "suspended") = return Suspended 110 | parseJSON (String "closed") = return Closed 111 | parseJSON _ = mzero 112 | 113 | data Type 114 | = Full 115 | | Trial 116 | deriving (Show, Read, Eq, Ord, Enum, Bounded) 117 | 118 | instance FromJSON Type where 119 | parseJSON (String "Full") = return Full 120 | parseJSON (String "Trial") = return Trial 121 | parseJSON _ = mzero 122 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/List.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE DeriveGeneric #-} 3 | {-#LANGUAGE FunctionalDependencies #-} 4 | {-#LANGUAGE OverloadedStrings #-} 5 | {-#LANGUAGE ScopedTypeVariables #-} 6 | 7 | module Twilio.Types.List 8 | ( List(..) 9 | , PagingInformation(..) 10 | , Wrapper 11 | , wrap 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Monad 16 | import Data.Aeson 17 | import Data.Aeson.Types 18 | import Data.Data 19 | import Data.Text (Text) 20 | import Debug.Trace (trace) 21 | import GHC.Generics 22 | import Network.URI 23 | 24 | (<&>) :: Functor f => f a -> (a -> b) -> f b 25 | (<&>) = flip fmap 26 | 27 | class FromJSON b => List a b | a -> b where 28 | 29 | -- | Get the 'wrap'-ed constructor of the 'List'. 30 | getListWrapper :: Wrapper (Maybe PagingInformation -> [b] -> a) 31 | 32 | {--- | The 'PagingInformation' for the 'List'. 33 | -- getPagingInformation :: a -> PagingInformation -} 34 | 35 | -- | The items in the 'List'. 36 | getList :: a -> [b] 37 | 38 | -- | The plural name for the items in the 'List'. 39 | getPlural :: Const Text (a, b) 40 | 41 | -- | Parse a 'JSON' 'Value' to an instance of the 'List'. 42 | parseJSONToList :: Value -> Parser a 43 | parseJSONToList o@(Object v) 44 | = unwrap (getListWrapper :: Wrapper (Maybe PagingInformation -> [b] -> a)) 45 | <$> maybePagingInformation 46 | <*> (v .: getConst (getPlural :: Const Text (a, b)) :: Parser [b]) 47 | where 48 | maybePagingInformation = case fromJSON o of 49 | Success pagingInformation -> return $ Just pagingInformation 50 | _ -> return Nothing 51 | parseJSONToList v = trace (show v) mzero 52 | 53 | data PagingInformation = PagingInformation 54 | { -- | The current page number. Zero-indexed, so the first page is 0. 55 | pageNumber :: !Integer 56 | -- | The total number of pages. 57 | , numberOfPages :: !(Maybe Integer) 58 | -- | How many items are in each page. 59 | , pageSize :: !Integer 60 | -- | The total number of items in the list. 61 | , total :: !(Maybe Integer) 62 | -- | The position in the overall list of the first item in this page. 63 | , start :: !Integer 64 | -- | The position in the overall list of the last item in this page. 65 | , end :: !Integer 66 | -- | The 'URI' of the current page. 67 | , pageURI :: !(Maybe URI) 68 | -- | The 'URI' for the first page of this list. 69 | , firstPageURI :: !(Maybe URI) 70 | -- | The 'URI' for the next page of this list. 71 | , nextPageURI :: !(Maybe URI) 72 | -- | The 'URI' for the previous page of this list. 73 | , previousPageURI :: !(Maybe URI) 74 | -- | The 'URI' for the last page of this list. 75 | , lastPageURI :: !(Maybe URI) 76 | } deriving (Data, Eq, Generic, Ord, Show, Typeable) 77 | 78 | instance FromJSON PagingInformation where 79 | parseJSON (Object v) 80 | = PagingInformation 81 | <$> v .: "page" 82 | <*> v .:? "num_pages" 83 | <*> v .: "page_size" 84 | <*> v .:? "total" 85 | <*> v .: "start" 86 | <*> v .: "end" 87 | <*> (v .: "uri" <&> fmap parseRelativeReference 88 | >>= maybeReturn') 89 | <*> (v .: "first_page_uri" <&> fmap parseRelativeReference 90 | >>= maybeReturn') 91 | <*> (v .: "next_page_uri" <&> fmap parseRelativeReference 92 | >>= maybeReturn') 93 | <*> (v .: "previous_page_uri" <&> fmap parseRelativeReference 94 | >>= maybeReturn') 95 | <*> (v .:? "last_page_uri" <&> fmap parseRelativeReference 96 | >>= maybeReturn') 97 | parseJSON _ = mzero 98 | 99 | maybeReturn' :: Maybe (Maybe a) -> Parser (Maybe a) 100 | maybeReturn' Nothing = return Nothing 101 | maybeReturn' (Just Nothing) = mzero 102 | maybeReturn' (Just ma) = return ma 103 | 104 | newtype Wrapper a = Wrapper { unwrap :: a } 105 | 106 | -- | 'wrap's a value so as not to break encapsulation. 107 | wrap :: a -> Wrapper a 108 | wrap = Wrapper 109 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE FlexibleContexts #-} 3 | {-#LANGUAGE LambdaCase #-} 4 | {-#LANGUAGE MultiParamTypeClasses #-} 5 | {-#LANGUAGE OverloadedStrings #-} 6 | {-#LANGUAGE Rank2Types #-} 7 | {-#LANGUAGE ScopedTypeVariables #-} 8 | 9 | module Twilio.Types 10 | ( APIVersion(..) 11 | , module X 12 | -- * Misc 13 | , makeTwilioRequest 14 | , makeTwilioRequest' 15 | , makeTwilioPOSTRequest 16 | , makeTwilioPOSTRequest' 17 | , makeTwilioDELETERequest 18 | , makeTwilioDELETERequest' 19 | ) where 20 | 21 | import Control.Exception 22 | import Control.Monad 23 | import Control.Monad.Reader.Class 24 | import Data.Aeson 25 | import qualified Data.ByteString.Char8 as C 26 | import Data.Monoid 27 | import Data.Text (Text) 28 | import qualified Data.Text as T 29 | import qualified Data.ByteString.Lazy as L 30 | import Network.HTTP.Client 31 | import Network.HTTP.Client.Internal (throwHttp) 32 | import Network.HTTP.Types 33 | 34 | import Control.Monad.Twilio 35 | import Twilio.Types.AddressRequirement as X 36 | import Twilio.Types.AuthToken as X 37 | import Twilio.Types.Capability as X 38 | import Twilio.Types.Issue as X 39 | import Twilio.Types.ISOCountryCode as X 40 | import Twilio.Types.List as X 41 | import Twilio.Types.PriceUnit as X 42 | import Twilio.Types.SID as X 43 | import Twilio.Internal.Parser 44 | import Twilio.Internal.Request 45 | 46 | data APIVersion 47 | = API_2010_04_01 48 | | API_2008_08_01 49 | deriving Eq 50 | 51 | instance Read APIVersion where 52 | readsPrec _ = \case 53 | "2010-04-01" -> return (API_2010_04_01, "") 54 | "2008-08-01" -> return (API_2008_08_01, "") 55 | _ -> mzero 56 | 57 | instance Show APIVersion where 58 | show API_2010_04_01 = "2010-04-01" 59 | show API_2008_08_01 = "2008-08-01" 60 | 61 | instance FromJSON APIVersion where 62 | parseJSON (String "2010-04-01") = return API_2010_04_01 63 | parseJSON (String "2008-08-01") = return API_2008_08_01 64 | parseJSON _ = mzero 65 | 66 | makeTwilioRequest' :: Monad m => Text -> TwilioT m Request 67 | makeTwilioRequest' suffix = do 68 | ((accountSID, authToken), _) <- ask 69 | let Just request = parseUrl . T.unpack $ baseURL <> suffix 70 | return $ applyBasicAuth (C.pack . T.unpack $ getSID accountSID) 71 | (C.pack . T.unpack $ getAuthToken authToken) request 72 | 73 | makeTwilioRequest :: Monad m => Text -> TwilioT m Request 74 | makeTwilioRequest suffix = do 75 | ((_, _), accountSID) <- ask 76 | makeTwilioRequest' $ "/Accounts/" <> getSID accountSID <> suffix 77 | 78 | makeTwilioPOSTRequest' :: Monad m 79 | => Text 80 | -> [(C.ByteString, C.ByteString)] 81 | -> TwilioT m Request 82 | makeTwilioPOSTRequest' resourceURL params = 83 | makeTwilioRequest' resourceURL <&> urlEncodedBody params 84 | 85 | makeTwilioPOSTRequest :: Monad m 86 | => Text 87 | -> [(C.ByteString, C.ByteString)] 88 | -> TwilioT m Request 89 | makeTwilioPOSTRequest resourceURL params = 90 | makeTwilioRequest resourceURL <&> urlEncodedBody params 91 | 92 | makeTwilioDELETERequest' :: Monad m 93 | => Text 94 | -> TwilioT m Request 95 | makeTwilioDELETERequest' resourceURL = 96 | makeTwilioRequest' resourceURL <&> (\req -> req { 97 | method = "DELETE", 98 | checkResponse = throwForNon204 99 | }) 100 | 101 | makeTwilioDELETERequest :: Monad m 102 | => Text 103 | -> TwilioT m Request 104 | makeTwilioDELETERequest resourceURL = 105 | makeTwilioRequest resourceURL <&> (\req -> req { 106 | method = "DELETE", 107 | checkResponse = throwForNon204 108 | }) 109 | 110 | 111 | throwForNon204 :: Request -> Response BodyReader -> IO () 112 | throwForNon204 _ resp = 113 | case responseStatus resp of 114 | Status 204 _ -> return () 115 | status -> do 116 | chunk <- brReadSome (responseBody resp) 1024 117 | throwHttp $ StatusCodeException (fmap (const ()) resp) (L.toStrict chunk) 118 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Internal/Resource.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE FlexibleInstances #-} 2 | {-#LANGUAGE MultiParamTypeClasses #-} 3 | {-#LANGUAGE OverlappingInstances #-} 4 | {-#LANGUAGE RankNTypes #-} 5 | 6 | module Twilio.Internal.Resource 7 | ( -- $about 8 | Get(..) 9 | , Get0(..) 10 | , Get1(..) 11 | , Get2(..) 12 | , Post(..) 13 | , Post0(..) 14 | , Post1(..) 15 | , Post2(..) 16 | , Post3(..) 17 | , Delete(..) 18 | , Delete1(..) 19 | , parseJSONFromResponse 20 | ) where 21 | 22 | import Control.Monad.Catch 23 | import Data.Aeson 24 | import Data.Aeson.Types 25 | import qualified Data.ByteString.Lazy as LBS 26 | import Network.HTTP.Client 27 | 28 | import Control.Monad.Twilio 29 | 30 | -- $about This module repackages functionality exposed by 'MonadRequest' into a 31 | -- set of classes that REST resources can easily consume. It also provides 32 | -- functions 'get' and 'post' that work well with type inference. 33 | 34 | -- | 'Get0' represents REST resources that support HTTP GET requests with 0 arguments. 35 | class Get0 r where 36 | get0 :: MonadThrow m => TwilioT m r 37 | 38 | -- | 'Get1' represents REST resources that support HTTP GET requests with 1 argument. 39 | class Get1 a r where 40 | get1 :: MonadThrow m => a -> TwilioT m r 41 | 42 | -- | 'Get2' represents REST resources that support HTTP GET requests with 2 arguments. 43 | class Get2 a b r where 44 | get2 :: MonadThrow m => a -> b -> TwilioT m r 45 | 46 | -- | 'Get' represents REST resources that support HTTP GET requests with any number of arguments. 47 | class Get r where 48 | get :: r 49 | 50 | -- | Instances of 'Get0' are instances of 'Get'. 51 | instance (MonadThrow m, Get0 r) => Get (TwilioT m r) where 52 | get = get0 53 | 54 | -- | Instances of 'Get1' are instances of 'Get'. 55 | instance (MonadThrow m, Get1 a r) => Get (a -> TwilioT m r) where 56 | get = get1 57 | 58 | -- | Instances of 'Get2' are instances of 'Get'. 59 | instance (MonadThrow m, Get2 a b r) => Get (a -> b -> TwilioT m r) where 60 | get = get2 61 | 62 | -- | 'Post0' represents REST resources that support HTTP POST requests with 0 arguments. 63 | class Post0 r where 64 | post0 :: MonadThrow m => TwilioT m r 65 | 66 | -- | 'Post1' represents REST resources that support HTTP POST requests with 1 argument. 67 | class Post1 a r where 68 | post1 :: MonadThrow m => a -> TwilioT m r 69 | 70 | -- | 'Post2' represents REST resources that support HTTP POST requests with 2 arguments. 71 | class Post2 a b r where 72 | post2 :: MonadThrow m => a -> b -> TwilioT m r 73 | 74 | -- | 'Post3' represents REST resources that support HTTP POST requests with 3 arguments. 75 | class Post3 a b c r where 76 | post3 :: MonadThrow m => a -> b -> c -> TwilioT m r 77 | 78 | -- | 'Post' represents REST resources that support HTTP POST requests with any number of arguments. 79 | class Post r where 80 | post :: r 81 | 82 | -- | Instances of 'Post0' are instances of 'Post'. 83 | instance (MonadThrow m, Post0 r) => Post (TwilioT m r) where 84 | post = post0 85 | 86 | -- | Instances of 'Post1' are instances of 'Post'. 87 | instance (MonadThrow m, Post1 a r) => Post (a -> TwilioT m r) where 88 | post = post1 89 | 90 | instance (MonadThrow m, Post2 a b r) => Post (a -> b -> TwilioT m r) where 91 | post = post2 92 | 93 | instance (MonadThrow m, Post3 a b c r) => Post (a -> b -> c -> TwilioT m r) where 94 | post = post3 95 | 96 | -- | 'Delete1' represents REST resources that support HTTP POST requests with 1 argument. 97 | class Delete1 a where 98 | delete1 :: MonadThrow m => a -> TwilioT m () 99 | 100 | -- | 'Delete' represents REST resources that support HTTP DELETE requests with any number of arguments. 101 | class Delete r where 102 | delete :: r 103 | 104 | -- | Instances of 'Delete1' are instances of 'Delete'. 105 | instance (MonadThrow m, Delete1 a) => Delete (a -> TwilioT m ()) where 106 | delete = delete1 107 | 108 | parseJSONFromResponse :: (FromJSON a, MonadThrow m) => Response LBS.ByteString -> m a 109 | parseJSONFromResponse response = 110 | case eitherDecode (responseBody response) >>= parseEither parseJSON of 111 | Left _ -> throwM $ UnexpectedResponse response 112 | Right a -> return a 113 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Message.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.Message 6 | ( -- * Resource 7 | Message(..) 8 | , Twilio.Message.get 9 | -- * Types 10 | , MessageDirection(..) 11 | , MessageStatus(..) 12 | ) where 13 | 14 | import Control.Applicative 15 | import Control.Monad 16 | import Control.Monad.Catch 17 | import Data.Aeson 18 | import Data.Monoid 19 | import Data.Text (Text) 20 | import Data.Time.Clock 21 | import Network.URI 22 | 23 | import Control.Monad.Twilio 24 | import Twilio.Internal.Parser 25 | import Twilio.Internal.Request 26 | import Twilio.Internal.Resource as Resource 27 | import Twilio.Types 28 | 29 | {- Resource -} 30 | 31 | data Message = Message 32 | { sid :: !MessageSID 33 | , dateCreated :: !UTCTime 34 | , dateUpdated :: !UTCTime 35 | , dateSent :: !(Maybe UTCTime) 36 | -- ^ This will be Nothing if Twilio hasn't sent the message yet. 37 | , accountSID :: !AccountSID 38 | , to :: !Text 39 | , from :: !Text 40 | , body :: !Text 41 | , status :: !MessageStatus 42 | -- , numSegments :: !Integer 43 | , direction :: !MessageDirection 44 | -- , price :: !Double 45 | , priceUnit :: !PriceUnit 46 | , apiVersion :: !APIVersion 47 | , uri :: !URI 48 | } deriving (Show, Eq) 49 | 50 | instance FromJSON Message where 51 | parseJSON (Object v) = Message 52 | <$> v .: "sid" 53 | <*> (v .: "date_created" >>= parseDateTime) 54 | <*> (v .: "date_updated" >>= parseDateTime) 55 | <*> (v .:? "date_sent" >>= parseMaybeDateTime) 56 | <*> v .: "account_sid" 57 | <*> v .: "to" 58 | <*> v .: "from" 59 | <*> v .: "body" 60 | <*> v .: "status" 61 | -- <*> (v .: "num_segments" <&> fmap safeRead 62 | -- >>= maybeReturn) 63 | <*> v .: "direction" 64 | -- <*> (v .: "price" <&> fmap safeRead 65 | -- >>= maybeReturn') 66 | <*> v .: "price_unit" 67 | <*> v .: "api_version" 68 | <*> (v .: "uri" <&> parseRelativeReference 69 | >>= maybeReturn) 70 | parseJSON _ = mzero 71 | 72 | instance Get1 MessageSID Message where 73 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 74 | ("/Messages/" <> sid <> ".json") 75 | 76 | -- | Get a 'Message' by 'MessageSID'. 77 | get :: MonadThrow m => MessageSID -> TwilioT m Message 78 | get = Resource.get 79 | 80 | {- Types -} 81 | 82 | data MessageDirection 83 | = Inbound 84 | | OutboundAPI 85 | | OutboundCall 86 | | OutboundReply 87 | deriving Eq 88 | 89 | instance Show MessageDirection where 90 | show Inbound = "inbound" 91 | show OutboundAPI = "outbound-api" 92 | show OutboundCall = "outbound-call" 93 | show OutboundReply = "outbound-reply" 94 | 95 | instance FromJSON MessageDirection where 96 | parseJSON (String "inbound") = return Inbound 97 | parseJSON (String "outbound-api") = return OutboundAPI 98 | parseJSON (String "outbound-call") = return OutboundCall 99 | parseJSON (String "outbound-reply") = return OutboundReply 100 | parseJSON _ = mzero 101 | 102 | data MessageStatus 103 | = Queued 104 | | Sending 105 | | Sent 106 | | Failed 107 | | Received 108 | | Delivered 109 | | Undelivered 110 | deriving Eq 111 | 112 | instance Show MessageStatus where 113 | show Queued = "queued" 114 | show Sending = "sending" 115 | show Sent = "sent" 116 | show Failed = "failed" 117 | show Received = "received" 118 | show Delivered = "delivered" 119 | show Undelivered = "undelivered" 120 | 121 | instance FromJSON MessageStatus where 122 | parseJSON (String "queued") = return Queued 123 | parseJSON (String "sending") = return Sending 124 | parseJSON (String "sent") = return Sent 125 | parseJSON (String "failed") = return Failed 126 | parseJSON (String "received") = return Received 127 | parseJSON (String "delivered") = return Delivered 128 | parseJSON (String "undelivered") = return Undelivered 129 | parseJSON _ = mzero 130 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Application.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | 4 | module Twilio.Application 5 | ( -- * Resource 6 | Application(..) 7 | , ApplicationSID 8 | , Twilio.Application.get 9 | -- * Types 10 | , Method(..) 11 | ) where 12 | 13 | import Control.Applicative 14 | import Control.Monad 15 | import Control.Monad.Catch 16 | import Data.Aeson 17 | import Data.Monoid 18 | import Data.Text (Text) 19 | import qualified Data.Text as T 20 | import Data.Time.Clock 21 | import Network.URI 22 | 23 | import Control.Monad.Twilio 24 | import Twilio.Internal.Parser 25 | import Twilio.Internal.Request 26 | import Twilio.Internal.Resource as Resource 27 | import Twilio.Types 28 | 29 | {- Resource -} 30 | 31 | data Application = Application 32 | { sid :: !ApplicationSID 33 | , dateCreated :: !UTCTime 34 | , dateUpdated :: !UTCTime 35 | , friendlyName :: !Text 36 | , accountSID :: !AccountSID 37 | , apiVersion :: !APIVersion 38 | , voiceURL :: !(Maybe URI) 39 | , voiceMethod :: !(Maybe Method) 40 | , voiceFallbackURL :: !(Maybe URI) 41 | , voiceFallbackMethod :: !(Maybe Method) 42 | , statusCallback :: !(Maybe URI) 43 | , statusCallbackMethod :: !(Maybe Method) 44 | , voiceCallerIDLookup :: !Bool 45 | , smsURL :: !(Maybe URI) 46 | , smsMethod :: !(Maybe Method) 47 | , smsFallbackURL :: !(Maybe URI) 48 | , smsFallbackMethod :: !(Maybe Method) 49 | , smsStatusCallback :: !(Maybe URI) 50 | , messageStatusCallback :: !(Maybe URI) 51 | , uri :: !URI 52 | } deriving (Show, Eq) 53 | 54 | instance FromJSON Application where 55 | parseJSON (Object v) = Application 56 | <$> v .: "sid" 57 | <*> (v .: "date_created" >>= parseDateTime) 58 | <*> (v .: "date_updated" >>= parseDateTime) 59 | <*> v .: "friendly_name" 60 | <*> v .: "account_sid" 61 | <*> v .: "api_version" 62 | <*> (v .: "voice_url" <&> filterEmpty 63 | <&> fmap (parseURI . T.unpack) 64 | >>= maybeReturn') 65 | <*> v .: "voice_method" 66 | <*> (v .: "voice_fallback_url" <&> filterEmpty 67 | <&> fmap (parseURI . T.unpack) 68 | >>= maybeReturn') 69 | <*> v .: "voice_fallback_method" 70 | <*> (v .: "status_callback" <&> filterEmpty 71 | <&> fmap (parseURI . T.unpack) 72 | >>= maybeReturn') 73 | <*> v .: "status_callback_method" 74 | <*> v .: "voice_caller_id_lookup" 75 | <*> (v .: "sms_url" <&> filterEmpty 76 | <&> fmap (parseURI . T.unpack) 77 | >>= maybeReturn') 78 | <*> v .: "sms_method" 79 | <*> (v .: "sms_fallback_url" <&> filterEmpty 80 | <&> fmap (parseURI . T.unpack) 81 | >>= maybeReturn') 82 | <*> v .: "sms_fallback_method" 83 | <*> (v .: "sms_status_callback" <&> fmap filterEmpty 84 | <&> fmap (fmap $ parseURI . T.unpack) 85 | >>= maybeReturn'') 86 | <*> (v .: "message_status_callback" <&> fmap filterEmpty 87 | <&> fmap (fmap $ parseURI . T.unpack) 88 | >>= maybeReturn'') 89 | <*> (v .: "uri" <&> parseRelativeReference 90 | >>= maybeReturn) 91 | parseJSON _ = mzero 92 | 93 | instance Get1 ApplicationSID Application where 94 | get1 applicationSID = request parseJSONFromResponse =<< makeTwilioRequest 95 | ("/Applications/" <> getSID applicationSID <> ".json") 96 | 97 | -- | Get an 'Application' by 'ApplicationSID'. 98 | get :: MonadThrow m => ApplicationSID -> TwilioT m Application 99 | get = Resource.get 100 | 101 | {- Types -} 102 | 103 | data Method 104 | = GET 105 | | POST 106 | deriving (Show, Eq) 107 | 108 | instance FromJSON Method where 109 | parseJSON (String "GET") = return GET 110 | parseJSON (String "POST") = return POST 111 | parseJSON _ = mzero 112 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Call.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE MultiParamTypeClasses #-} 2 | {-#LANGUAGE OverloadedStrings #-} 3 | {-#LANGUAGE ViewPatterns #-} 4 | 5 | module Twilio.Call 6 | ( -- * Resource 7 | Call(..) 8 | , CallSID 9 | , Twilio.Call.get 10 | -- * Types 11 | , AnsweredBy(..) 12 | , CallDirection(..) 13 | , CallStatus(..) 14 | ) where 15 | 16 | import Control.Applicative 17 | import Control.Error.Safe 18 | import Control.Monad 19 | import Control.Monad.Catch 20 | import Data.Aeson 21 | import Data.Monoid 22 | import Data.Text (Text) 23 | import Data.Time.Clock 24 | import Network.URI 25 | 26 | import Control.Monad.Twilio 27 | import Twilio.Internal.Parser 28 | import Twilio.Internal.Request 29 | import Twilio.Internal.Resource as Resource 30 | import Twilio.Types 31 | 32 | {- Resource -} 33 | 34 | data Call = Call 35 | { sid :: !CallSID 36 | , parentCallSID :: !(Maybe CallSID) 37 | , dateCreated :: !(Maybe UTCTime) -- "date_created" is initially null 38 | , dateUpdated :: !(Maybe UTCTime) -- "date_updated" is initially null 39 | , accountSID :: !AccountSID 40 | , to :: !(Maybe Text) 41 | , from :: !Text 42 | , phoneNumberSID :: !(Maybe PhoneNumberSID) 43 | , status :: !CallStatus 44 | , startTime :: !(Maybe UTCTime) -- "start_time" is initially null 45 | , endTime :: !(Maybe UTCTime) 46 | , duration :: !(Maybe Int) 47 | , price :: !(Maybe Double) 48 | , priceUnit :: !(Maybe PriceUnit) 49 | , direction :: !(Maybe CallDirection) 50 | , answeredBy :: !(Maybe AnsweredBy) 51 | , forwardedFrom :: !(Maybe Text) 52 | , callerName :: !(Maybe Text) 53 | , uri :: !URI 54 | , apiVersion :: !APIVersion 55 | } deriving (Show, Eq) 56 | 57 | instance FromJSON Call where 58 | parseJSON (Object v) = Call 59 | <$> v .: "sid" 60 | <*> v .: "parent_call_sid" 61 | <*> (v .: "date_created" <&> (=<<) parseDateTime) 62 | <*> (v .: "date_updated" <&> (=<<) parseDateTime) 63 | <*> v .: "account_sid" 64 | <*> (v .: "to" <&> filterEmpty) 65 | <*> v .: "from" 66 | <*> (v .: "phone_number_sid" <&> (=<<) filterEmpty 67 | <&> (=<<) parseSID) 68 | <*> v .: "status" 69 | <*> (v .: "start_time" <&> (=<<) parseDateTime) 70 | <*> (v .: "end_time" <&> (=<<) parseDateTime) 71 | <*> (v .: "duration" <&> fmap readZ 72 | >>= maybeReturn') 73 | <*> (v .: "price" <&> fmap readZ 74 | >>= maybeReturn') 75 | <*> v .: "price_unit" 76 | <*> v .: "direction" 77 | <*> v .: "answered_by" 78 | <*> v .: "forwarded_from" <&> (=<<) filterEmpty 79 | <*> v .: "caller_name" 80 | <*> (v .: "uri" <&> parseRelativeReference 81 | >>= maybeReturn) 82 | <*> v .: "api_version" 83 | parseJSON _ = mzero 84 | 85 | instance Get1 CallSID Call where 86 | get1 (getSID -> sid) = request parseJSONFromResponse =<< makeTwilioRequest 87 | ("/Calls/" <> sid <> ".json") 88 | 89 | -- | Get a 'Call' by 'CallSID'. 90 | get :: MonadThrow m => CallSID -> TwilioT m Call 91 | get = Resource.get 92 | 93 | {- Types -} 94 | 95 | data AnsweredBy 96 | = Human 97 | | Machine 98 | deriving Eq 99 | 100 | instance Show AnsweredBy where 101 | show Human = "human" 102 | show Machine = "machine" 103 | 104 | instance FromJSON AnsweredBy where 105 | parseJSON (String "human") = return Human 106 | parseJSON (String "machine") = return Machine 107 | parseJSON _ = mzero 108 | 109 | data CallDirection 110 | = Inbound 111 | | OutboundAPI 112 | | OutboundDial 113 | deriving Eq 114 | 115 | instance Show CallDirection where 116 | show Inbound = "inbound" 117 | show OutboundAPI = "outbound-api" 118 | show OutboundDial = "outbound-dial" 119 | 120 | instance FromJSON CallDirection where 121 | parseJSON (String "inbound") = return Inbound 122 | parseJSON (String "outbound-api") = return OutboundAPI 123 | parseJSON (String "outbound-dial") = return OutboundDial 124 | parseJSON _ = mzero 125 | 126 | data CallStatus 127 | = Queued 128 | | Ringing 129 | | InProgress 130 | | Canceled 131 | | Completed 132 | | Failed 133 | | Busy 134 | | NoAnswer 135 | deriving (Bounded, Enum, Eq, Ord, Read, Show) 136 | 137 | instance FromJSON CallStatus where 138 | parseJSON (String "queued") = return Queued 139 | parseJSON (String "ringing") = return Ringing 140 | parseJSON (String "in-progress") = return InProgress 141 | parseJSON (String "canceled") = return Canceled 142 | parseJSON (String "completed") = return Completed 143 | parseJSON (String "failed") = return Failed 144 | parseJSON (String "busy") = return Busy 145 | parseJSON (String "no-answer") = return NoAnswer 146 | parseJSON _ = mzero 147 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/twilio.cabal: -------------------------------------------------------------------------------- 1 | name: twilio 2 | version: 0.1.3.1 3 | synopsis: Twilio REST API library for Haskell 4 | description: This package exports bindings to Twilio's REST API (). While we would like to have a complete binding to Twilio's REST API, this package is being developed on demand. If you need something that has not been implemented yet, please send a pull request or file an issue on GitHub (). 5 | homepage: https://github.com/markandrus/twilio-haskell 6 | license: BSD3 7 | license-file: LICENSE 8 | author: Mark Andrus Roberts 9 | maintainer: markandrusroberts@gmail.com 10 | category: Network APIs, Web 11 | build-type: Simple 12 | cabal-version: >=1.8 13 | tested-with: GHC == 7.8.4, GHC == 7.10.1, GHC == 7.10.2, GHC == 8.0.1 14 | 15 | source-repository head 16 | type: git 17 | location: https://github.com/markandrus/twilio-haskell 18 | 19 | library 20 | ghc-options: -Wall -fno-warn-name-shadowing 21 | exposed-modules: Control.Monad.Twilio, 22 | Twilio, 23 | Twilio.Account, 24 | Twilio.Accounts, 25 | Twilio.Address, 26 | Twilio.Addresses, 27 | Twilio.APIKey, 28 | Twilio.APIKeys, 29 | Twilio.Application, 30 | Twilio.Applications, 31 | Twilio.AuthorizedConnectApp, 32 | Twilio.AuthorizedConnectApps, 33 | Twilio.AvailablePhoneNumber, 34 | Twilio.AvailablePhoneNumbers, 35 | Twilio.Call, 36 | Twilio.Call.Feedback, 37 | Twilio.Calls, 38 | Twilio.Calls.FeedbackSummary, 39 | Twilio.Conference, 40 | Twilio.Conference.Participant, 41 | Twilio.Conference.Participants, 42 | Twilio.Conferences, 43 | Twilio.ConnectApp, 44 | Twilio.ConnectApps, 45 | Twilio.IncomingPhoneNumber, 46 | Twilio.IncomingPhoneNumbers, 47 | Twilio.Internal.Parser, 48 | Twilio.Internal.Request, 49 | Twilio.Internal.Resource, 50 | Twilio.Message, 51 | Twilio.Message.Feedback, 52 | Twilio.Message.Media, 53 | Twilio.Message.MediaList, 54 | Twilio.Messages, 55 | Twilio.OutgoingCallerID, 56 | Twilio.OutgoingCallerIDs, 57 | Twilio.Queue, 58 | Twilio.Queue.Member, 59 | Twilio.Queue.Members, 60 | Twilio.Queues, 61 | Twilio.Recording, 62 | Twilio.Recordings, 63 | Twilio.ShortCode, 64 | Twilio.ShortCodes, 65 | Twilio.Tokens 66 | Twilio.Transcription, 67 | Twilio.Transcriptions, 68 | Twilio.Types, 69 | Twilio.Types.AuthToken, 70 | Twilio.Types.AddressRequirement, 71 | Twilio.Types.Capability, 72 | Twilio.Types.ISOCountryCode, 73 | Twilio.Types.Issue, 74 | Twilio.Types.List, 75 | Twilio.Types.SID, 76 | Twilio.Types.PriceUnit, 77 | Twilio.UsageRecord, 78 | Twilio.UsageRecords, 79 | Twilio.UsageTrigger, 80 | Twilio.UsageTriggers 81 | hs-source-dirs: src 82 | build-depends: aeson, 83 | base, 84 | bifunctors, 85 | bytestring, 86 | containers, 87 | errors, 88 | exceptions, 89 | free, 90 | http-client, 91 | http-client-tls, 92 | http-types, 93 | mtl, 94 | network-uri, 95 | old-locale, 96 | scientific, 97 | text, 98 | time, 99 | transformers, 100 | unordered-containers 101 | 102 | test-suite Tests 103 | hs-source-dirs: test 104 | main-is: Test.hs 105 | Type: exitcode-stdio-1.0 106 | build-depends: aeson, 107 | base, 108 | bytestring, 109 | Cabal, 110 | http-client, 111 | http-client-tls, 112 | network-uri, 113 | text, 114 | transformers, 115 | twilio 116 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Control/Monad/Twilio.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE DeriveDataTypeable #-} 2 | {-#LANGUAGE FlexibleInstances #-} 3 | {-#LANGUAGE MultiParamTypeClasses #-} 4 | {-#LANGUAGE RankNTypes #-} 5 | 6 | module Control.Monad.Twilio 7 | ( -- * The Twilio monad 8 | Twilio 9 | , runTwilio 10 | , runTwilio' 11 | -- * The Twilio monad transformer 12 | , TwilioT(..) 13 | , runTwilioT 14 | , runTwilioT' 15 | -- * Types 16 | , Credentials 17 | , TwilioException(..) 18 | ) where 19 | 20 | import Control.Applicative 21 | import Control.Exception 22 | import Control.Monad 23 | import Control.Monad.Catch 24 | import Control.Monad.IO.Class 25 | import Control.Monad.Reader.Class 26 | import Control.Monad.Trans.Class 27 | import Control.Monad.Trans.Free 28 | import qualified Data.ByteString.Lazy as LBS 29 | import Data.Text (Text) 30 | import qualified Data.Text as T 31 | import Data.Typeable 32 | import Network.HTTP.Client 33 | 34 | import Twilio.Internal.Request 35 | import Twilio.Types.AuthToken 36 | import Twilio.Types.SID 37 | 38 | {- The Twilio monad -} 39 | 40 | -- | This monad allows you to make authenticated REST API requests to Twilio 41 | -- using your 'AccountSID' and 'AuthToken'. 42 | type Twilio = TwilioT IO 43 | 44 | -- | Run zero or more REST API requests to Twilio. 45 | runTwilio :: Credentials -> Twilio a -> IO a 46 | runTwilio = runTwilioT 47 | 48 | {- | Parse an 'AccountSID' and 'AuthToken' before running zero or more REST API 49 | requests to Twilio. 50 | 51 | For example, you can fetch the 'Calls' resource in the 'IO' monad as follows: 52 | 53 | >module Main where 54 | > 55 | >import Control.Monad.IO.Class (liftIO) 56 | >import System.Environment (getEnv) 57 | >import Twilio.Calls as Calls 58 | >import Twilio.Types 59 | > 60 | >-- | Print calls. 61 | >main :: IO () 62 | >main = runTwilio' (getEnv "ACCOUNT_SID") 63 | > (getEnv "AUTH_TOKEN") 64 | > $ Calls.get >>= liftIO . print 65 | -} 66 | runTwilio' :: IO String -- ^ Account SID 67 | -> IO String -- ^ Authentication Token 68 | -> Twilio a 69 | -> IO a 70 | runTwilio' = runTwilioT' 71 | 72 | {- The Twilio monad transformer -} 73 | 74 | -- | This monad transformer allows you to make authenticated REST API requests 75 | -- to Twilio using your 'AccountSID' and 'AuthToken'. 76 | newtype TwilioT m a = TwilioT (Monad m => (Credentials, AccountSID) -> RequestT m a) 77 | 78 | getTwilioT :: Monad m => TwilioT m a -> (Credentials, AccountSID) -> RequestT m a 79 | getTwilioT (TwilioT f) = f 80 | 81 | instance Monad m => MonadRequest (TwilioT m) where 82 | request go r 83 | = TwilioT $ \config -> RequestT . FreeT . return . Free 84 | $ RequestF (r, \response -> runRequestT $ getTwilioT (go response) config) 85 | 86 | -- | Run zero or more REST API requests to Twilio, unwrapping the inner monad 87 | -- @m@. 88 | runTwilioT :: MonadIO m => Credentials -> TwilioT m a -> m a 89 | runTwilioT credentials@(accountSID, authToken) (TwilioT go) = do 90 | let basicAuthCredentials = (getSID accountSID, getAuthToken authToken) 91 | let requestM = go (credentials, accountSID) 92 | runRequest' basicAuthCredentials requestM 93 | 94 | -- | Parse an 'AccountSID' and 'AuthToken' before running zero or more REST API 95 | -- requests to Twilio, unwrapping the inner monad @m@. 96 | runTwilioT' :: (Functor m, MonadThrow m, MonadIO m) 97 | => m String -- ^ Account SID 98 | -> m String -- ^ Authentication Token 99 | -> TwilioT m a 100 | -> m a 101 | runTwilioT' getAccountSID getAuthToken twilio = do 102 | accountSID <- T.pack <$> getAccountSID 103 | authToken <- T.pack <$> getAuthToken 104 | case parseCredentials accountSID authToken of 105 | Nothing -> throwM InvalidCredentials 106 | Just credentials -> runTwilioT credentials twilio 107 | 108 | instance Functor (TwilioT m) where 109 | fmap f ma = TwilioT $ \credentials -> do 110 | a <- getTwilioT ma credentials 111 | return $ f a 112 | 113 | liftTwilioT :: m a -> TwilioT m a 114 | liftTwilioT m = TwilioT (const (lift m)) 115 | 116 | instance Applicative m => Applicative (TwilioT m) where 117 | pure = liftTwilioT . pure 118 | f <*> v = TwilioT $ \r -> getTwilioT f r <*> getTwilioT v r 119 | 120 | {- 121 | instance Alternative m => Alternative (TwilioT m) where 122 | empty = liftTwilioT empty 123 | m <|> n = TwilioT $ \r -> getTwilioT m r <|> getTwilioT n r 124 | -} 125 | 126 | instance Monad m => Monad (TwilioT m) where 127 | return a = TwilioT (return . const a) 128 | m >>= k = TwilioT $ \client -> do 129 | a <- getTwilioT m client 130 | getTwilioT (k a) client 131 | 132 | instance Monad m => MonadReader (Credentials, AccountSID) (TwilioT m) where 133 | ask = TwilioT return 134 | local f m = TwilioT $ getTwilioT m . f 135 | 136 | instance MonadThrow m => MonadThrow (TwilioT m) where 137 | throwM = liftTwilioT . throwM 138 | 139 | instance MonadTrans TwilioT where 140 | lift m = TwilioT $ const (lift m) 141 | 142 | instance MonadIO m => MonadIO (TwilioT m) where 143 | liftIO = lift . liftIO 144 | 145 | {- Types -} 146 | 147 | -- | Your 'AccountSID' and 'AuthToken' are used to make authenticated REST API 148 | -- requests to Twilio. 149 | type Credentials = (AccountSID, AuthToken) 150 | 151 | parseCredentials 152 | :: Text -- ^ Account SID 153 | -> Text -- ^ Authentication Token 154 | -> Maybe Credentials 155 | parseCredentials accountSID authToken = uncurry (liftM2 (,)) 156 | ( parseSID accountSID :: Maybe AccountSID 157 | , parseAuthToken authToken ) 158 | 159 | -- | The set of 'Exception's that may be thrown when attempting to make 160 | -- requests against Twilio's REST API. 161 | data TwilioException 162 | = InvalidSID !Text 163 | | InvalidAuthToken !Text 164 | | InvalidCredentials 165 | | UnexpectedResponse !(Response LBS.ByteString) 166 | deriving (Show, Eq, Typeable) 167 | 168 | instance Exception TwilioException 169 | -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/test/Test.hs: -------------------------------------------------------------------------------- 1 | {-#LANGUAGE OverloadedStrings #-} 2 | {-#LANGUAGE ScopedTypeVariables #-} 3 | 4 | module Main where 5 | 6 | import Control.Exception.Base 7 | import Control.Monad 8 | import Control.Monad.IO.Class 9 | import Data.Maybe (fromJust) 10 | import Data.Monoid 11 | import Data.Text (Text, unpack) 12 | import Network.URI 13 | import System.Environment 14 | import Twilio 15 | import Twilio.Account (Account) 16 | import Twilio.Account as Account 17 | import Twilio.Accounts (Accounts) 18 | import Twilio.Accounts as Accounts 19 | import Twilio.Addresses (Addresses) 20 | import Twilio.Addresses as Addresses 21 | import Twilio.APIKey (APIKey) 22 | import Twilio.APIKey as APIKey 23 | import Twilio.APIKeys (APIKeys) 24 | import Twilio.APIKeys as APIKeys 25 | import Twilio.Applications (Applications) 26 | import Twilio.Applications as Applications 27 | import Twilio.AuthorizedConnectApps (AuthorizedConnectApps) 28 | import Twilio.AuthorizedConnectApps as AuthorizedConnectApps 29 | import Twilio.AvailablePhoneNumbers (AvailablePhoneNumbers) 30 | import Twilio.AvailablePhoneNumbers as AvailablePhoneNumbers 31 | import Twilio.Calls (Calls) 32 | import Twilio.Calls as Calls 33 | import Twilio.Call (Call) 34 | import Twilio.Call as Call 35 | import Twilio.ConnectApps (ConnectApps) 36 | import Twilio.ConnectApps as ConnectApps 37 | import Twilio.IncomingPhoneNumbers (IncomingPhoneNumbers) 38 | import Twilio.IncomingPhoneNumbers as IncomingPhoneNumbers 39 | import Twilio.Messages (Messages) 40 | import Twilio.Messages as Messages 41 | import Twilio.Message (Message) 42 | import Twilio.Message as Message 43 | import Twilio.OutgoingCallerIDs (OutgoingCallerIDs) 44 | import Twilio.OutgoingCallerIDs as OutgoingCallerIDs 45 | import Twilio.Queues (Queues) 46 | import Twilio.Queues as Queues 47 | import Twilio.Queue (Queue) 48 | import Twilio.Queue as Queue 49 | import Twilio.Recordings (Recordings) 50 | import Twilio.Recordings as Recordings 51 | import Twilio.ShortCode (ShortCode) 52 | import Twilio.ShortCode as ShortCode 53 | import Twilio.ShortCodes (ShortCodes) 54 | import Twilio.ShortCodes as ShortCodes 55 | import Twilio.Tokens (Token) 56 | import Twilio.Tokens as Tokens 57 | import Twilio.Transcriptions (Transcriptions) 58 | import Twilio.Transcriptions as Transcriptions 59 | import Twilio.UsageRecords (UsageRecords) 60 | import Twilio.UsageRecords as UsageRecords 61 | import Twilio.UsageTriggers (UsageTriggers) 62 | import Twilio.UsageTriggers as UsageTriggers 63 | 64 | import Twilio.Types.SID (parseSID) 65 | import Twilio.Internal.Resource (post) 66 | 67 | main :: IO () 68 | main = runTwilio' (getEnv "ACCOUNT_SID") 69 | (getEnv "AUTH_TOKEN") $ do 70 | -- Test GET 71 | sequence_ 72 | [ {- Accounts.get >>= liftIO . print 73 | , Addresses.get >>= liftIO . print 74 | , Applications.get >>= liftIO . print 75 | , AuthorizedConnectApps.get >>= liftIO . print 76 | , AvailablePhoneNumbers.get US >>= liftIO . print 77 | , Calls.get >>= liftIO . print 78 | , ConnectApps.get >>= liftIO . print 79 | , IncomingPhoneNumbers.get >>= liftIO . print 80 | , Messages.get >>= liftIO . print 81 | , OutgoingCallerIDs.get >>= liftIO . print 82 | , -} Queues.get >>= liftIO . print 83 | {- , Recordings.get >>= liftIO . print 84 | , Transcriptions.get >>= liftIO . print 85 | , UsageRecords.get >>= liftIO . print 86 | , UsageTriggers.get >>= liftIO . print -} ] 87 | 88 | -- account { sid = accountSID } <- testPOSTAccounts 89 | accounts <- testGETAccounts 90 | -- testGETAccount accountSID 91 | 92 | apiKeys <- testGETAPIKeys 93 | 94 | Call { Call.sid = callSID } <- testPOSTCalls 95 | calls <- testGETCalls 96 | testGETCall callSID 97 | 98 | Message { Message.sid = messageSID } <- testPOSTMessages 99 | messages <- testGETMessages 100 | testGETMessage messageSID 101 | 102 | Queue { Queue.sid = queueSID } <- testPOSTQueues 103 | queues <- testGETQueues 104 | testGETQueue queueSID 105 | testDELETEQueue queueSID 106 | 107 | shortCodes <- testGETShortCodes 108 | 109 | testPOSTTokens 110 | 111 | return () 112 | 113 | {- Accounts -} 114 | 115 | testPOSTAccounts :: Twilio Account 116 | testPOSTAccounts = do 117 | liftIO $ putStrLn "POST /Accounts" 118 | account <- Accounts.post Nothing 119 | liftIO $ print account 120 | return account 121 | 122 | testGETAccounts :: Twilio Accounts 123 | testGETAccounts = do 124 | liftIO $ putStrLn "GET /Accounts" 125 | accounts <- Accounts.get 126 | liftIO $ print accounts 127 | return accounts 128 | 129 | {- Account -} 130 | 131 | testGETAccount :: AccountSID -> Twilio Account 132 | testGETAccount accountSID = do 133 | liftIO . putStrLn . unpack $ "GET /Accounts/" <> getSID accountSID 134 | account <- Account.get accountSID 135 | liftIO $ print account 136 | return account 137 | 138 | {- Api Keys -} 139 | 140 | testGETAPIKeys :: Twilio APIKeys 141 | testGETAPIKeys = do 142 | liftIO $ putStrLn "GET /Keys" 143 | apiKeys <- APIKeys.get 144 | liftIO $ print apiKeys 145 | return apiKeys 146 | 147 | {- Api Key -} 148 | 149 | testGETAPIKey :: APIKeySID -> Twilio APIKey 150 | testGETAPIKey apiKeySID = do 151 | liftIO . putStrLn . unpack $ "GET /Keys/" <> getSID apiKeySID 152 | apiKey <- APIKey.get apiKeySID 153 | liftIO $ print apiKey 154 | return apiKey 155 | 156 | {- Calls -} 157 | 158 | testPOSTCalls :: Twilio Call 159 | testPOSTCalls = do 160 | liftIO $ putStrLn "POST /Calls" 161 | let Just url = parseAbsoluteURI "https://demo.twilio.com/welcome/voice/" 162 | (call :: Call) <- Twilio.Internal.Resource.post 163 | ("+14158059869" :: Text) ("+14158059869" :: Text) url 164 | liftIO $ print call 165 | return call 166 | 167 | testGETCalls :: Twilio Calls 168 | testGETCalls = do 169 | liftIO $ putStrLn "GET /Calls" 170 | calls <- Calls.get 171 | liftIO $ print calls 172 | return calls 173 | 174 | {- Call -} 175 | 176 | testGETCall :: CallSID -> Twilio Call 177 | testGETCall callSID = do 178 | liftIO . putStrLn . unpack $ "GET /Calls/" <> getSID callSID 179 | call <- Call.get callSID 180 | liftIO $ print call 181 | return call 182 | 183 | {- Messages -} 184 | 185 | testGETMessages :: Twilio Messages 186 | testGETMessages = do 187 | liftIO $ putStrLn "GET /Messages" 188 | messages <- Messages.get 189 | liftIO $ print messages 190 | return messages 191 | 192 | testPOSTMessages :: Twilio Message 193 | testPOSTMessages = do 194 | liftIO $ putStrLn "POST /Messages" 195 | let body = PostMessage "+14158059869" "+14158059869" "Hello" 196 | message <- Messages.post body 197 | liftIO $ print message 198 | return message 199 | 200 | {- Message -} 201 | 202 | testGETMessage :: MessageSID -> Twilio Message 203 | testGETMessage messageSID = do 204 | liftIO . putStrLn . unpack $ "GET /Message/" <> getSID messageSID 205 | message <- Message.get messageSID 206 | liftIO $ print message 207 | return message 208 | 209 | {- Queues -} 210 | 211 | testPOSTQueues :: Twilio Queue 212 | testPOSTQueues = do 213 | liftIO $ putStrLn "POST /Queues" 214 | let body = PostQueues (Just "Some Queue") (Just 3) 215 | queue <- Queues.post body 216 | liftIO $ print queue 217 | return queue 218 | 219 | testGETQueues :: Twilio Queues 220 | testGETQueues = do 221 | liftIO $ putStrLn "GET /Queues" 222 | queues <- Queues.get 223 | liftIO $ print queues 224 | return queues 225 | 226 | {- Queue -} 227 | 228 | testGETQueue :: QueueSID -> Twilio Queue 229 | testGETQueue queueSID = do 230 | liftIO . putStrLn . unpack $ "GET /Queues/" <> getSID queueSID 231 | queue <- Queue.get queueSID 232 | liftIO $ print queue 233 | return queue 234 | 235 | testDELETEQueue :: QueueSID -> Twilio () 236 | testDELETEQueue queueSID = do 237 | liftIO . putStrLn . unpack $ "DELETE /Queues/" <> getSID queueSID 238 | Queue.delete queueSID 239 | 240 | {- Short Codes -} 241 | 242 | testGETShortCodes :: Twilio ShortCodes 243 | testGETShortCodes = do 244 | liftIO $ putStrLn "GET /ShortCodes" 245 | queues <- ShortCodes.get 246 | liftIO $ print queues 247 | return queues 248 | 249 | {- Short Code -} 250 | 251 | testGETShortCode :: ShortCodeSID -> Twilio ShortCode 252 | testGETShortCode shortCodeSID = do 253 | liftIO . putStrLn . unpack $ "GET /ShortCodes/" <> getSID shortCodeSID 254 | queue <- ShortCode.get shortCodeSID 255 | liftIO $ print queue 256 | return queue 257 | 258 | {- Tokens -} 259 | 260 | testPOSTTokens :: Twilio Token 261 | testPOSTTokens = do 262 | liftIO $ putStrLn "POST /Tokens" 263 | token <- Tokens.post Nothing 264 | liftIO $ print token 265 | return token 266 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | Do you like [Reddit](https://reddit.com) ? Do you like bots? If you answered yes to one, both, or neither of those, then you are in luck. This post will go over how to build a Reddit Slack bot in Haskell. 2 | 3 | This is the second Haskell post here on the [Twilio blog](https://twilio.com/blog). So if you haven’t read the first one on setting up your Haskell developer environment, check that out [here](https://www.twilio.com/blog/2017/07/setting-up-your-haskell-developer-environment-with-twilio.html). 4 | 5 | ## What are Slash Commands? 6 | According to the [Slack API site](https://api.slack.com/slash-commands), messages beginning with “/” are treated differently from other messages: they “enable Slack users to interact with your app directly from Slack” and all send their messages “…to the configured external URL via HTTP POST.” 7 | 8 | In this post, we’ll build a custom slash command that shares a trending programming post from Reddit about whatever you pass it as a command. For example, if you typed */redditbot python*, it would return an article about Python. 9 | 10 | ## What We’ll be Using 11 | - Access to a Slack team that allows you to install apps 12 | - [Ngrok](https://ngrok.com) 13 | - [Linklater API](https://github.com/hlian/linklater) to build Slack bots in Haskell 14 | - [Reddit API](https://github.com/intolerable/reddit) to get Reddit posts 15 | 16 | ## Cloning the GitHub repo 17 | Get started by cloning the repo [here](https://github.com/elizabethsiegle/reddit-slack-bot) by entering the following in your terminal: 18 | 19 | > git clone git@github.com:elizabethsiegle/reddit-slack-bot.git 20 | 21 | > cd reddit-slack-bot 22 | 23 | This keeps things simple because otherwise, so many package dependencies would clash and turn into [cabal hell](https://www.reddit.com/r/haskell/comments/2al3vx/how_do_you_avoid_the_cabal_hell/). 24 | 25 | Next, fire up another terminal, **cd** 26 | into the directory you cloned the repo into, and let’s open up a Ngrok tunnel. 27 | 28 | 29 | > ngrok http 3000 30 | 31 | 32 | We need that location part to pull from the *move-to-stack* fork of Twilio-Haskell which works with the dependencies of all the other packages. 33 | 34 | ## Get Started with Slack Apps 35 | First, let’s go to https://api.slack.com. Then, click *Your Apps* in the top right-hand corner. You should see a page like the one below, and then click *Create New App*. 36 | 37 | 38 | Pick a name for your bot and pick one of your Slack channels to deploy the bot to. Many groups now have a channel dedicated to testing bots. That’s one channel you could use. You should see something like this image below, which asks for your app name and a team you’re in (like a hackathon you were in, a class you took, a team you were on, or your company): 39 | 40 | 41 | Click *Create App*, and then under features on the left-hand side, click on *Webhooks*. Make sure *Activate Incoming Webhooks* is on, and then scroll down and click *Add New Webhook to Team*. 42 | 43 | 44 | You should then see a page asking to confirm your identity. Pick the channel you wish to post to, and click *Authorize*. 45 | 46 | 47 | Copy this webhook URL and paste it into a new file in our project called *hook*. 48 | 49 | Sweet! Now, again on the left column of your app’s page, under *Features* go to *Slash Commands*. Click *Create New Command*. 50 | 51 | Fill in the text boxes above however you wish. The slash command */redditbot* will be followed by a word that is searched for on Reddit. The bot will search Reddit and return a trending post about the word you searched for. Let’s check if the slash command registered. Save your project, open Slack, go to the team and channel you used above, and try your slash command. 52 | 53 | 54 | If you don’t see the command auto-complete, then go back to *Create New Command*, check each step, and try again. 55 | 56 | ## Haskell Code: Building the Slack Bot 57 | Now it’s time to examine some code. You will see a lot of packages and libraries imported into our project at the top of *Main.hs*. Let’s go over the functions our project already has. 58 | 59 | First, we have *readSlackFile* which takes in our hook file and formats it into a type we want–in this case, one of *IO Text*. We need to read this file to send our message on Slack. Then *configIO* actually calls the function. 60 | 61 | > readSlackFile :: FilePath -> IO Text 62 | 63 | > readSlackFile filename = 64 | T.filter (/= '\n') . T.pack <$> Prelude.readFile filename 65 | 66 | > configIO :: IO Config 67 | 68 | > configIO = 69 | Config <$> (readSlackFile "hook") 70 | 71 | Then we have *parseText* and *liftMaybe* check that there is a word to search following the slash command. Whatever parseText returns is passed to liftMaybe to return the topic we want to search Reddit for. Our printPost function formats the link to display in the message. 72 | 73 | 74 | > parseText :: Text -> Maybe Text 75 | 76 | > parseText text = case T.strip text of 77 | "" -> Nothing 78 | x -> Just x 79 | 80 | > liftMaybe :: Maybe a -> IO a 81 | 82 | > liftMaybe = maybe mzero return 83 | 84 | > printPost :: Post -> T.Text 85 | 86 | > printPost post = do 87 | title post <> "\n" <> (T.pack . show . created $ post) <> "\n" <> "http://reddit.com"<> permalink post <> "\n" <> "Score: " <> (T.pack . show . score $ post) 88 | 89 | Next, we have *findQPosts* which takes in the query we return from *liftMaybe* and passes that to *runRedditAnon* from the Reddit API, which gets all the posts from the front page of Reddit. *FindQPosts* searches those for trending posts on a certain topic (in this case, programming). 90 | 91 | 92 | > findQPosts:: Text -> RedditT IO PostListing 93 | > findQPosts c = search (Just $ R "programming") (Reddit.Options Nothing (Just 0)) Hot c 94 | 95 | Now we need to get Reddit posts and create the message that will be posted to Slack. We do this by calling all the functions we just made above. We also use some of Linklater’s functions to check the command and its passed text, search Reddit for the passed text, and format the information as a message for the Slack channel you configured. The returned Slack message @’s your Slack username in the beginning, too. 96 | 97 | > messageOfCommandReddit :: Command -> IO Network.Linklater.Message 98 | 99 | > messageOfCommandReddit (Command "reddit" user channel (Just text)) = do 100 | 101 | > query <- liftMaybe (parseText text) 102 | 103 | > posts <- runRedditAnon (findQPosts query) 104 | 105 | > case posts of 106 | 107 | > Right posts' -\> 108 | 109 | > return (messageOf [FormatAt user, FormatString (T.intercalate "\n\n". Prelude.map 110 | 111 | > printPost $ contents posts')]) where 112 | 113 | > messageOf = 114 | 115 | > FormattedMessage(EmojiIcon "gift") "redditbot" channel 116 | 117 | This is actually called in *messageOfCommandReddit* which checks if there’s no parameter passed. In that case, we post “Unrecognized Slack request!” 118 | 119 | > redditify :: Maybe Command -> IO Text 120 | 121 | > redditify Nothing = 122 | 123 | > return "Unrecognized Slack request!" 124 | 125 | > redditify(Just command) = do 126 | 127 | > Prelude.putStrLn ("+ Incoming command: " <> show command) 128 | 129 | > message <- (messageOfCommandReddit) command 130 | 131 | > config <- configIO 132 | > case (debug, message) of 133 | 134 | > (False, m) -\> do 135 | 136 | > _ <- say m config 137 | 138 | > return "" 139 | 140 | > _ -\> 141 | 142 | > return "" 143 | 144 | > where 145 | 146 | > debug = False 147 | 148 | Lastly, our main function uses Linklater’s *slashSimple* method to run on that same port you also call with ngrok. It prints a message out upon successfully running. 149 | 150 | > main :: IO () 151 | 152 | > main = do 153 | 154 | > Prelude.putStrLn ("+ Listening on port " <> show port) 155 | 156 | > run port (slashSimple redditify) 157 | 158 | > where 159 | 160 | > port = 3000 161 | 162 | To run, go back to your current directory in the terminal and type *stack build*. This could take some time. So let’s check out Reddit or Hacker News in the meantime. 163 | 164 | 165 | Once that’s complete, type *stack exec redditbot* on the command line in our current directory. In the Slack channel that you configured to handle apps, type in */redditbot ____* where you fill in the underscore with a programming-related topic, like Python, Swift, or Haskell. 166 | 167 | 168 | If you get a **404_client_error**, then check your ngrok connection and tunnel. It should be open to 3000. If you get a **500_service_error**, then check that the command that is being expected in *messageOfCommandReddit* is the same command that is expected in your Slack app on the Slack website (in this post, it’s “/redditbot”). 169 | 170 | If you see a pop-up asking if you want your application to accept incoming network connections, click “allow”. 171 | 172 | 173 | ## Conclusion 174 | Yes, building Slack bots in Haskell is really that easy! Some Haskell APIs that you could use for another bot (or just in general) include: 175 | 176 | - [Twilio](https://twilio.com) 177 | - [Clarifai](https://clarifai.com) 178 | - [Hacker News](https://github.com/dmjio/hackernews) 179 | - [Google Calendar](https://github.com/georgeee/haskell-google-calendar-api) 180 | - [Stripe](https://github.com/dmjio/stripe) 181 | - [GitHub](https://github.com/phadej/github) 182 | - [Giphy](https://github.com/passy/giphy-api) 183 | - [Yahoo Finance](https://github.com/cdepillabout/yahoo-finance-api) 184 | Questions? Comments? Find me online: 185 | 186 | Email: lsiegle@twilio.com 187 | 188 | Twitter: [@lizziepika](https://twitter.com/lizziepika) 189 | 190 | GitHub: [elizabethsiegle](https://github.com/elizabethsiegle) -------------------------------------------------------------------------------- /twilio-haskell-move-to-stack/src/Twilio/Types/SID.hs: -------------------------------------------------------------------------------- 1 | {-#OPTIONS_GHC -fno-warn-unused-binds #-} 2 | {-#LANGUAGE DefaultSignatures #-} 3 | {-#LANGUAGE DeriveGeneric #-} 4 | {-#LANGUAGE DeriveDataTypeable #-} 5 | {-#LANGUAGE FlexibleContexts #-} 6 | {-#LANGUAGE FlexibleInstances #-} 7 | {-#LANGUAGE GeneralizedNewtypeDeriving #-} 8 | {-#LANGUAGE ScopedTypeVariables #-} 9 | {-#LANGUAGE TypeSynonymInstances #-} 10 | 11 | module Twilio.Types.SID 12 | ( -- * String Identifier (SID) 13 | SID(getSID, parseSID) 14 | -- ** Instances 15 | , AccountSID 16 | , AddressSID 17 | , APIKeySID 18 | , ApplicationSID 19 | , CallSID 20 | , ConferenceSID 21 | , ConnectAppSID 22 | , CredentialSID 23 | , CredentialListSID 24 | , DomainSID 25 | , FeedbackSummarySID 26 | , IPAccessControlListSID 27 | , IPAddressSID 28 | , MediaSID 29 | , MessageSID 30 | , PhoneNumberSID 31 | , QueueSID 32 | , RecordingSID 33 | , ShortCodeSID 34 | , TranscriptionSID 35 | , UsageTriggerSID 36 | ) where 37 | 38 | import Control.Monad 39 | import Control.Applicative 40 | import Data.Aeson 41 | import Data.Data 42 | import Data.Bifunctor.Flip 43 | import Data.Text (Text) 44 | import qualified Data.Text as T 45 | import GHC.Generics 46 | 47 | {- Account SID -} 48 | 49 | newtype AccountSID = AccountSID { getAccountSID :: Text } 50 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 51 | 52 | instance SID AccountSID where 53 | getPrefix = Const ('A', 'C') 54 | 55 | instance FromJSON AccountSID where 56 | parseJSON = parseSIDFromJSON 57 | 58 | instance ToJSON AccountSID where 59 | toJSON = sidToJSON 60 | 61 | {- Address SID -} 62 | 63 | newtype AddressSID = AddressSID { getAddressSID :: Text } 64 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 65 | 66 | instance SID AddressSID where 67 | getPrefix = Const ('A', 'D') 68 | 69 | instance FromJSON AddressSID where 70 | parseJSON = parseSIDFromJSON 71 | 72 | instance ToJSON AddressSID where 73 | toJSON = sidToJSON 74 | 75 | {- Api Key SID -} 76 | 77 | newtype APIKeySID = APIKeySID { getAPIKeySID :: Text } 78 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 79 | 80 | instance SID APIKeySID where 81 | getPrefix = Const ('S', 'K') 82 | 83 | instance FromJSON APIKeySID where 84 | parseJSON = parseSIDFromJSON 85 | 86 | instance ToJSON APIKeySID where 87 | toJSON = sidToJSON 88 | 89 | {- Application SID -} 90 | 91 | newtype ApplicationSID = ApplicationSID { getApplicationSID :: Text } 92 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 93 | 94 | instance SID ApplicationSID where 95 | getPrefix = Const ('A', 'P') 96 | 97 | instance FromJSON ApplicationSID where 98 | parseJSON = parseSIDFromJSON 99 | 100 | instance ToJSON ApplicationSID where 101 | toJSON = sidToJSON 102 | 103 | {- Call SID -} 104 | 105 | newtype CallSID = CallSID { getCallSID :: Text } 106 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 107 | 108 | instance SID CallSID where 109 | getPrefix = Const ('C', 'A') 110 | 111 | instance FromJSON CallSID where 112 | parseJSON = parseSIDFromJSON 113 | 114 | instance ToJSON CallSID where 115 | toJSON = sidToJSON 116 | 117 | {- Conference SID -} 118 | 119 | newtype ConferenceSID = ConferenceSID { getConferenceSID :: Text } 120 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 121 | 122 | instance SID ConferenceSID where 123 | getPrefix = Const ('C', 'O') 124 | 125 | instance FromJSON ConferenceSID where 126 | parseJSON = parseSIDFromJSON 127 | 128 | instance ToJSON ConferenceSID where 129 | toJSON = sidToJSON 130 | 131 | {- Connect App SID -} 132 | 133 | newtype ConnectAppSID = ConnectAppSID { getConnectAppSID :: Text } 134 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 135 | 136 | instance SID ConnectAppSID where 137 | getPrefix = Const ('C', 'N') 138 | 139 | instance FromJSON ConnectAppSID where 140 | parseJSON = parseSIDFromJSON 141 | 142 | instance ToJSON ConnectAppSID where 143 | toJSON = sidToJSON 144 | 145 | {- Credential SID -} 146 | 147 | newtype CredentialSID = CredentialSID { getCredentialSID :: Text } 148 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 149 | 150 | instance SID CredentialSID where 151 | getPrefix = Const ('S', 'C') 152 | 153 | instance FromJSON CredentialSID where 154 | parseJSON = parseSIDFromJSON 155 | 156 | instance ToJSON CredentialSID where 157 | toJSON = sidToJSON 158 | 159 | {- Credential List SID -} 160 | 161 | newtype CredentialListSID = CredentialListSID { getCredentialListSID :: Text } 162 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 163 | 164 | instance SID CredentialListSID where 165 | getPrefix = Const ('C', 'L') 166 | 167 | instance FromJSON CredentialListSID where 168 | parseJSON = parseSIDFromJSON 169 | 170 | instance ToJSON CredentialListSID where 171 | toJSON = sidToJSON 172 | 173 | {- Domain SID -} 174 | 175 | newtype DomainSID = DomainSID { getDomainSID :: Text } 176 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 177 | 178 | instance SID DomainSID where 179 | getPrefix = Const ('S', 'D') 180 | 181 | instance FromJSON DomainSID where 182 | parseJSON = parseSIDFromJSON 183 | 184 | instance ToJSON DomainSID where 185 | toJSON = sidToJSON 186 | 187 | {- Feedback Summary SID -} 188 | 189 | newtype FeedbackSummarySID = FeedbackSummarySID { getFeedbackSummarySID :: Text } 190 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 191 | 192 | instance SID FeedbackSummarySID where 193 | getPrefix = Const ('F', 'S') 194 | 195 | instance FromJSON FeedbackSummarySID where 196 | parseJSON = parseSIDFromJSON 197 | 198 | instance ToJSON FeedbackSummarySID where 199 | toJSON = sidToJSON 200 | 201 | {- IP Access Control List -} 202 | 203 | newtype IPAccessControlListSID = IPAccessControlListSID { getIPAccessControlListSID :: Text } 204 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 205 | 206 | instance SID IPAccessControlListSID where 207 | getPrefix = Const ('A', 'L') 208 | 209 | instance FromJSON IPAccessControlListSID where 210 | parseJSON = parseSIDFromJSON 211 | 212 | instance ToJSON IPAccessControlListSID where 213 | toJSON = sidToJSON 214 | 215 | {- IP Address -} 216 | 217 | newtype IPAddressSID = IPAddressSID { getIPAddressSID :: Text } 218 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 219 | 220 | instance SID IPAddressSID where 221 | getPrefix = Const ('I', 'P') 222 | 223 | instance FromJSON IPAddressSID where 224 | parseJSON = parseSIDFromJSON 225 | 226 | instance ToJSON IPAddressSID where 227 | toJSON = sidToJSON 228 | 229 | {- Media SID -} 230 | 231 | newtype MediaSID = MediaSID { getMediaSID :: Text } 232 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 233 | 234 | instance SID MediaSID where 235 | getPrefix = Const ('M', 'E') 236 | 237 | instance FromJSON MediaSID where 238 | parseJSON = parseSIDFromJSON 239 | 240 | instance ToJSON MediaSID where 241 | toJSON = sidToJSON 242 | 243 | {- Message SID -} 244 | 245 | newtype MessageSID = MessageSID { getMessageSID :: Text } 246 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 247 | 248 | instance SID MessageSID where 249 | getPrefix = Const ('S', 'M') 250 | 251 | instance FromJSON MessageSID where 252 | parseJSON = parseSIDFromJSON 253 | 254 | instance ToJSON MessageSID where 255 | toJSON = sidToJSON 256 | 257 | {- Phone Number SID -} 258 | 259 | newtype PhoneNumberSID = PhoneNumberSID { getPhoneNumberSID :: Text } 260 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 261 | 262 | instance SID PhoneNumberSID where 263 | getPrefix = Const ('P', 'N') 264 | 265 | instance FromJSON PhoneNumberSID where 266 | parseJSON = parseSIDFromJSON 267 | 268 | instance ToJSON PhoneNumberSID where 269 | toJSON = sidToJSON 270 | 271 | {- Queue SID -} 272 | 273 | newtype QueueSID = QueueSID { getQueueSID :: Text } 274 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 275 | 276 | instance SID QueueSID where 277 | getPrefix = Const ('Q', 'U') 278 | 279 | instance FromJSON QueueSID where 280 | parseJSON = parseSIDFromJSON 281 | 282 | instance ToJSON QueueSID where 283 | toJSON = sidToJSON 284 | 285 | {- Recording SID -} 286 | 287 | newtype RecordingSID = RecordingSID { getRecordingSID :: Text } 288 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 289 | 290 | instance SID RecordingSID where 291 | getPrefix = Const ('R', 'E') 292 | 293 | instance FromJSON RecordingSID where 294 | parseJSON = parseSIDFromJSON 295 | 296 | instance ToJSON RecordingSID where 297 | toJSON = sidToJSON 298 | 299 | {- Short Code SID -} 300 | 301 | newtype ShortCodeSID = ShortCodeSID { getShortCodeSID :: Text } 302 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 303 | 304 | instance SID ShortCodeSID where 305 | getPrefix = Const ('S', 'C') 306 | 307 | instance FromJSON ShortCodeSID where 308 | parseJSON = parseSIDFromJSON 309 | 310 | instance ToJSON ShortCodeSID where 311 | toJSON = sidToJSON 312 | 313 | {- Transcription SID -} 314 | 315 | newtype TranscriptionSID = TranscriptionSID { getTranscriptionSID :: Text } 316 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 317 | 318 | instance SID TranscriptionSID where 319 | getPrefix = Const ('T', 'R') 320 | 321 | instance FromJSON TranscriptionSID where 322 | parseJSON = parseSIDFromJSON 323 | 324 | instance ToJSON TranscriptionSID where 325 | toJSON = sidToJSON 326 | 327 | {- Usage Trigger SID -} 328 | 329 | newtype UsageTriggerSID = UsageTriggerSID { getUsageTriggerSID :: Text } 330 | deriving (Data, Eq, Generic, Ord, Read, Show, Typeable) 331 | 332 | instance SID UsageTriggerSID where 333 | getPrefix = Const ('U', 'T') 334 | 335 | instance FromJSON UsageTriggerSID where 336 | parseJSON = parseSIDFromJSON 337 | 338 | instance ToJSON UsageTriggerSID where 339 | toJSON = sidToJSON 340 | 341 | parseSID' :: (MonadPlus m, SID s) => Text -> Const (m s) s 342 | parseSID' sid = 343 | case T.unpack sid of 344 | a:b:_ -> runFlip $ (\ab' -> if (a, b) == ab' then return (makeSID sid) else mzero) 345 | <$> Flip getPrefix 346 | _ -> Const mzero 347 | 348 | parseSIDFromJSON :: (MonadPlus m, SID s) => Value -> m s 349 | parseSIDFromJSON (String v) = getConst $ parseSID' v 350 | parseSIDFromJSON _ = mzero 351 | 352 | sidToJSON :: SID s => s -> Value 353 | sidToJSON = String . getSID 354 | 355 | class SID s where 356 | getPrefix :: Const (Char, Char) s 357 | 358 | getSID :: s -> Text 359 | default getSID :: (Generic s, GSID (Rep s ())) => s -> Text 360 | getSID = (gGetSID :: Rep s () -> Text) . from 361 | 362 | makeSID :: Text -> s 363 | default makeSID :: (Generic s, GSID (Rep s ())) => Text -> s 364 | makeSID = to . (gMakeSID :: Text -> Rep s ()) 365 | 366 | parseSID :: Text -> Maybe s 367 | parseSID = getConst . parseSID' 368 | 369 | class GSID s where 370 | gGetSID :: s -> Text 371 | gMakeSID :: Text -> s 372 | 373 | instance GSID (D1 a (C1 b (S1 c (Rec0 Text))) ()) where 374 | gGetSID (M1 (M1 (M1 (K1 s)))) = s 375 | gMakeSID = M1 . M1 . M1 . K1 376 | --------------------------------------------------------------------------------