packages feed

voicebase (empty) → 0.1.1.0

raw patch · 11 files changed

+719/−0 lines, 11 filesdep +HsOpenSSLdep +aesondep +basesetup-changed

Dependencies added: HsOpenSSL, aeson, base, bytestring, http-client, http-client-openssl, json-autotype, lens, mime-types, options, text, voicebase, wreq

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for voicebase++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Daisee (c) 2018++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Daisee nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,42 @@+# Haskell bindings for voice base api++http://voicebase.readthedocs.io/en/v2-beta/++## Usage++```haskell+    import VoicebaseClient+    import Json.TranscriptTypes++    main = do+      result <- transcribe "your bearer token" "./audio.mp3"+      -- get validated response or error+      case result of+        Left err -> print $ err+        Right val -> putStrLn val++      -- get a parsed structure+      parsed <- transcribeParse "your bearer token" "./audio.mp3"+      case parsed of +        Left err -> print $ err+        Right val -> print $ latestTranscriptsWordsTranscripts . transcriptsLatestTranscripts . mediaTranscripts . topLevelMedia val++```++There is also a main file which shows usage+++## features+++ Post audio file to voicebase++ Poll for progress++ Get resulting transcript or valid json or an error.++## Trivia+### wreq client instead of servant ###++The reason httplib is used rather than servant is because of multipart posting.+It was asserted that servant is incapable of doing that.+This is a requirement for the voicebase api however.+Http client does support this however we need to  write more code as a result of this.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Network.Mime(defaultMimeLookup)+import VoicebaseClient+import Options+import qualified Data.ByteString.Lazy as LBS++data MainOptions = MainOptions+    { +      token :: Maybe BearerToken,+      targetFile :: Maybe FilePath+    }++instance Options MainOptions where+    defineOptions = pure MainOptions+        <*> simpleOption "bearer-token" Nothing+            "The bearer token obtained from voicebase"+        <*> simpleOption "file" Nothing+            "The file to be posted"+main :: IO ()+main = runCommand $ \opts args -> do+  case token opts of+    Just bearToken -> case targetFile opts of +      Just file -> withBearAndFile bearToken file+      Nothing -> putStrLn "file required"+    Nothing -> putStrLn "bearer token required"++withBearAndFile :: BearerToken -> FilePath -> IO() +withBearAndFile bearToken file = do +  -- result <- transcribeFile bearToken file +  bytes <- LBS.readFile file+  result <- transcribeBytes bearToken (LazyByteFile bytes (defaultMimeLookup "f.mp3"))+  putStrLn . show $ result
+ src/Json/ProgressTypes.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE TemplateHaskell     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE TypeOperators       #-}+{-# LANGUAGE DeriveGeneric       #-}++-- | Auto generated module by json-autotype+module Json.ProgressTypes where++import           System.Exit        (exitFailure, exitSuccess)+import           System.IO          (stderr, hPutStrLn)+import qualified Data.ByteString.Lazy.Char8 as BSL+import           Data.ByteString.Lazy+import           System.Environment (getArgs)+import           Control.Monad      (forM_, mzero, join)+import           Control.Applicative+import           Data.Aeson.AutoType.Alternative+import           Data.Aeson(eitherDecode, Value(..), FromJSON(..), ToJSON(..),+                            pairs,+                            (.:), (.:?), (.=), object)+import           Data.Monoid+import           Data.Text (Text)+import qualified GHC.Generics++-- | Workaround for https://github.com/bos/aeson/issues/287.+o .:?? val = fmap join (o .:? val)+++data Tasks = Tasks { ++  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Tasks where+  parseJSON (Object v) = return  Tasks+  parseJSON _          = mzero+++instance ToJSON Tasks where+  toJSON     (Tasks {}) = object []++++data Progress = Progress { +    progressStatus :: Text,+    progressJobId :: Text,+    progressTasks :: Tasks+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Progress where+  parseJSON (Object v) = Progress <$> v .:   "status" <*> v .:   "jobId" <*> v .:   "tasks"+  parseJSON _          = mzero+++instance ToJSON Progress where+  toJSON     (Progress {..}) = object ["status" .= progressStatus, "jobId" .= progressJobId, "tasks" .= progressTasks]+  toEncoding (Progress {..}) = pairs  ("status" .= progressStatus<>"jobId" .= progressJobId<>"tasks" .= progressTasks)+++data TopLevel = TopLevel { +    topLevelStatus :: Text,+    topLevelProgress :: Progress,+    topLevelMediaId :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON TopLevel where+  parseJSON (Object v) = TopLevel <$> v .:   "status" <*> v .:   "progress" <*> v .:   "mediaId"+  parseJSON _          = mzero+++instance ToJSON TopLevel where+  toJSON     (TopLevel {..}) = object ["status" .= topLevelStatus, "progress" .= topLevelProgress, "mediaId" .= topLevelMediaId]+  toEncoding (TopLevel {..}) = pairs  ("status" .= topLevelStatus<>"progress" .= topLevelProgress<>"mediaId" .= topLevelMediaId)+++parse :: ByteString -> Either String TopLevel+parse = eitherDecode
+ src/Json/SubmitMediaTypes.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE TemplateHaskell     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE TypeOperators       #-}+{-# LANGUAGE DeriveGeneric       #-}++-- | Auto generated module by json-autotype+module Json.SubmitMediaTypes where++import           System.Exit        (exitFailure, exitSuccess)+import           System.IO          (stderr, hPutStrLn)+import qualified Data.ByteString.Lazy.Char8 as BSL+import           Data.ByteString.Lazy+import           System.Environment (getArgs)+import           Control.Monad      (forM_, mzero, join)+import           Control.Applicative+import           Data.Aeson.AutoType.Alternative+import           Data.Aeson(eitherDecode, Value(..), FromJSON(..), ToJSON(..),+                            pairs,+                            (.:), (.:?), (.=), object)+import           Data.Monoid+import           Data.Text (Text)+import qualified GHC.Generics++-- | Workaround for https://github.com/bos/aeson/issues/287.+o .:?? val = fmap join (o .:? val)+++data Self = Self { +    selfHref :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Self where+  parseJSON (Object v) = Self <$> v .:   "href"+  parseJSON _          = mzero+++instance ToJSON Self where+  toJSON     (Self {..}) = object ["href" .= selfHref]+  toEncoding (Self {..}) = pairs  ("href" .= selfHref)+++data Links = Links { +    linksSelf :: Self+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Links where+  parseJSON (Object v) = Links <$> v .:   "self"+  parseJSON _          = mzero+++instance ToJSON Links where+  toJSON     (Links {..}) = object ["self" .= linksSelf]+  toEncoding (Links {..}) = pairs  ("self" .= linksSelf)+++data Metadata = Metadata { ++  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Metadata where+  parseJSON (Object v) = return  Metadata+  parseJSON _          = mzero+++instance ToJSON Metadata where+  toJSON     (Metadata {}) = object []++++data TopLevel = TopLevel { +    topLevelStatus :: Text,+    topLevelMediaId :: Text,+    topLevelLinks :: Links,+    topLevelMetadata :: Metadata+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON TopLevel where+  parseJSON (Object v) = TopLevel <$> v .:   "status" <*> v .:   "mediaId" <*> v .:   "_links" <*> v .:   "metadata"+  parseJSON _          = mzero+++instance ToJSON TopLevel where+  toJSON     (TopLevel {..}) = object ["status" .= topLevelStatus, "mediaId" .= topLevelMediaId, "_links" .= topLevelLinks, "metadata" .= topLevelMetadata]+  toEncoding (TopLevel {..}) = pairs  ("status" .= topLevelStatus<>"mediaId" .= topLevelMediaId<>"_links" .= topLevelLinks<>"metadata" .= topLevelMetadata)++++parse :: ByteString -> Either String TopLevel+parse = eitherDecode
+ src/Json/TranscriptTypes.hs view
@@ -0,0 +1,234 @@+{-# LANGUAGE TemplateHaskell     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards     #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE TypeOperators       #-}+{-# LANGUAGE DeriveGeneric       #-}++-- | Auto generated module by json-autotype+module Json.TranscriptTypes where++import           System.Exit        (exitFailure, exitSuccess)+import           System.IO          (stderr, hPutStrLn)+import qualified Data.ByteString.Lazy.Char8 as BSL+import           Data.ByteString.Lazy+import           System.Environment (getArgs)+import           Control.Monad      (forM_, mzero, join)+import           Control.Applicative+import           Data.Aeson.AutoType.Alternative+import           Data.Aeson(eitherDecode, Value(..), FromJSON(..), ToJSON(..),+                            pairs,+                            (.:), (.:?), (.=), object)+import           Data.Monoid+import           Data.Text (Text)+import qualified GHC.Generics++-- | Workaround for https://github.com/bos/aeson/issues/287.+o .:?? val = fmap join (o .:? val)++data LatestTopics = LatestTopics { +    latestTopicsTopics :: [(Maybe Value)],+    latestTopicsRevision :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON LatestTopics where+  parseJSON (Object v) = LatestTopics <$> v .:   "topics" <*> v .:   "revision"+  parseJSON _          = mzero+++data Topics = Topics { +    topicsLatestTopics :: LatestTopics+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Topics where+  parseJSON (Object v) = Topics <$> v .:   "latest"+  parseJSON _          = mzero+++data Tasks = Tasks { +} deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Tasks where+  parseJSON (Object v) = return  Tasks+  parseJSON _          = mzero++data Progress = Progress { +    progressStatus :: Text,+    progressJobId :: Text,+    progressTasks :: Tasks+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Progress where+  parseJSON (Object v) = Progress <$> v .:   "status" <*> v .:   "jobId" <*> v .:   "tasks"+  parseJSON _          = mzero+++data Job = Job { +    jobProgress :: Progress+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Job where+  parseJSON (Object v) = Job <$> v .:   "progress"+  parseJSON _          = mzero+++data T = T { +    tUnknown :: [Text]+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON T where+  parseJSON (Object v) = T <$> v .:   "unknown"+  parseJSON _          = mzero+++data WordsKeyElt = WordsKeyElt { +    wordsKeyEltRelevance :: Text,+    wordsKeyEltT :: T,+    wordsKeyEltName :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON WordsKeyElt where+  parseJSON (Object v) = WordsKeyElt <$> v .:   "relevance" <*> v .:   "t" <*> v .:   "name"+  parseJSON _          = mzero+++data LatestKeywords = LatestKeywords { +    latestKeywordsGroups :: [(Maybe Value)],+    latestKeywordsWordsKey :: [WordsKeyElt],+    latestKeywordsRevision :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON LatestKeywords where+  parseJSON (Object v) = LatestKeywords <$> v .:   "groups" <*> v .:   "words" <*> v .:   "revision"+  parseJSON _          = mzero+++data Keywords = Keywords { +    keywordsLatestKeywords :: LatestKeywords+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Keywords where+  parseJSON (Object v) = Keywords <$> v .:   "latest"+  parseJSON _          = mzero+++data Length = Length { +    lengthDescriptive :: Text,+    lengthMilliseconds :: Double+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Length where+  parseJSON (Object v) = Length <$> v .:   "descriptive" <*> v .:   "milliseconds"+  parseJSON _          = mzero+++data Metadata = Metadata { +    metadataLength :: Length,+    metadataContentType :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Metadata where+  parseJSON (Object v) = Metadata <$> v .:   "length" <*> v .:   "contentType"+  parseJSON _          = mzero+++data WordsTranscriptsElt = WordsTranscriptsElt { +    wordsTranscriptsEltW :: Text,+    wordsTranscriptsEltM :: (Maybe (Text:|:[(Maybe Value)])),+    wordsTranscriptsEltP :: Double,+    wordsTranscriptsEltE :: Double,+    wordsTranscriptsEltC :: Double,+    wordsTranscriptsEltS :: Double+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON WordsTranscriptsElt where+  parseJSON (Object v) = WordsTranscriptsElt <$> v .:   "w" <*> v .:?? "m" <*> v .:   "p" <*> v .:   "e" <*> v .:   "c" <*> v .:   "s"+  parseJSON _          = mzero+++data LatestTranscripts = LatestTranscripts { +    latestTranscriptsEngine :: Text,+    latestTranscriptsConfidence :: Double,+    latestTranscriptsName :: Text,+    latestTranscriptsWordsTranscripts :: [WordsTranscriptsElt],+    latestTranscriptsRevision :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON LatestTranscripts where+  parseJSON (Object v) = LatestTranscripts <$> v .:   "engine" <*> v .:   "confidence" <*> v .:   "name" <*> v .:   "words" <*> v .:   "revision"+  parseJSON _          = mzero+++data Transcripts = Transcripts { +    transcriptsLatestTranscripts :: LatestTranscripts+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Transcripts where+  parseJSON (Object v) = Transcripts <$> v .:   "latest"+  parseJSON _          = mzero+++data Media = Media { +    mediaStatus :: Text,+    mediaTopics :: Topics,+    mediaMediaId :: Text,+    mediaDateCreated :: Text,+    mediaJob :: Job,+    mediaKeywords :: Keywords,+    mediaMetadata :: Metadata,+    mediaTranscripts :: Transcripts,+    mediaDateFinished :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Media where+  parseJSON (Object v) = Media <$> v .:   "status" <*> v .:   "topics" <*> v .:   "mediaId" <*> v .:   "dateCreated" <*> v .:   "job" <*> v .:   "keywords" <*> v .:   "metadata" <*> v .:   "transcripts" <*> v .:   "dateFinished"+  parseJSON _          = mzero+++data Self = Self { +    selfHref :: Text+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Self where+  parseJSON (Object v) = Self <$> v .:   "href"+  parseJSON _          = mzero+++data Links = Links { +    linksSelf :: Self+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Links where+  parseJSON (Object v) = Links <$> v .:   "self"+  parseJSON _          = mzero+++data Transcript = Transcript { +    topLevelMedia :: Media,+    topLevelLinks :: Links+  } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Transcript where+  parseJSON (Object v) = Transcript <$> v .:   "media" <*> v .:   "_links"+  parseJSON _          = mzero++parse :: ByteString -> Either String Transcript+parse = eitherDecode
+ src/VoicebaseClient.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Main module for doing the requests to the voicebase api: http://voicebase.readthedocs.io/en/v2-beta/+module VoicebaseClient+    (+      transcribe, +      transcribeFile, +      transcribeBytes, +      transcribeParse, +      BearerToken, +      Error(..),+      LazyByteFile(..)+  ) where++import Control.Lens+import Control.Monad+import Control.Applicative++import Data.Maybe+import Data.Monoid+import Data.Text.Encoding as TE+import Data.Bifunctor++import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as C8LBS+import qualified Data.ByteString.Lazy as LBS+import Data.Text (Text, unpack)++import Data.Aeson(Value, eitherDecode, FromJSON(parseJSON))+import Data.Aeson.Types(parseEither)+import Network.Wreq+import Network.Mime(MimeType)+import qualified Network.Wreq.Session as Sess+import Network.HTTP.Client.OpenSSL+import Network.HTTP.Client (defaultManagerSettings, managerResponseTimeout,  responseTimeoutMicro)+import OpenSSL.Session (context)++import qualified Json.SubmitMediaTypes as Submit+import qualified Json.ProgressTypes as Progress+import qualified Json.TranscriptTypes as Transcript++import System.IO(fixIO)++-- | get your bearer token at http://voicebase.readthedocs.io/en/v2-beta/how-to-guides/hello-world.html#token+type BearerToken = String++data Error = UnkownResponse | ParseError String+  deriving(Show)++url :: String+url = "https://apis.voicebase.com/v2-beta/media"++timeout = responseTimeoutMicro 10000++makeOpts :: BearerToken -> Options+makeOpts token = withAuthorization token defaults++withAuthorization :: BearerToken -> Options -> Options+withAuthorization token options = options & manager .~ Left (opensslManagerSettings context) +  & manager .~ Left (defaultManagerSettings { managerResponseTimeout = timeout} )+  & header "Authorization" .~ [(BS.pack ("Bearer " ++ token))]++-- | transcribes the audio file and puts it into a Transcript+transcribeParse  :: BearerToken -> FilePath -> IO(Either Error Transcript.Transcript)+transcribeParse token filepath = fmap (join . second (first ParseError . parseEither parseJSON)) (transcribeFile token filepath)++inputName = "media"+-- | Given a bearer token, and a filepath to an audio file, this function will +-- | eventually return a transcript or times out after 10 seconds+-- | Throws HttpExceptionRequest, IOException (file not found)+transcribeFile :: BearerToken -> FilePath -> IO(Either Error Value)+transcribeFile bearerToken filePath = transcribe bearerToken defaults (+    [partFileSource inputName filePath]+  )++-- | In case of a bytestring, we also need to mimetype to decode the send string+data LazyByteFile = LazyByteFile {+  content :: LBS.ByteString,+  mimetype :: MimeType -- to get this Jappie used defaultMimeLookup "f.mp3"+  }+-- | Transcribe a bytestring+-- | Throws HttpExceptionRequest+transcribeBytes :: BearerToken -> LazyByteFile -> IO(Either Error Value)+transcribeBytes bearerToken (LazyByteFile content mimetype) = +  transcribe bearerToken defaults (+    [partLBS inputName content & partContentType .~ Just mimetype ]+  )++-- | Generic transcribe, agnostic of options, will add ssl, +-- | the bearer token and a timeout to the options.+-- | Throws HttpExceptionRequest+transcribe :: BearerToken -> Options -> [Part] -> IO(Either Error Value)+transcribe token options parts = do+  session <- Sess.newSession+  response <- Sess.postWith (withAuthorization token options) session url parts+  case Submit.parse (response ^. responseBody) of+    Left error -> return $ Left $ ParseError error+    Right parsed -> do +      -- wait for the service to complete+      waitResult <- pollStatus token session (Submit.topLevelMediaId parsed)+      case waitResult of+        Right _ -> first ParseError . eitherDecode <$> (requestTranscript token session (Submit.topLevelMediaId parsed)) +        Left error -> return $ Left $ error+++pollStatus :: BearerToken -> Sess.Session -> Text -> IO(Either Error ())+pollStatus token session mediaId = pollStatus' token session mediaId "pending"++pollStatus' :: BearerToken -> Sess.Session -> Text -> Text -> IO (Either Error ())+pollStatus' token session mediaId "pending" = do+  response <- Sess.getWith (makeOpts token) session (url <> "/" <> (unpack mediaId) <> "/progress")+  case Progress.parse (response ^. responseBody) of +    Left error -> return $ Left $ ParseError error+    Right parsed -> pollStatus' token session mediaId ((Progress.progressStatus . Progress.topLevelProgress) $ parsed)+pollStatus' token session mediaId "started" = pollStatus' token session mediaId "pending"+pollStatus' token session mediaId "completed" = return $ Right ()+pollStatus' _ _ _ _ = return $ Left UnkownResponse++requestTranscript :: BearerToken -> Sess.Session -> Text -> IO (C8LBS.ByteString)+requestTranscript token session mediaId = do+  response <- Sess.getWith (makeOpts token) session (url <> "/" <> (unpack mediaId))+  return $ response ^. responseBody
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"
+ voicebase.cabal view
@@ -0,0 +1,74 @@+-- This file has been generated from package.yaml by hpack version 0.20.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 1d2c9271b864ce3c92a5289357729a078bb7fa8814ebedf3ccf470a746a16270++name:           voicebase+version:        0.1.1.0+synopsis:       Upload audio files to voicebase to get a transcription+description:    voicebase bindings for <http://voicebase.readthedocs.io/en/v2-beta/>+category:       API+author:         Jappie Klooster+maintainer:     jappie.klooster@daisee.com+copyright:      Daisee Pty Ltd+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10++extra-source-files:+    ChangeLog.md+    README.md++library+  hs-source-dirs:+      src+  build-depends:+      HsOpenSSL+    , aeson+    , base >=4.7 && <5+    , bytestring+    , http-client+    , http-client-openssl+    , json-autotype+    , lens+    , mime-types+    , text+    , wreq+  exposed-modules:+      Json.ProgressTypes+      Json.SubmitMediaTypes+      Json.TranscriptTypes+      VoicebaseClient+  other-modules:+      Paths_voicebase+  default-language: Haskell2010++executable voicebase+  main-is: Main.hs+  hs-source-dirs:+      app+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , bytestring+    , mime-types+    , options+    , voicebase+  other-modules:+      Paths_voicebase+  default-language: Haskell2010++test-suite voicebase-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , voicebase+  other-modules:+      Paths_voicebase+  default-language: Haskell2010