packages feed

givegif-1.0.0.0: app/Main.hs

{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}

module Main where

import qualified Control.Error.Util         as Err
import qualified Data.ByteString.Lazy       as BSL
import qualified Data.ByteString.Lazy.Char8 as BS8
import qualified Data.Text                  as T
import qualified Data.Text.IO               as TIO
import qualified Network.Wreq               as Wreq
import qualified Options.Applicative        as Opt
import qualified Options.Applicative.Text   as Opt
import qualified Web.Giphy                  as Giphy

import           Control.Applicative        (optional, (<**>), (<|>))
import           Control.Lens               (Getting (), preview)
import           Control.Lens.At            (at)
import           Control.Lens.Cons          (_head)
import           Control.Lens.Operators
import           Control.Lens.Prism         (_Left, _Right)
import           Control.Monad              (join, unless)
import           Data.Monoid                (First (), (<>))
import           Data.Version               (Version (), showVersion)
import           Paths_givegif              (version)
import           System.Environment         (getProgName)
import           System.IO                  (stderr)

import           Console

data Options = Options
  { optNoPreview :: Bool
  , optMode      :: SearchMode
  }

data SearchMode = OptSearch T.Text | OptTranslate T.Text | OptRandom (Maybe T.Text)

options :: Opt.Parser Options
options =
  Options <$> Opt.switch ( Opt.long "no-preview"
                        <> Opt.short 'n'
                        <> Opt.help "Don't render an inline image preview." )
          <*> ( ( OptSearch <$> Opt.textOption ( Opt.long "search"
                                          <> Opt.short 's'
                                          <> Opt.help "Use search to find a matching GIF." ) )
          <|> ( OptTranslate <$> Opt.textOption ( Opt.long "translate"
                                           <> Opt.short 't'
                                           <> Opt.help "Use translate to find a matching GIF." ) )
          <|> ( OptRandom <$> optional ( Opt.textArgument ( Opt.metavar "RANDOM_TAG" ) ) ) )

cliParser :: String -> Version -> Opt.ParserInfo Options
cliParser progName ver =
  Opt.info ( Opt.helper <*> options <**> versionInfo )
    ( Opt.fullDesc
   <> Opt.progDesc "Find GIFs on the command line."
   <> Opt.header progName )
  where
    versionInfo = Opt.infoOption ( unwords [progName, showVersion ver] )
      ( Opt.short 'V'
     <> Opt.long "version"
     <> Opt.hidden
     <> Opt.help "Show version information" )

apiKey :: Giphy.Key
apiKey = Giphy.Key "dc6zaTOxFJmzC"

taggedPreview
  :: t
  -> Getting (First a) s a
  -> s
  -> Either t a
taggedPreview tag l s = Err.note tag $ preview l s

main :: IO ()
main = do
  progName <- getProgName
  Opt.execParser (cliParser progName version) >>= run
  where
    run :: Options -> IO ()
    run opts = do
      let config = Giphy.GiphyConfig apiKey
      let app = getApp opts
      resp <- Giphy.runGiphy app config

      -- Get the first result and turn the left side into a String error
      let fstRes = resp & _Right %~ taggedPreview "No results found." _head
                        & _Left  %~ T.pack . show
                        & join

      -- Turn the right hand side into an Either.
      let fstUrl = fstRes & _Right %~ taggedPreview "No images attached."
                                      ( Giphy.gifImages
                                      . at "original"
                                      . traverse
                                      . Giphy.imageUrl
                                      . traverse )
                                    & join

      -- TODO: Understand lens actions and perform the fetch + tuple transform
      --       through it.
      let doReq uri = Wreq.get uri >>= (\resp' -> return (uri, resp'))
      resp' <- sequence $ doReq <$> (show <$> fstUrl)

      case resp' of
        Right r -> uncurry (printGif opts) r
        Left e -> TIO.hPutStrLn stderr $ "Error: " <> e

    getApp :: Options -> Giphy.Giphy [Giphy.Gif]
    getApp opts =
      case optMode opts of
        OptSearch s -> searchApp s
        OptTranslate t -> translateApp t
        OptRandom r -> randomApp r

    printGif :: Options -> String -> Wreq.Response BSL.ByteString -> IO ()
    printGif opts uri r = do
      unless (optNoPreview opts) $ do
        render <- getImageRenderer
        BS8.putStrLn . render $ consoleImage True (r ^. Wreq.responseBody)
      putStrLn uri

translateApp :: T.Text -> Giphy.Giphy [Giphy.Gif]
translateApp q = do
  resp <- Giphy.translate $ Giphy.Phrase q
  return . pure $ resp ^. Giphy.translateItem

searchApp :: T.Text -> Giphy.Giphy [Giphy.Gif]
searchApp q = do
  resp <- Giphy.search $ Giphy.Query q
  return $ resp ^. Giphy.searchItems

randomApp :: Maybe T.Text -> Giphy.Giphy [Giphy.Gif]
randomApp q = do
  resp <- Giphy.random $ Giphy.Tag <$> q
  return . pure $ resp ^. Giphy.randomGifItem