feed-translator-0.1.0.2: src/Web/Feed/FeedTranslatorWai.hs
{-# LANGUAGE OverloadedStrings #-}
module Web.Feed.FeedTranslatorWai (application) where
import Data.Maybe (catMaybes, listToMaybe)
import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
import Control.Monad (liftM)
import Control.Monad.IO.Class (liftIO)
import Control.Lens.Getter ((^.))
import Data.ByteString (ByteString)
import Data.LanguageCodes (ISO639_1, fromChars)
import Data.Map.Strict as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import Data.Text.Encoding (decodeUtf8)
import qualified Data.Text.Lazy.Encoding as LTE
import Language.Translate.Naver (translate, translateUrl)
import Network.Wai (Application, rawQueryString)
import qualified Network.Wreq as W
import Text.Blaze.Html.Renderer.Text (renderHtml)
import Text.Feed.Types (Feed)
import Text.Feed.Export (xmlFeed)
import Text.Feed.Import (readAtom, readRSS1, readRSS2)
import Text.XML.Light.Input (parseXMLDoc)
import Text.XML.Light.Output (showElement)
import Web.Scotty (ActionM, get, html, next, param, params, raw, redirect,
regex, request, scottyApp, setHeader)
import Web.Feed.FeedTranslator (Translator(..), translateFeed)
import Web.Feed.FeedTranslatorCache (CacheMap, wrapTranslate)
import Web.Feed.FeedTranslatorView (index)
-- | Return the Naver Translate engine that translates text from the
-- 'sourceLanguage' to 'targetLanguage'.
naverTranslator :: ISO639_1 -- | The language translated from
-> ISO639_1 -- | The language to translate to
-> Translator
naverTranslator sourceLanguage targetLanguage =
Translator { textTranslator = translate sourceLanguage targetLanguage
, urlTranslator = translateUrl sourceLanguage targetLanguage
}
-- | Translate the feed of given 'url' using 'translator'.
translateFeedUrl :: Translator -- | The translator engine to use
-> String -- | The feed url to translate
-> IO Feed -- | The translated feed
translateFeedUrl translator url = do
response <- W.get url
case response ^. W.responseStatus . W.statusCode of
200 -> do
let xmlString = LTE.decodeUtf8 $ response ^. W.responseBody
xmlDoc = parseXMLDoc xmlString
readers = [readAtom, readRSS2, readRSS1]
possibleFeeds = catMaybes [read =<< xmlDoc | read <- readers]
feed = listToMaybe possibleFeeds
case feed of
Just feed' -> translateFeed translator feed'
Nothing ->
ioError $ userError "failed to parse feed"
_ -> ioError $ userError $ "bad request: " ++ url
parseLanguageCode :: ActionM LT.Text -> ActionM ISO639_1
parseLanguageCode code = do
code' <- code
case LT.unpack code' of
[a, b] -> case fromChars a b of
Just lang -> return lang
Nothing -> next
_ -> next
type CacheKey = (ISO639_1, ISO639_1)
-- | WAI web application.
application :: IORef (M.Map CacheKey (IORef CacheMap)) -> IO Application
application cacheMapRef = scottyApp $ do
get "/" $
html $ renderHtml index
get "/query/" $ do
source <- param "source"
target <- param "target"
url <- param "url"
let getUrl scheme path = LT.concat [ "/", source, "/", target, "/"
, scheme , "/", LT.drop 3 path
]
case LT.breakOn "://" url of
("http", path) -> redirect $ getUrl "http" path
("https", path) -> redirect $ getUrl "https" path
a -> redirect "/"
get (regex "^/([a-z]{2})/([a-z]{2})/(http|https)/(.*)") $ do
source <- parseLanguageCode $ param "1"
target <- parseLanguageCode $ param "2"
scheme <- liftM LT.toStrict $ param "3"
path <- liftM LT.toStrict $ param "4"
query <- liftM (decodeUtf8 . rawQueryString) request
let cacheKey :: CacheKey
cacheKey = (source, target)
cacheMap <- liftIO $ readIORef cacheMapRef
cacheRef <- liftIO $ case M.lookup cacheKey cacheMap of
Just ref -> return ref
Nothing -> do
ref <- newIORef M.empty
atomicModifyIORef' cacheMapRef $ \cacheMap ->
(M.insert cacheKey ref cacheMap, ref)
let url :: String
url = T.unpack $ T.concat [scheme, "://", path, query]
translator :: Translator
translator = naverTranslator source target
cachedTranslator :: Translator
cachedTranslator = translator {
textTranslator = wrapTranslate cacheRef
(textTranslator translator)
}
feed <- liftIO $ translateFeedUrl cachedTranslator url
let feedXml = LT.pack $ showElement $ xmlFeed feed
setHeader "Content-Type" "text/xml; charset=utf8"
raw $ LTE.encodeUtf8 feedXml