packages feed

slack-web-2.0.1.0: main/cli.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeApplications #-}

-- base
import Control.Exception (throwIO)
import Control.Monad.IO.Class (liftIO)
-- butcher

-- bytestring

-- monad-loops
import Control.Monad.Loops (iterateUntil)
-- mtl
import Control.Monad.Reader (runReaderT)
import Data.ByteString.Lazy.Char8 qualified as BL
import Data.Functor (void)
-- pretty-simple

-- slack-web

-- text
import Data.Text qualified as Text
import Data.Text.Lazy qualified as TextLazy
-- time
import Data.Time.Clock (addUTCTime, getCurrentTime, nominalDay)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
-- servant-client-core
import Servant.Client.Core (ClientError (..), Response, ResponseF (..))
import System.Environment (getEnv)
import System.Exit (die)
import System.IO (hPutStrLn, stderr)
import Text.Pretty.Simple (pPrint, pShow)
import Text.Read (readMaybe)
import UI.Butcher.Monadic
import Web.Slack.Classy qualified as Slack
import Web.Slack.Common qualified as Slack
import Web.Slack.Conversation qualified as SlackConversation

main :: IO ()
main = do
  apiConfig <- Slack.mkSlackConfig . Text.pack =<< getEnv "SLACK_API_TOKEN"
  mainFromCmdParserWithHelpDesc $ \helpDesc -> do
    addHelpCommand helpDesc
    addCmd "conversations.list" . addCmdImpl $ do
      let listReq =
            SlackConversation.ListReq
              { SlackConversation.listReqExcludeArchived = Just True
              , SlackConversation.listReqTypes =
                  [ SlackConversation.PublicChannelType
                  , SlackConversation.PrivateChannelType
                  , SlackConversation.MpimType
                  , SlackConversation.ImType
                  ]
              }
      Slack.conversationsList listReq
        `runReaderT` apiConfig
        >>= \case
          Right (SlackConversation.ListRsp cs) -> do
            pPrint cs
          Left err -> do
            peepInResponseBody err
            hPutStrLn stderr "Error when fetching the list of conversations:"
            die . TextLazy.unpack $ pShow err

    addCmd "conversations.history" $ do
      conversationId <-
        Slack.ConversationId
          . Text.pack
          <$> addParamString "CONVERSATION_ID" (paramHelpStr "ID of the conversation to fetch")
      getsAll <- addSimpleBoolFlag "A" ["all"] (flagHelpStr "Get all available messages in the channel")
      addCmdImpl
        $ if getsAll
          then do
            (`runReaderT` apiConfig) $ do
              fetchPage <- Slack.conversationsHistoryAll $ (SlackConversation.mkHistoryReq conversationId) {SlackConversation.historyReqCount = 2}
              void . iterateUntil null $ do
                result <- either (liftIO . throwIO) return =<< fetchPage
                liftIO $ pPrint result
                return result
          else do
            nowUtc <- getCurrentTime
            let now = Slack.mkSlackTimestamp nowUtc
                thirtyDaysAgo = Slack.mkSlackTimestamp $ addUTCTime (nominalDay * negate 30) nowUtc
                histReq =
                  SlackConversation.HistoryReq
                    { SlackConversation.historyReqChannel = conversationId
                    , SlackConversation.historyReqCount = 5
                    , SlackConversation.historyReqLatest = Just now
                    , SlackConversation.historyReqOldest = Just thirtyDaysAgo
                    , SlackConversation.historyReqInclusive = True
                    , SlackConversation.historyReqCursor = Nothing
                    }
            Slack.conversationsHistory histReq
              `runReaderT` apiConfig
              >>= \case
                Right rsp ->
                  pPrint rsp
                Left err -> do
                  peepInResponseBody err
                  hPutStrLn stderr "Error when fetching the history of conversations:"
                  die . TextLazy.unpack $ pShow err

    addCmd "conversations.replies" $ do
      conversationId <-
        Slack.ConversationId
          . Text.pack
          <$> addParamString "CONVERSATION_ID" (paramHelpStr "ID of the conversation to fetch")
      threadTimeStampStr <- addParamString "TIMESTAMP" (paramHelpStr "Timestamp of the thread to fetch")
      let ethreadTimeStamp = Slack.timestampFromText $ Text.pack threadTimeStampStr
      pageSize <- addParamRead "PAGE_SIZE" (paramHelpStr "How many messages to get by a request.")
      addCmdImpl $ do
        -- NOTE: butcher's CmdParser isn't a MonadFail
        threadTimeStamp <-
          either
            (\emsg -> fail $ "Invalid timestamp " ++ show threadTimeStampStr ++ ": " ++ emsg)
            return
            ethreadTimeStamp
        nowUtc <- getCurrentTime
        let now = Slack.mkSlackTimestamp nowUtc
            tenDaysAgo = Slack.mkSlackTimestamp $ addUTCTime (nominalDay * negate 10) nowUtc
            req =
              (SlackConversation.mkRepliesReq conversationId threadTimeStamp)
                { SlackConversation.repliesReqLimit = pageSize
                , SlackConversation.repliesReqLatest = Just now
                , SlackConversation.repliesReqOldest = Just tenDaysAgo
                }
        (`runReaderT` apiConfig) $ do
          fetchPage <- Slack.repliesFetchAll req
          void . iterateUntil null $ do
            result <- either (liftIO . throwIO) return =<< fetchPage
            liftIO $ pPrint result
            return result

peepInResponseBody err = do
  {- Uncomment these lines when you want to see the JSON in the reponse body.
  case err of
    Slack.ServantError (DecodeFailure _ res) ->
      BL.putStrLn $ responseBody res
    _ -> return ()
  -}
  return ()