packages feed

sauron-0.0.0.0: src/Sauron/Top/Tweet.hs

{- |
Copyright: (c) 2022 Dmitrii Kovanikov
SPDX-License-Identifier: MPL-2.0
Maintainer: Dmitrii Kovanikov <kovanikov@gmail.com>

The 'Tweet' type in the JSON API response.
-}

module Sauron.Top.Tweet
    ( Tweet (..)
    , topTweets

      -- * Internals
    , parseTime
    , showTime
    , subtractSecond
    ) where

import Data.Aeson (FromJSON (..), ToJSON (..), object, withObject, (.:), (.=))
import Data.Time.Clock (UTCTime, addUTCTime)
import Data.Time.Format (defaultTimeLocale, formatTime)
import Data.Time.Format.ISO8601 (formatParseM, iso8601Format)

import Sauron.Top.User (Username (..))

import qualified Data.Text as Text


data Tweet = Tweet
    { tweetId        :: Text
    , tweetText      :: Text
    , tweetLikeCount :: Int
    , tweetCreatedAt :: UTCTime
    } deriving stock (Show, Eq)

parseTime :: String -> Maybe UTCTime
parseTime = formatParseM iso8601Format

showTime :: UTCTime -> String
showTime = formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ"

subtractSecond :: UTCTime -> UTCTime
subtractSecond = addUTCTime (-1)

{- | Parses Tweet from the following JSON object:

@
{
  "public_metrics": {
    "retweet_count": 0,
    "reply_count": 1,
    "like_count": 1,
    "quote_count": 0
  },
  "text": "🔮 I still had some theories about the space leak...",
  "created_at": "2022-09-01T07:54:18.000Z",
  "id": "1565246655103995905"
}
@
-}
instance FromJSON Tweet where
    parseJSON = withObject "Tweet" $ \o -> do
        tweetId   <- o .: "id"
        tweetText <- o .: "text"

        createdAt <- o .: "created_at"
        tweetCreatedAt <- case parseTime createdAt of
            Nothing   -> fail $ "Error parsing time: " <> createdAt
            Just time -> pure time

        publicMetrics <- o .: "public_metrics"
        tweetLikeCount <- publicMetrics .: "like_count"

        pure Tweet{..}

instance ToJSON Tweet where
    toJSON Tweet{..} = object
        [ "id"         .= tweetId
        , "created_at" .= showTime tweetCreatedAt
        , "text"       .= tweetText
        , "public_metrics" .= object
            [ "like_count" .= tweetLikeCount
            ]
        ]

-- | Extract top N tweets and pretty format them.
topTweets :: Int -> Username -> [Tweet] -> Text
topTweets maxTweets username
    = formatTweets username
    . take maxTweets
    . sortWith (Down . tweetLikeCount)

formatTweets :: Username -> [Tweet] -> Text
formatTweets _ [] = "No tweets found"
formatTweets username tweets = foldMap formatTweet tweets
  where
    formatTweet :: Tweet -> Text
    formatTweet Tweet{..} = unlines
        [ "URL          : " <> tweetUrl
        , "Tweeted at   : " <> toText (showTime tweetCreatedAt)
        , "Likes        : " <> show tweetLikeCount
        , "Awesome text :"
        , chunkedText
        ]
      where
        tweetUrl :: Text
        tweetUrl = mconcat
            [ "https://twitter.com/"
            , unUsername username
            , "/status/"
            , tweetId
            ]

        chunkedText :: Text
        chunkedText
            = unlines
            $ map ("    " <>)
            $ Text.chunksOf 60 tweetText