packages feed

erebos-0.2.2: src/Erebos/Conversation.hs

{-# LANGUAGE OverloadedStrings #-}

module Erebos.Conversation (
    Message,
    makeMessage,
    messageFrom,
    messageTime,
    messageText,
    messageUnread,
    formatMessage,
    formatMessageFT,

    Conversation,
    isSameConversation,
    directMessageConversation,
    chatroomConversation,
    chatroomConversationByStateData,
    isChatroomStateConversation,
    reloadConversation,
    lookupConversations,
    lookupConversationByRef,

    conversationReference,
    conversationName,
    conversationPeer,
    conversationHistory,
    conversationHistoryChange,

    sendMessage,
    deleteConversation,
    markAllSeen,
) where

import Control.Monad.Except

import Data.List
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as T
import Data.Time.Format
import Data.Time.LocalTime

import Erebos.Chatroom
import Erebos.Conversation.Class
import Erebos.DirectMessage
import Erebos.Identity
import Erebos.State
import Erebos.Storable
import Erebos.TextFormat
import Erebos.TextFormat.Types


data Message = forall conv msg. ConversationType conv msg => Message msg Bool

makeMessage :: ConversationType conv msg => Bool -> msg -> Message
makeMessage = flip Message

withMessage :: (forall conv msg. ConversationType conv msg => msg -> a) -> Message -> a
withMessage f (Message msg _) = f msg

messageFrom :: Message -> ComposedIdentity
messageFrom = withMessage convMessageFrom

messageTime :: Message -> ZonedTime
messageTime = withMessage convMessageTime

messageText :: Message -> Maybe Text
messageText = withMessage convMessageText

messageUnread :: Message -> Bool
messageUnread (Message _ unread) = unread

formatMessage :: TimeZone -> Message -> String
formatMessage tzone = T.unpack . renderPlainText . formatMessageFT tzone

formatMessageFT :: TimeZone -> Message -> FormattedText
formatMessageFT tzone msg = mconcat $ concat
    [ [ PlainText $ T.pack $ formatTime defaultTimeLocale "[%H:%M] " $ utcToLocalTime tzone $ zonedTimeToUTC $ messageTime msg ]
    , [ maybe "<unnamed>" PlainText $ idName $ messageFrom msg ]
    , map formatExtra $ withMessage convMessageExtra msg
    , [ maybe "" ((": " <>) . PlainText) $ messageText msg ]
    ]
  where
    formatExtra UserJoined = " " <> withStyle (setForegroundColor BrightMagenta noStyle) (plainText "joined")
    formatExtra UserLeft = " " <> withStyle (setForegroundColor Magenta noStyle) (plainText "left")


data Conversation
    = DirectMessageConversation DirectMessageThread
    | ChatroomConversation ChatroomState

withConversation :: (forall conv msg. ConversationType conv msg => conv -> a) -> Conversation -> a
withConversation f (DirectMessageConversation conv) = f conv
withConversation f (ChatroomConversation conv) = f conv

withConversations :: (forall conv msg. ConversationType conv msg => Maybe conv -> conv -> a) -> Conversation -> Conversation -> a
withConversations f (DirectMessageConversation conv) (DirectMessageConversation conv') = f (Just conv) conv'
withConversations f (ChatroomConversation conv) (ChatroomConversation conv') = f (Just conv) conv'
withConversations f _ conv = withConversation (f Nothing) conv

isSameConversation :: Conversation -> Conversation -> Bool
isSameConversation (DirectMessageConversation t) (DirectMessageConversation t')
    = sameIdentity (msgPeer t) (msgPeer t')
isSameConversation (ChatroomConversation rstate) (ChatroomConversation rstate') = isSameChatroom rstate rstate'
isSameConversation _ _ = False

directMessageConversation :: MonadHead LocalState m => ComposedIdentity -> m Conversation
directMessageConversation peer = do
    createOrUpdateDirectMessagePeer peer
    (find (sameIdentity peer . msgPeer) . dmThreadList . lookupSharedValue . lsShared . fromStored <$> getLocalHead) >>= \case
        Just thread -> return $ DirectMessageConversation thread
        Nothing -> return $ DirectMessageConversation $ dmEmptyThread peer

chatroomConversation :: MonadHead LocalState m => ChatroomState -> m (Maybe Conversation)
chatroomConversation rstate = chatroomConversationByStateData (head $ roomStateData rstate)

chatroomConversationByStateData :: MonadHead LocalState m => Stored ChatroomStateData -> m (Maybe Conversation)
chatroomConversationByStateData sdata = fmap ChatroomConversation <$> findChatroomByStateData sdata

isChatroomStateConversation :: ChatroomState -> Conversation -> Bool
isChatroomStateConversation rstate (ChatroomConversation rstate') = isSameChatroom rstate rstate'
isChatroomStateConversation _ _ = False

reloadConversation :: MonadHead LocalState m => Conversation -> m Conversation
reloadConversation (DirectMessageConversation thread) = directMessageConversation (msgPeer thread)
reloadConversation cur@(ChatroomConversation rstate) =
    fromMaybe cur <$> chatroomConversation rstate

lookupConversations :: MonadHead LocalState m => m [ Conversation ]
lookupConversations = map DirectMessageConversation . dmThreadList . lookupSharedValue . lsShared . fromStored <$> getLocalHead

lookupConversationByRef :: MonadHead LocalState m => RefDigest -> m (Maybe Conversation)
lookupConversationByRef dgst = find ((dgst ==) . conversationReference) <$> lookupConversations


conversationReference :: Conversation -> RefDigest
conversationReference = withConversation convReference

conversationName :: Conversation -> Text
conversationName (DirectMessageConversation thread) = fromMaybe (T.pack "<unnamed>") $ idName $ msgPeer thread
conversationName (ChatroomConversation rstate) = fromMaybe (T.pack "<unnamed>") $ roomName =<< roomStateRoom rstate

conversationPeer :: Conversation -> Maybe ComposedIdentity
conversationPeer (DirectMessageConversation thread) = Just $ msgPeer thread
conversationPeer (ChatroomConversation _) = Nothing

conversationHistory :: Conversation -> [ Message ]
conversationHistory = withConversation $ map (uncurry Message) . snd . convMessageListSince Nothing

conversationHistoryChange :: Conversation -> Conversation -> ( Int, [ Message ] )
conversationHistoryChange = withConversations $ \since -> fmap (map (uncurry Message)) . convMessageListSince since


sendMessage :: (MonadHead LocalState m, MonadError e m, FromErebosError e) => Conversation -> Text -> m ()
sendMessage (DirectMessageConversation thread) text = sendDirectMessage (msgPeer thread) text
sendMessage (ChatroomConversation rstate) text = sendChatroomMessage rstate text

deleteConversation :: (MonadHead LocalState m, MonadError e m, FromErebosError e) => Conversation -> m ()
deleteConversation (DirectMessageConversation _) = throwOtherError "deleting direct message conversation is not supported"
deleteConversation (ChatroomConversation rstate) = deleteChatroomByStateData (head $ roomStateData rstate)

markAllSeen :: (MonadHead LocalState m, MonadError e m, FromErebosError e) => Conversation -> m ()
markAllSeen = withConversation convMarkAllSeen