{-# LANGUAGE TemplateHaskell #-} module Network.API.GoogleDictionary ( Entry(..) , lookupWord , getResponse , module Network.API.GoogleDictionary.Types ) where import Control.Applicative ((<$>)) import Control.Lens import Control.Monad (join) import Control.Monad.State import Data.Aeson (eitherDecode) import qualified Data.ByteString.Lazy.Char8 as BS import Data.List (dropWhileEnd) import Data.Maybe (catMaybes) import Data.Monoid (First(..), mconcat) import Network.API.GoogleDictionary.Internal import Network.API.GoogleDictionary.Types data Entry = Entry { entryWord :: !String , entryDefinition :: !String , entryPartOfSpeech :: Maybe String , entryPhonetic :: !String , entrySoundUrl :: Maybe String } deriving Show -- Internal representation of an Entry that is more similar to a Response. data EntryInternal = EntryInternal { _eiDefinitions :: [String] , _eiPartOfSpeech :: Maybe String , _eiPhonetic :: !String , _eiSoundUrl :: Maybe String } deriving Show makeLenses ''EntryInternal initEntryInternal :: EntryInternal initEntryInternal = EntryInternal { _eiDefinitions = [] , _eiPartOfSpeech = Nothing , _eiPhonetic = "" , _eiSoundUrl = Nothing } entryInternalToEntries :: String -> EntryInternal -> [Entry] entryInternalToEntries word (EntryInternal defs pos phon sound) = map (\def -> Entry word def pos phon sound) defs lookupWord :: String -> IO [Entry] lookupWord word = either (const []) (entryInternalToEntries word . makeEntry) <$> getResponse word {- lookupWordDebug :: String -> IO (Either String Entry) lookupWordDebug word = lookupWord' Left (Right . makeEntry word) word -} makeEntry :: Response -> EntryInternal makeEntry response = flip execState initEntryInternal $ mapM processPrimary (responsePrimaries response) processPrimary :: Primary -> State EntryInternal () processPrimary (Primary pentries terms _) = do mapM_ processPentry pentries mapM_ processPterm terms processPentry :: PEntry -> State EntryInternal () processPentry (PEntry _ terms PEMeaning) = mapM_ processPentryTerm terms processPentry _ = return () processPentryTerm :: Term -> State EntryInternal () processPentryTerm (Term _ _ def TText) = eiDefinitions %= (def:) processPentryTerm _ = return () processPterm :: Term -> State EntryInternal () processPterm (Term (Just labels) _ _ TText) = processPtermLabels labels processPterm (Term _ _ soundUrl TSound) = eiSoundUrl .= Just soundUrl processPterm (Term _ _ phonetic TPhonetic) = eiPhonetic .= phonetic processPterm _ = return () processPtermLabels :: [Label] -> State EntryInternal () processPtermLabels ((Label pos (Just "Part-of-speech")):_) = eiPartOfSpeech .= Just pos processPtermLabels (_:xs) = processPtermLabels xs processPtermLabels [] = return () -- No part of speech! getResponse :: String -> IO (Either String Response) getResponse word = do let url = "http://www.google.com/dictionary/json?callback=a&sl=en&tl=en&q=" ++ word fmap (decodeHex . trimResponse) (getJson url) >>= maybe badContents goodContents where -- | Trim off the boiler plate callback characters, because JSONP is returned. -- Hard-code "2" because "callback=a" is also hard-coded, so the first two -- characters are "a(" trimResponse :: String -> String trimResponse = dropWhileEnd (/= '}') . drop 2 badContents :: IO (Either String Response) badContents = return (Left "invalid hex code encountered") goodContents :: String -> IO (Either String Response) goodContents = return . eitherDecode . BS.pack