core: change quoted messages types/protocol (#443)

* core: change quoted messages types/protocol

* remove comments and unused field

* rename CIQuote type

* change type for quote direction to allow unknown group member, use QuotedMsg to save received chat item

* change queries of quoted items when the sending group member is known

* refactor

* fix: make ciQuote polymorphic
This commit is contained in:
Evgeny Poberezkin
2022-03-16 13:20:47 +00:00
committed by GitHub
parent d4765bcfec
commit 148474e1ba
8 changed files with 314 additions and 257 deletions
+63 -71
View File
@@ -177,36 +177,43 @@ processChatCommand = \case
APISendMessage cType chatId mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of
CTDirect -> do
ct <- withStore $ \st -> getContact st userId chatId
sendNewMsg user ct (MCSimple mc) mc
sendNewMsg user ct (MCSimple mc) mc Nothing
CTGroup -> do
group@(Group GroupInfo {membership} _) <- withStore $ \st -> getGroup st user chatId
unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved
sendNewGroupMsg user group (MCSimple mc) mc
sendNewGroupMsg user group (MCSimple mc) mc Nothing
CTContactRequest -> pure $ chatCmdError "not supported"
APISendMessageQuote cType chatId quotedItemId mc -> withUser $ \user@User {userId} -> withChatLock $ case cType of
CTDirect -> do
(ct, ci) <- withStore $ \st -> (,) <$> getContact st userId chatId <*> getDirectChatItem st userId chatId quotedItemId
case ci of
CChatItem _ (ChatItem {meta, content}) -> do
let CIMeta {itemTs, itemSharedMsgId} = meta
(qmc, sent) <- case content of
CISndMsgContent qmc -> pure (qmc, True)
CIRcvMsgContent qmc -> pure (qmc, False)
(ct, qci) <- withStore $ \st -> (,) <$> getContact st userId chatId <*> getDirectChatItem st userId chatId quotedItemId
case qci of
CChatItem _ (ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, content = ciContent, formattedText}) -> do
case ciContent of
CISndMsgContent qmc -> send_ CIQDirectSnd True qmc
CIRcvMsgContent qmc -> send_ CIQDirectRcv False qmc
_ -> throwChatError CEInvalidQuote
let msgRef = MsgRefDirect {msgId = itemSharedMsgId, sentAt = itemTs, sent}
sendNewMsg user ct (MCQuote QuotedMsg {msgRef, content = qmc} mc) mc
where
send_ :: CIQDirection 'CTDirect -> Bool -> MsgContent -> m ChatResponse
send_ chatDir sent qmc =
let quotedItem = CIQuote {chatDir, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText}
msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Nothing}
in sendNewMsg user ct (MCQuote QuotedMsg {msgRef, content = qmc} mc) mc (Just quotedItem)
CTGroup -> do
group@(Group GroupInfo {membership} _) <- withStore $ \st -> getGroup st user chatId
unless (memberActive membership) $ throwChatError CEGroupMemberUserRemoved
withStore (\st -> getGroupChatItem st user chatId quotedItemId) >>= \case
CChatItem _ (ChatItem {chatDir, meta, content}) -> do
let CIMeta {itemTs, itemSharedMsgId} = meta
(qmc, GroupMember {memberId}) <- case (content, chatDir) of
(CISndMsgContent qmc, _) -> pure (qmc, membership)
(CIRcvMsgContent qmc, CIGroupRcv m) -> pure (qmc, m)
qci <- withStore $ \st -> getGroupChatItem st user chatId quotedItemId
case qci of
CChatItem _ (ChatItem {chatDir, meta = CIMeta {itemTs, itemSharedMsgId}, content = ciContent, formattedText}) -> do
case (ciContent, chatDir) of
(CISndMsgContent qmc, _) -> send_ CIQGroupSnd True membership qmc
(CIRcvMsgContent qmc, CIGroupRcv m) -> send_ (CIQGroupRcv $ Just m) False m qmc
_ -> throwChatError CEInvalidQuote
let msgRef = MsgRefGroup {msgId = itemSharedMsgId, sentAt = itemTs, memberId}
sendNewGroupMsg user group (MCQuote QuotedMsg {msgRef, content = qmc} mc) mc
where
send_ :: CIQDirection 'CTGroup -> Bool -> GroupMember -> MsgContent -> m ChatResponse
send_ qd sent GroupMember {memberId} content =
let quotedItem = CIQuote {chatDir = qd, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content, formattedText}
msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Just memberId}
in sendNewGroupMsg user group (MCQuote QuotedMsg {msgRef, content} mc) mc (Just quotedItem)
CTContactRequest -> pure $ chatCmdError "not supported"
APIChatRead cType chatId fromToIds -> withChatLock $ case cType of
CTDirect -> withStore (\st -> updateDirectChatItemsRead st chatId fromToIds) $> CRCmdOk
@@ -378,7 +385,7 @@ processChatCommand = \case
let fileInv = FileInvitation {fileName = takeFileName f, fileSize, fileConnReq}
SndFileTransfer {fileId} <- withStore $ \st ->
createSndFileTransfer st userId contact f fileInv agentConnId chSize
ci <- sendDirectChatItem user contact (XFile fileInv) (CISndFileInvitation fileId f)
ci <- sendDirectChatItem user contact (XFile fileInv) (CISndFileInvitation fileId f) Nothing
withStore $ \st -> updateFileTransferChatItemId st fileId $ chatItemId' ci
setActive $ ActiveC cName
pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat contact) ci
@@ -395,12 +402,10 @@ processChatCommand = \case
forM_ ms $ \(m, _, fileInv) ->
traverse (\conn -> sendDirectMessage conn (XFile fileInv) (GroupId groupId)) $ memberConn m
setActive $ ActiveG gName
createdAt <- liftIO getCurrentTime
-- this is a hack as we have multiple direct messages instead of one per group
let msg = Message {msgId = 0, direction = MDSnd, chatMsgEvent = XOk, sharedMsgId_ = Nothing, msgBody = ""}
let msg = SndMessage {msgId = 0, sharedMsgId = SharedMsgId "", msgBody = ""}
ciContent = CISndFileInvitation fileId f
ci = mkNewChatItem ciContent msg createdAt createdAt
cItem@ChatItem {meta = CIMeta {itemId}} <- saveChatItem user (CDGroupSnd gInfo) ci
cItem@ChatItem {meta = CIMeta {itemId}} <- saveSndChatItem user (CDGroupSnd gInfo) msg ciContent Nothing
withStore $ \st -> updateFileTransferChatItemId st fileId itemId
pure . CRNewChatItem $ AChatItem SCTGroup SMDSnd (GroupChat gInfo) cItem
ReceiveFile fileId filePath_ -> withUser $ \User {userId} -> do
@@ -463,15 +468,14 @@ processChatCommand = \case
connId <- withAgent $ \a -> joinConnection a cReq $ directMessage (XContact profile $ Just xContactId)
withStore $ \st -> createConnReqConnection st userId connId cReqHash xContactId
pure CRSentInvitation
sendNewMsg user ct@Contact {localDisplayName = c} msgContainer mc = do
ci <- sendDirectChatItem user ct (XMsgNew msgContainer) (CISndMsgContent mc)
sendNewMsg user ct@Contact {localDisplayName = c} msgContainer mc quotedItem = do
ci <- sendDirectChatItem user ct (XMsgNew msgContainer) (CISndMsgContent mc) quotedItem
setActive $ ActiveC c
pure . CRNewChatItem $ AChatItem SCTDirect SMDSnd (DirectChat ct) ci
sendNewGroupMsg user g@(Group gInfo@GroupInfo {localDisplayName = gName} _) msgContainer mc = do
ci <- sendGroupChatItem user g (XMsgNew msgContainer) (CISndMsgContent mc)
sendNewGroupMsg user g@(Group gInfo@GroupInfo {localDisplayName = gName} _) msgContainer mc quotedItem = do
ci <- sendGroupChatItem user g (XMsgNew msgContainer) (CISndMsgContent mc) quotedItem
setActive $ ActiveG gName
pure . CRNewChatItem $ AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci
contactMember :: Contact -> [GroupMember] -> Maybe GroupMember
contactMember Contact {contactId} =
find $ \GroupMember {memberContactId = cId, memberStatus = s} ->
@@ -675,7 +679,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
_ -> pure ()
Just ct@Contact {localDisplayName = c, contactId} -> case agentMsg of
MSG msgMeta msgBody -> do
msg@Message {chatMsgEvent} <- saveRcvMSG conn (ConnectionId connId) msgMeta msgBody
msg@RcvMessage {chatMsgEvent} <- saveRcvMSG conn (ConnectionId connId) msgMeta msgBody
withAckMessage agentConnId msgMeta $
case chatMsgEvent of
XMsgNew mc -> newContentMessage ct mc msg msgMeta
@@ -814,7 +818,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
notifyMemberConnected gInfo m
when (memberCategory m == GCPreMember) $ probeMatchingContacts ct
MSG msgMeta msgBody -> do
msg@Message {chatMsgEvent} <- saveRcvMSG conn (GroupId groupId) msgMeta msgBody
msg@RcvMessage {chatMsgEvent} <- saveRcvMSG conn (GroupId groupId) msgMeta msgBody
withAckMessage agentConnId msgMeta $
case chatMsgEvent of
XMsgNew mc -> newGroupContentMessage gInfo m mc msg msgMeta
@@ -987,7 +991,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
messageError :: Text -> m ()
messageError = toView . CRMessageError "error"
newContentMessage :: Contact -> MsgContainer -> Message -> MsgMeta -> m ()
newContentMessage :: Contact -> MsgContainer -> RcvMessage -> MsgMeta -> m ()
newContentMessage ct@Contact {localDisplayName = c} mc msg msgMeta = do
let content = mcContent mc
ci@ChatItem {formattedText} <- saveRcvChatItem user (CDDirectRcv ct) msg msgMeta (CIRcvMsgContent content)
@@ -996,7 +1000,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
showMsgToast (c <> "> ") content formattedText
setActive $ ActiveC c
newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> Message -> MsgMeta -> m ()
newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> MsgMeta -> m ()
newGroupContentMessage gInfo m@GroupMember {localDisplayName = c} mc msg msgMeta = do
let content = mcContent mc
ci@ChatItem {formattedText} <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvMsgContent content)
@@ -1005,7 +1009,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
showMsgToast ("#" <> g <> " " <> c <> "> ") content formattedText
setActive $ ActiveG g
processFileInvitation :: Contact -> FileInvitation -> Message -> MsgMeta -> m ()
processFileInvitation :: Contact -> FileInvitation -> RcvMessage -> MsgMeta -> m ()
processFileInvitation ct@Contact {localDisplayName = c} fInv msg msgMeta = do
-- TODO chunk size has to be sent as part of invitation
chSize <- asks $ fileChunkSize . config
@@ -1017,7 +1021,7 @@ processAgentMessage (Just user@User {userId, profile}) agentConnId agentMessage
showToast (c <> "> ") "wants to send a file"
setActive $ ActiveC c
processGroupFileInvitation :: GroupInfo -> GroupMember -> FileInvitation -> Message -> MsgMeta -> m ()
processGroupFileInvitation :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> MsgMeta -> m ()
processGroupFileInvitation gInfo m@GroupMember {localDisplayName = c} fInv msg msgMeta = do
chSize <- asks $ fileChunkSize . config
ft@RcvFileTransfer {fileId} <- withStore $ \st -> createRcvGroupFileTransfer st userId m fInv chSize
@@ -1315,7 +1319,7 @@ createSndMessage chatMsgEvent connOrGroupId = do
gVar <- asks idsDrg
withStore $ \st -> createNewSndMessage st gVar connOrGroupId $ \sharedMsgId ->
let msgBody = strEncode ChatMessage {msgId = Just sharedMsgId, chatMsgEvent}
in NewMessage {direction = MDSnd, chatMsgEvent, msgBody}
in NewMessage {chatMsgEvent, msgBody}
directMessage :: ChatMsgEvent -> ByteString
directMessage chatMsgEvent = strEncode ChatMessage {msgId = Nothing, chatMsgEvent}
@@ -1359,54 +1363,42 @@ sendPendingGroupMessages GroupMember {groupMemberId, localDisplayName} conn = do
Nothing -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName
Just introId -> withStore (\st -> updateIntroStatus st introId GMIntroInvForwarded)
saveRcvMSG :: ChatMonad m => Connection -> ConnOrGroupId -> MsgMeta -> MsgBody -> m Message
saveRcvMSG :: ChatMonad m => Connection -> ConnOrGroupId -> MsgMeta -> MsgBody -> m RcvMessage
saveRcvMSG Connection {connId} connOrGroupId agentMsgMeta msgBody = do
ChatMessage {msgId = sharedMsgId_, chatMsgEvent} <- liftEither $ parseChatMessage msgBody
let agentMsgId = fst $ recipient agentMsgMeta
newMsg = NewMessage {direction = MDRcv, chatMsgEvent, msgBody}
newMsg = NewMessage {chatMsgEvent, msgBody}
rcvMsgDelivery = RcvMsgDelivery {connId, agentMsgId, agentMsgMeta}
withStore $ \st -> createNewMessageAndRcvMsgDelivery st connOrGroupId newMsg sharedMsgId_ rcvMsgDelivery
sendDirectChatItem :: ChatMonad m => User -> Contact -> ChatMsgEvent -> CIContent 'MDSnd -> m (ChatItem 'CTDirect 'MDSnd)
sendDirectChatItem user ct chatMsgEvent ciContent = do
sendDirectChatItem :: ChatMonad m => User -> Contact -> ChatMsgEvent -> CIContent 'MDSnd -> Maybe (CIQuote 'CTDirect) -> m (ChatItem 'CTDirect 'MDSnd)
sendDirectChatItem user ct chatMsgEvent ciContent quotedItem = do
msg <- sendDirectContactMessage ct chatMsgEvent
saveSndChatItem user (CDDirectSnd ct) msg ciContent
saveSndChatItem user (CDDirectSnd ct) msg ciContent quotedItem
sendGroupChatItem :: ChatMonad m => User -> Group -> ChatMsgEvent -> CIContent 'MDSnd -> m (ChatItem 'CTGroup 'MDSnd)
sendGroupChatItem user (Group g ms) chatMsgEvent ciContent = do
sendGroupChatItem :: ChatMonad m => User -> Group -> ChatMsgEvent -> CIContent 'MDSnd -> Maybe (CIQuote 'CTGroup) -> m (ChatItem 'CTGroup 'MDSnd)
sendGroupChatItem user (Group g ms) chatMsgEvent ciContent quotedItem = do
msg <- sendGroupMessage g ms chatMsgEvent
saveSndChatItem user (CDGroupSnd g) msg ciContent
saveSndChatItem user (CDGroupSnd g) msg ciContent quotedItem
saveSndChatItem :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> m (ChatItem c 'MDSnd)
saveSndChatItem user cd msg ciContent = do
saveSndChatItem :: ChatMonad m => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> m (ChatItem c 'MDSnd)
saveSndChatItem user cd msg@SndMessage {sharedMsgId} content quotedItem = do
createdAt <- liftIO getCurrentTime
saveChatItem user cd $ mkNewChatItem ciContent (anyMessage msg) createdAt createdAt
ciId <- withStore $ \st -> createNewSndChatItem st user cd msg content quotedItem createdAt
liftIO $ mkChatItem cd ciId content quotedItem (Just sharedMsgId) createdAt createdAt
saveRcvChatItem :: ChatMonad m => User -> ChatDirection c 'MDRcv -> Message -> MsgMeta -> CIContent 'MDRcv -> m (ChatItem c 'MDRcv)
saveRcvChatItem user cd msg MsgMeta {broker = (_, brokerTs)} ciContent = do
saveRcvChatItem :: ChatMonad m => User -> ChatDirection c 'MDRcv -> RcvMessage -> MsgMeta -> CIContent 'MDRcv -> m (ChatItem c 'MDRcv)
saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} MsgMeta {broker = (_, brokerTs)} content = do
createdAt <- liftIO getCurrentTime
saveChatItem user cd $ mkNewChatItem ciContent msg brokerTs createdAt
(ciId, quotedItem) <- withStore $ \st -> createNewRcvChatItem st user cd msg content brokerTs createdAt -- createNewChatItem st user cd $ mkNewChatItem content msg brokerTs createdAt
liftIO $ mkChatItem cd ciId content quotedItem sharedMsgId_ brokerTs createdAt
saveChatItem :: (ChatMonad m, MsgDirectionI d) => User -> ChatDirection c d -> NewChatItem d -> m (ChatItem c d)
saveChatItem user cd ci@NewChatItem {itemContent = content, itemTs, itemText, itemSharedMsgId, createdAt} = do
tz <- liftIO getCurrentTimeZone
(ciId, quotedItem) <- withStore $ \st -> createNewChatItem st user cd ci
let meta = mkCIMeta ciId itemText ciStatusNew itemSharedMsgId tz itemTs createdAt
pure $ ChatItem {chatDir = toCIDirection cd, meta, content, formattedText = parseMaybeMarkdownList itemText, quotedItem}
mkNewChatItem :: forall d. MsgDirectionI d => CIContent d -> Message -> UTCTime -> UTCTime -> NewChatItem d
mkNewChatItem itemContent Message {msgId, chatMsgEvent, sharedMsgId_ = itemSharedMsgId} itemTs createdAt =
NewChatItem
{ createdByMsgId = if msgId == 0 then Nothing else Just msgId,
itemSent = msgDirection @d,
itemTs,
itemContent,
itemText = ciContentToText itemContent,
itemStatus = ciStatusNew,
itemSharedMsgId,
itemQuotedMsg = cmToQuotedMsg chatMsgEvent,
createdAt
}
mkChatItem :: MsgDirectionI d => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIQuote c) -> Maybe SharedMsgId -> ChatItemTs -> UTCTime -> IO (ChatItem c d)
mkChatItem cd ciId content quotedItem sharedMsgId itemTs createdAt = do
tz <- getCurrentTimeZone
let itemText = ciContentToText content
meta = mkCIMeta ciId itemText ciStatusNew sharedMsgId tz itemTs createdAt
pure ChatItem {chatDir = toCIDirection cd, meta, content, formattedText = parseMaybeMarkdownList itemText, quotedItem}
allowAgentConnection :: ChatMonad m => Connection -> ConfirmationId -> ChatMsgEvent -> m ()
allowAgentConnection conn confId msg = do
@@ -1524,7 +1516,7 @@ chatCommandP =
<|> "/_get chat " *> (APIGetChat <$> chatTypeP <*> A.decimal <* A.space <*> chatPaginationP)
<|> "/_get items count=" *> (APIGetChatItems <$> A.decimal)
<|> "/_send " *> (APISendMessage <$> chatTypeP <*> A.decimal <* A.space <*> msgContentP)
<|> "/_send_quote" *> (APISendMessageQuote <$> chatTypeP <*> A.decimal <* A.space <*> A.decimal <* A.space <*> msgContentP)
<|> "/_send_quote " *> (APISendMessageQuote <$> chatTypeP <*> A.decimal <* A.space <*> A.decimal <* A.space <*> msgContentP)
<|> "/_read chat " *> (APIChatRead <$> chatTypeP <*> A.decimal <* A.space <*> ((,) <$> ("from=" *> A.decimal) <* A.space <*> ("to=" *> A.decimal)))
<|> "/_delete " *> (APIDeleteChat <$> chatTypeP <*> A.decimal)
<|> "/_accept " *> (APIAcceptContact <$> A.decimal)
+31 -36
View File
@@ -103,9 +103,6 @@ data JSONCIDirection
| JCIGroupRcv {groupMember :: GroupMember}
deriving (Generic, Show)
instance FromJSON JSONCIDirection where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "JCI"
instance ToJSON JSONCIDirection where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCI"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCI"
@@ -189,7 +186,7 @@ instance ToJSON ChatStats where
toEncoding = J.genericToEncoding J.defaultOptions
-- | type to show a mix of messages from multiple chats
data AChatItem = forall c d. AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d)
data AChatItem = forall c d. MsgDirectionI d => AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d)
deriving instance Show AChatItem
@@ -222,41 +219,46 @@ mkCIMeta itemId itemText itemStatus itemSharedMsgId tz itemTs createdAt =
instance ToJSON (CIMeta d) where toEncoding = J.genericToEncoding J.defaultOptions
data CIQuoteData = CIQuoteData
{ itemId :: Maybe ChatItemId,
data CIQuote (c :: ChatType) = CIQuote
{ chatDir :: CIQDirection c,
itemId :: Maybe ChatItemId, -- Nothing in case MsgRef references the item the user did not receive yet
sharedMsgId :: Maybe SharedMsgId, -- Nothing for the messages from the old clients
sentAt :: UTCTime,
content :: MsgContent,
formattedText :: Maybe MarkdownList
}
deriving (Show, Generic)
instance ToJSON CIQuoteData where
instance ToJSON (CIQuote c) where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data CIQuote (c :: ChatType) where
CIQuoteDirect :: CIQuoteData -> Bool -> CIQuote 'CTDirect
CIQuoteGroup :: CIQuoteData -> GroupMember -> CIQuote 'CTGroup
data CIQDirection (c :: ChatType) where
CIQDirectSnd :: CIQDirection 'CTDirect
CIQDirectRcv :: CIQDirection 'CTDirect
CIQGroupSnd :: CIQDirection 'CTGroup
CIQGroupRcv :: Maybe GroupMember -> CIQDirection 'CTGroup -- member can be Nothing in case MsgRef has memberId that the user is not notified about yet
deriving instance Show (CIQuote c)
deriving instance Show (CIQDirection c)
instance ToJSON (CIQuote c) where
toJSON = J.toJSON . jsonCIQuote
toEncoding = J.toEncoding . jsonCIQuote
instance ToJSON (CIQDirection c) where
toJSON = J.toJSON . jsonCIQDirection
toEncoding = J.toEncoding . jsonCIQDirection
data JSONCIQuote
= JCIQuoteDirect {quote :: CIQuoteData, sent :: Bool}
| JCIQuoteGroup {quote :: CIQuoteData, member :: GroupMember}
deriving (Show, Generic)
jsonCIQDirection :: CIQDirection c -> Maybe JSONCIDirection
jsonCIQDirection = \case
CIQDirectSnd -> Just JCIDirectSnd
CIQDirectRcv -> Just JCIDirectRcv
CIQGroupSnd -> Just JCIGroupSnd
CIQGroupRcv (Just m) -> Just $ JCIGroupRcv m
CIQGroupRcv Nothing -> Nothing
instance ToJSON JSONCIQuote where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCIQuote"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCIQuote"
jsonCIQuote :: CIQuote c -> JSONCIQuote
jsonCIQuote = \case
CIQuoteDirect quote sent -> JCIQuoteDirect {quote, sent}
CIQuoteGroup quote member -> JCIQuoteGroup {quote, member}
quoteMsgDirection :: CIQDirection c -> MsgDirection
quoteMsgDirection = \case
CIQDirectSnd -> MDSnd
CIQDirectRcv -> MDRcv
CIQGroupSnd -> MDSnd
CIQGroupRcv _ -> MDRcv
data CIStatus (d :: MsgDirection) where
CISSndNew :: CIStatus 'MDSnd
@@ -452,31 +454,24 @@ instance ChatTypeI 'CTDirect where chatType = SCTDirect
instance ChatTypeI 'CTGroup where chatType = SCTGroup
data NewMessage = NewMessage
{ direction :: MsgDirection,
chatMsgEvent :: ChatMsgEvent,
{ chatMsgEvent :: ChatMsgEvent,
msgBody :: MsgBody
}
deriving (Show)
data SndMessage = SndMessage
{ msgId :: MessageId,
direction :: MsgDirection,
chatMsgEvent :: ChatMsgEvent,
sharedMsgId :: SharedMsgId,
msgBody :: MsgBody
}
data Message = Message
data RcvMessage = RcvMessage
{ msgId :: MessageId,
direction :: MsgDirection,
chatMsgEvent :: ChatMsgEvent,
sharedMsgId_ :: Maybe SharedMsgId,
msgBody :: MsgBody
}
anyMessage :: SndMessage -> Message
anyMessage SndMessage {..} = Message {msgId, direction, chatMsgEvent, sharedMsgId_ = Just sharedMsgId, msgBody}
data PendingGroupMessage = PendingGroupMessage
{ msgId :: MessageId,
cmEventTag :: CMEventTag,
@@ -489,7 +484,7 @@ type MessageId = Int64
data ConnOrGroupId = ConnectionId Int64 | GroupId Int64
data MsgDirection = MDRcv | MDSnd
deriving (Show, Generic)
deriving (Eq, Show, Generic)
instance FromJSON MsgDirection where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "MD"
@@ -18,7 +18,7 @@ m20220304_msg_quotes =
ALTER TABLE chat_items ADD COLUMN quoted_shared_msg_id BLOB; -- from MessageRef in QuotedMsg
ALTER TABLE chat_items ADD COLUMN quoted_sent_at TEXT; -- from MessageRef in QuotedMsg
ALTER TABLE chat_items ADD COLUMN quoted_content TEXT; -- from MsgContent in QuotedMsg (JSON)
ALTER TABLE chat_items ADD COLUMN quoted_sent INTEGER; -- from MessageRef, 1 for sent, 0 for received, NULL for group items (or not reply messages)
ALTER TABLE chat_items ADD COLUMN quoted_sent INTEGER; -- from MessageRef, 1 for sent, 0 for received, NULL for messages without quote
ALTER TABLE chat_items ADD COLUMN quoted_member_id BLOB; -- from MessageRef
CREATE INDEX idx_chat_items_shared_msg_id ON chat_items (shared_msg_id);
|]
+12 -21
View File
@@ -33,7 +33,6 @@ import Simplex.Chat.Types
import Simplex.Chat.Util (eitherToMaybe, safeDecodeUtf8)
import Simplex.Messaging.Agent.Store.SQLite (fromTextField_)
import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON)
import Simplex.Messaging.Util ((<$?>))
data ConnectionEntity
@@ -85,28 +84,20 @@ instance ToJSON SharedMsgId where
toJSON = strToJSON
toEncoding = strToJEncoding
data MessageRef
= MsgRefDirect
{ msgId :: Maybe SharedMsgId,
sentAt :: UTCTime,
sent :: Bool
}
| MsgRefGroup
{ msgId :: Maybe SharedMsgId,
sentAt :: UTCTime,
memberId :: MemberId
}
data MsgRef = MsgRef
{ msgId :: Maybe SharedMsgId,
sentAt :: UTCTime,
sent :: Bool,
memberId :: Maybe MemberId -- must be present in all group message references, both referencing sent and received
}
deriving (Eq, Show, Generic)
msgRefJSONOpts :: J.Options
msgRefJSONOpts = taggedObjectJSON $ dropPrefix "MsgRef"
instance FromJSON MsgRef where
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
instance FromJSON MessageRef where
parseJSON = J.genericParseJSON msgRefJSONOpts
instance ToJSON MessageRef where
toJSON = J.genericToJSON msgRefJSONOpts
toEncoding = J.genericToEncoding msgRefJSONOpts
instance ToJSON MsgRef where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data ChatMessage = ChatMessage {msgId :: Maybe SharedMsgId, chatMsgEvent :: ChatMsgEvent}
deriving (Eq, Show)
@@ -141,7 +132,7 @@ data ChatMsgEvent
| XUnknown {event :: Text, params :: J.Object}
deriving (Eq, Show)
data QuotedMsg = QuotedMsg {msgRef :: MessageRef, content :: MsgContent}
data QuotedMsg = QuotedMsg {msgRef :: MsgRef, content :: MsgContent}
deriving (Eq, Show, Generic, FromJSON)
instance ToJSON QuotedMsg where
+161 -112
View File
@@ -113,7 +113,8 @@ module Simplex.Chat.Store
createPendingGroupMessage,
getPendingGroupMessages,
deletePendingGroupMessage,
createNewChatItem,
createNewSndChatItem,
createNewRcvChatItem,
getChatPreviews,
getDirectChat,
getGroupChat,
@@ -147,7 +148,7 @@ import Data.Function (on)
import Data.Functor (($>))
import Data.Int (Int64)
import Data.List (find, sortBy, sortOn)
import Data.Maybe (listToMaybe)
import Data.Maybe (isJust, listToMaybe)
import Data.Ord (Down (..))
import Data.Text (Text)
import qualified Data.Text as T
@@ -2040,7 +2041,7 @@ createNewSndMessage st gVar connOrGroupId mkMessage =
"INSERT INTO messages (msg_sent, chat_msg_event, msg_body, shared_msg_id, shared_msg_id_user, created_at, updated_at) VALUES (?,?,?,?,?,?,?)"
(MDSnd, XUnknown_ "", "" :: MsgBody, sharedMsgId, Just True, createdAt, createdAt)
msgId <- insertedRowId db
let NewMessage {direction, chatMsgEvent, msgBody} = mkMessage $ SharedMsgId sharedMsgId
let NewMessage {chatMsgEvent, msgBody} = mkMessage $ SharedMsgId sharedMsgId
DB.execute
db
[sql|
@@ -2048,8 +2049,8 @@ createNewSndMessage st gVar connOrGroupId mkMessage =
SET msg_sent = ?, chat_msg_event = ?, msg_body = ?, connection_id = ?, group_id = ?
WHERE message_id = ?
|]
(direction, toCMEventTag chatMsgEvent, msgBody, connId_, groupId_, msgId)
pure SndMessage {msgId, direction, chatMsgEvent, sharedMsgId = SharedMsgId sharedMsgId, msgBody}
(MDSnd, toCMEventTag chatMsgEvent, msgBody, connId_, groupId_, msgId)
pure SndMessage {msgId, sharedMsgId = SharedMsgId sharedMsgId, msgBody}
where
(connId_, groupId_) = case connOrGroupId of
ConnectionId connId -> (Just connId, Nothing)
@@ -2062,14 +2063,14 @@ createSndMsgDelivery st sndMsgDelivery messageId =
msgDeliveryId <- createSndMsgDelivery_ db sndMsgDelivery messageId currentTs
createMsgDeliveryEvent_ db msgDeliveryId MDSSndAgent currentTs
createNewMessageAndRcvMsgDelivery :: MonadUnliftIO m => SQLiteStore -> ConnOrGroupId -> NewMessage -> Maybe SharedMsgId -> RcvMsgDelivery -> m Message
createNewMessageAndRcvMsgDelivery st connOrGroupId NewMessage {direction, chatMsgEvent, msgBody} sharedMsgId_ RcvMsgDelivery {connId, agentMsgId, agentMsgMeta} =
createNewMessageAndRcvMsgDelivery :: MonadUnliftIO m => SQLiteStore -> ConnOrGroupId -> NewMessage -> Maybe SharedMsgId -> RcvMsgDelivery -> m RcvMessage
createNewMessageAndRcvMsgDelivery st connOrGroupId NewMessage {chatMsgEvent, msgBody} sharedMsgId_ RcvMsgDelivery {connId, agentMsgId, agentMsgMeta} =
liftIO . withTransaction st $ \db -> do
currentTs <- getCurrentTime
DB.execute
db
"INSERT INTO messages (msg_sent, chat_msg_event, msg_body, created_at, updated_at, connection_id, group_id, shared_msg_id) VALUES (?,?,?,?,?,?,?,?)"
(direction, toCMEventTag chatMsgEvent, msgBody, currentTs, currentTs, connId_, groupId_, sharedMsgId_)
(MDRcv, toCMEventTag chatMsgEvent, msgBody, currentTs, currentTs, connId_, groupId_, sharedMsgId_)
msgId <- insertedRowId db
DB.execute
db
@@ -2077,7 +2078,7 @@ createNewMessageAndRcvMsgDelivery st connOrGroupId NewMessage {direction, chatMs
(msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, snd $ broker agentMsgMeta, currentTs, currentTs)
msgDeliveryId <- insertedRowId db
createMsgDeliveryEvent_ db msgDeliveryId MDSRcvAgent currentTs
pure Message {msgId, direction, chatMsgEvent, sharedMsgId_, msgBody}
pure RcvMessage {msgId, chatMsgEvent, sharedMsgId_, msgBody}
where
(connId_, groupId_) = case connOrGroupId of
ConnectionId connId' -> (Just connId', Nothing)
@@ -2170,74 +2171,113 @@ deletePendingGroupMessage st groupMemberId messageId =
liftIO . withTransaction st $ \db ->
DB.execute db "DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ?" (groupMemberId, messageId)
createNewChatItem :: (MonadUnliftIO m, MsgDirectionI d) => SQLiteStore -> User -> ChatDirection c d -> NewChatItem d -> m (ChatItemId, Maybe (CIQuote c))
createNewChatItem st user@User {userId} chatDirection NewChatItem {createdByMsgId, itemSent, itemTs, itemContent, itemText, itemStatus, itemSharedMsgId, itemQuotedMsg, createdAt} =
liftIO . withTransaction st $ \db -> do
let itemMeta = (itemSent, itemTs, itemContent, itemText, itemStatus, itemSharedMsgId, createdAt, createdAt)
DB.execute
db
[sql|
INSERT INTO chat_items (
-- user and IDs
user_id, created_by_msg_id, contact_id, group_id, group_member_id,
-- meta
item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at,
-- quote
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((userId, createdByMsgId) :. ids :. itemMeta :. quote)
ciId <- insertedRowId db
case createdByMsgId of
Nothing -> pure ()
Just msgId ->
DB.execute
db
"INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?)"
(ciId, msgId, createdAt, createdAt)
ciRef <- getChatItemRef_ db user chatDirection itemQuotedMsg
pure (ciId, ciRef)
where
ids :: (Maybe Int64, Maybe Int64, Maybe Int64)
ids = case chatDirection of
CDDirectSnd Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDGroupSnd GroupInfo {groupId} -> (Nothing, Just groupId, Nothing)
CDGroupRcv GroupInfo {groupId} GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId)
quote :: (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId)
quote = case itemQuotedMsg of
Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing)
Just QuotedMsg {msgRef = MsgRefDirect {msgId, sentAt, sent}, content} -> (msgId, Just sentAt, Just content, Just sent, Nothing)
Just QuotedMsg {msgRef = MsgRefGroup {msgId, sentAt, memberId}, content} -> (msgId, Just sentAt, Just content, Nothing, Just memberId)
type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId)
getChatItemRef_ :: DB.Connection -> User -> ChatDirection c d -> Maybe QuotedMsg -> IO (Maybe (CIQuote c))
getChatItemRef_ db User {userId, userContactId} chatDirection = \case
Just QuotedMsg {msgRef = MsgRefDirect {msgId, sentAt, sent}, content} -> case chatDirection of
CDDirectSnd Contact {contactId} -> Just <$> getDirectChatItemRef_ sentAt content contactId msgId sent
CDDirectRcv Contact {contactId} -> Just <$> getDirectChatItemRef_ sentAt content contactId msgId (not sent)
_ -> pure Nothing
Just QuotedMsg {msgRef = MsgRefGroup {msgId, sentAt, memberId}, content} -> case chatDirection of
CDGroupSnd GroupInfo {groupId} -> getGroupChatItemRef_ sentAt content groupId msgId memberId
CDGroupRcv GroupInfo {groupId} _ -> getGroupChatItemRef_ sentAt content groupId msgId memberId
_ -> pure Nothing
_ -> pure Nothing
createNewSndChatItem :: MonadUnliftIO m => SQLiteStore -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> UTCTime -> m ChatItemId
createNewSndChatItem st user chatDirection SndMessage {msgId, sharedMsgId} ciContent quotedItem createdAt =
liftIO . withTransaction st $ \db ->
createNewChatItem_ db user chatDirection createdByMsgId (Just sharedMsgId) ciContent quoteRow createdAt createdAt
where
getDirectChatItemRef_ :: UTCTime -> MsgContent -> Int64 -> Maybe SharedMsgId -> Bool -> IO (CIQuote 'CTDirect)
getDirectChatItemRef_ sentAt content contactId msgId sent = do
ciRefDirect . listToMaybe . map fromOnly
createdByMsgId = if msgId == 0 then Nothing else Just msgId
quoteRow :: NewQuoteRow
quoteRow = case quotedItem of
Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing)
Just (CIQuote {chatDir, sharedMsgId = quotedSharedMsgId, sentAt, content}) ->
uncurry (quotedSharedMsgId,Just sentAt,Just content,,) $ case chatDir of
CIQDirectSnd -> (Just True, Nothing)
CIQDirectRcv -> (Just False, Nothing)
CIQGroupSnd -> (Just True, Nothing)
CIQGroupRcv (Just GroupMember {memberId}) -> (Just False, Just memberId)
CIQGroupRcv Nothing -> (Just False, Nothing)
createNewRcvChatItem :: MonadUnliftIO m => SQLiteStore -> User -> ChatDirection c 'MDRcv -> RcvMessage -> CIContent 'MDRcv -> UTCTime -> UTCTime -> m (ChatItemId, Maybe (CIQuote c))
createNewRcvChatItem st user chatDirection RcvMessage {msgId, chatMsgEvent, sharedMsgId_} ciContent itemTs createdAt =
liftIO . withTransaction st $ \db -> do
ciId <- createNewChatItem_ db user chatDirection (Just msgId) sharedMsgId_ ciContent quoteRow itemTs createdAt
quotedItem <- mapM (getChatItemQuote_ db user chatDirection) quotedMsg
pure (ciId, quotedItem)
where
quotedMsg = cmToQuotedMsg chatMsgEvent
quoteRow :: NewQuoteRow
quoteRow = case quotedMsg of
Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing)
Just QuotedMsg {msgRef = MsgRef {msgId = sharedMsgId, sentAt, sent, memberId}, content} ->
uncurry (sharedMsgId,Just sentAt,Just content,,) $ case chatDirection of
CDDirectRcv _ -> (Just $ not sent, Nothing)
CDGroupRcv GroupInfo {membership = GroupMember {memberId = userMemberId}} _ ->
(Just $ Just userMemberId == memberId, memberId)
createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> UTCTime -> UTCTime -> IO ChatItemId
createNewChatItem_ db User {userId} chatDirection msgId sharedMsgId ciContent quoteRow itemTs createdAt = do
DB.execute
db
[sql|
INSERT INTO chat_items (
-- user and IDs
user_id, created_by_msg_id, contact_id, group_id, group_member_id,
-- meta
item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at,
-- quote
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((userId, msgId) :. idsRow :. itemRow :. quoteRow)
ciId <- insertedRowId db
when (isJust msgId) $
DB.execute db "INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?)" (ciId, msgId, createdAt, createdAt)
pure ciId
where
itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId, UTCTime, UTCTime)
itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciStatusNew @d, sharedMsgId, createdAt, createdAt)
idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64)
idsRow = case chatDirection of
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDDirectSnd Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDGroupRcv GroupInfo {groupId} GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId)
CDGroupSnd GroupInfo {groupId} -> (Nothing, Just groupId, Nothing)
getChatItemQuote_ :: DB.Connection -> User -> ChatDirection c 'MDRcv -> QuotedMsg -> IO (CIQuote c)
getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} =
case chatDirection of
CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent)
CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} sender@GroupMember {memberId = senderMemberId} ->
case memberId of
Just mId
| mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId
| mId == senderMemberId -> (`ciQuote` CIQGroupRcv (Just sender)) <$> getGroupChatItemId_ groupId mId
| otherwise -> getGroupChatItemQuote_ groupId mId
_ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing
where
ciQuote :: Maybe ChatItemId -> CIQDirection c -> CIQuote c
ciQuote itemId dir = CIQuote dir itemId msgId sentAt content . parseMaybeMarkdownList $ msgContentText content
getDirectChatItemQuote_ :: Int64 -> Bool -> IO (CIQuote 'CTDirect)
getDirectChatItemQuote_ contactId userSent = do
ciQuoteDirect . listToMaybe . map fromOnly
<$> DB.query
db
"SELECT chat_item_id FROM chat_items WHERE user_id = ? AND contact_id = ? AND shared_msg_id = ? AND item_sent = ?"
(userId, contactId, msgId, sent)
(userId, contactId, msgId, userSent)
where
ciRefDirect :: Maybe ChatItemId -> CIQuote 'CTDirect
ciRefDirect chatItemId =
let quote = CIQuoteData chatItemId sentAt content . parseMaybeMarkdownList $ msgContentText content
in CIQuoteDirect quote sent
getGroupChatItemRef_ :: UTCTime -> MsgContent -> Int64 -> Maybe SharedMsgId -> MemberId -> IO (Maybe (CIQuote 'CTGroup))
getGroupChatItemRef_ sentAt content groupId msgId memberId = do
ciRefGroup
ciQuoteDirect :: Maybe ChatItemId -> CIQuote 'CTDirect
ciQuoteDirect = (`ciQuote` if userSent then CIQDirectSnd else CIQDirectRcv)
getUserGroupChatItemId_ :: Int64 -> IO (Maybe ChatItemId)
getUserGroupChatItemId_ groupId =
listToMaybe . map fromOnly
<$> DB.query
db
"SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id IS NULL"
(userId, groupId, msgId, MDSnd)
getGroupChatItemId_ :: Int64 -> MemberId -> IO (Maybe ChatItemId)
getGroupChatItemId_ groupId mId =
listToMaybe . map fromOnly
<$> DB.query
db
"SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND shared_msg_id = ? AND item_sent = ? AND group_member_id = ?"
(userId, groupId, msgId, MDRcv, mId)
getGroupChatItemQuote_ :: Int64 -> MemberId -> IO (CIQuote 'CTGroup)
getGroupChatItemQuote_ groupId mId = do
ciQuoteGroup
<$> DB.queryNamed
db
[sql|
SELECT i.chat_item_id,
@@ -2246,20 +2286,18 @@ getChatItemRef_ db User {userId, userContactId} chatDirection = \case
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
p.display_name, p.full_name, p.image
FROM group_members m
JOIN contact_profiles p USING (contact_profile_id)
JOIN contact_profiles p ON m.contact_profile_id = p.contact_profile_id
LEFT JOIN contacts c ON m.contact_id = c.contact_id
LEFT JOIN chat_items i ON i.group_id = m.group_id
AND (m.group_member_id = i.group_member_id OR i.group_member_id IS NULL)
WHERE (i.shared_msg_id = ? OR i.shared_msg_id IS NULL)
AND m.user_id = ? AND m.group_id = ? AND m.member_id = ?
AND m.group_member_id = i.group_member_id
AND i.shared_msg_id = :msg_id
WHERE m.user_id = :user_id AND m.group_id = :group_id AND m.member_id = :member_id
|]
(msgId, userId, groupId, memberId)
[":user_id" := userId, ":group_id" := groupId, ":member_id" := mId, ":msg_id" := msgId]
where
ciRefGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> Maybe (CIQuote 'CTGroup)
ciRefGroup [] = Nothing
ciRefGroup ((Only chatItemId :. memberRow) : _) =
let member = toGroupMember userContactId memberRow
quote = CIQuoteData chatItemId sentAt content . parseMaybeMarkdownList $ msgContentText content
in Just $ CIQuoteGroup quote member
ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup
ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing
ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow
getChatPreviews :: MonadUnliftIO m => SQLiteStore -> User -> m [AChat]
getChatPreviews st user =
@@ -2297,7 +2335,7 @@ getDirectChatPreviews_ db User {userId} = do
-- ChatItem
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.created_at,
-- DirectQuote
ri.chat_item_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM contacts ct
JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id
JOIN connections c ON c.contact_id = ct.contact_id
@@ -2332,7 +2370,7 @@ getDirectChatPreviews_ db User {userId} = do
|]
(CISRcvNew, userId, ConnReady, ConnSndReady)
where
toDirectChatPreview :: TimeZone -> ContactRow :. ConnectionRow :. ChatStatsRow :. MaybeDirectChatItemRow -> AChat
toDirectChatPreview :: TimeZone -> ContactRow :. ConnectionRow :. ChatStatsRow :. MaybeChatItemRow :. QuoteRow -> AChat
toDirectChatPreview tz (contactRow :. connRow :. statsRow :. ciRow_) =
let contact = toContact $ contactRow :. connRow
ci_ = toDirectChatItemList tz ciRow_
@@ -2362,7 +2400,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
p.display_name, p.full_name, p.image,
-- quoted ChatItem
ri.chat_item_id, i.quoted_sent_at, i.quoted_content,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id,
@@ -2450,7 +2488,7 @@ getDirectChatLast_ db User {userId} contactId count = do
-- ChatItem
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.created_at,
-- DirectQuote
ri.chat_item_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM chat_items i
LEFT JOIN chat_items ri ON i.quoted_shared_msg_id = ri.shared_msg_id
WHERE i.user_id = ? AND i.contact_id = ?
@@ -2477,7 +2515,7 @@ getDirectChatAfter_ db User {userId} contactId afterChatItemId count = do
-- ChatItem
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.created_at,
-- DirectQuote
ri.chat_item_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM chat_items i
LEFT JOIN chat_items ri ON i.quoted_shared_msg_id = ri.shared_msg_id
WHERE i.user_id = ? AND i.contact_id = ? AND i.chat_item_id > ?
@@ -2504,7 +2542,7 @@ getDirectChatBefore_ db User {userId} contactId beforeChatItemId count = do
-- ChatItem
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.created_at,
-- DirectQuote
ri.chat_item_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM chat_items i
LEFT JOIN chat_items ri ON i.quoted_shared_msg_id = ri.shared_msg_id
WHERE i.user_id = ? AND i.contact_id = ? AND i.chat_item_id < ?
@@ -2607,7 +2645,7 @@ getGroupChatLast_ db user@User {userId, userContactId} groupId count = do
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
p.display_name, p.full_name, p.image,
-- quoted ChatItem
ri.chat_item_id, i.quoted_sent_at, i.quoted_content,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id,
@@ -2646,7 +2684,7 @@ getGroupChatAfter_ db user@User {userId, userContactId} groupId afterChatItemId
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
p.display_name, p.full_name, p.image,
-- quoted ChatItem
ri.chat_item_id, i.quoted_sent_at, i.quoted_content,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id,
@@ -2685,7 +2723,7 @@ getGroupChatBefore_ db user@User {userId, userContactId} groupId beforeChatItemI
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
p.display_name, p.full_name, p.image,
-- quoted ChatItem
ri.chat_item_id, i.quoted_sent_at, i.quoted_content,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id,
@@ -2800,7 +2838,7 @@ getDirectChatItem_ db userId contactId itemId = do
-- ChatItem
i.chat_item_id, i.item_ts, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.created_at,
-- DirectQuote
ri.chat_item_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent
FROM chat_items i
LEFT JOIN chat_items ri ON i.quoted_shared_msg_id = ri.shared_msg_id
WHERE i.user_id = ? AND i.contact_id = ? AND i.chat_item_id = ?
@@ -2840,7 +2878,7 @@ getGroupChatItem st User {userId, userContactId} groupId itemId =
m.member_status, m.invited_by, m.local_display_name, m.contact_id,
p.display_name, p.full_name, p.image,
-- quoted ChatItem
ri.chat_item_id, i.quoted_sent_at, i.quoted_content,
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category,
rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id,
@@ -2918,20 +2956,27 @@ type ChatItemRow = (Int64, ChatItemTs, ACIContent, Text, ACIStatus, Maybe Shared
type MaybeChatItemRow = (Maybe Int64, Maybe ChatItemTs, Maybe ACIContent, Maybe Text, Maybe ACIStatus, Maybe SharedMsgId, Maybe UTCTime)
type QuoteDataRow = (Maybe ChatItemId, Maybe UTCTime, Maybe MsgContent)
type QuoteRow = (Maybe ChatItemId, Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool)
type DirectQuote = QuoteDataRow :. Only (Maybe Bool)
-- type DirectChatItemRow = ChatItemRow :. DirectQuoteRow
type DirectChatItemRow = ChatItemRow :. DirectQuote
-- type MaybeDirectChatItemRow = MaybeChatItemRow :. DirectQuoteRow
type MaybeDirectChatItemRow = MaybeChatItemRow :. DirectQuote
-- toQuoteData :: QuoteDataRow -> Maybe CIQuoteData
-- toQuoteData (quotedItemId, quotedSentAt, quotedMsgContent) =
-- CIQuoteData quotedItemId <$> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent)
toQuoteData :: QuoteDataRow -> Maybe CIQuoteData
toQuoteData (quotedItemId, quotedSentAt, quotedMsgContent) =
CIQuoteData quotedItemId <$> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent)
toDirectQuote :: QuoteRow -> Maybe (CIQuote 'CTDirect)
toDirectQuote qr@(_, _, _, _, quotedSent) = toQuote qr $ direction <$> quotedSent
where
direction sent = if sent then CIQDirectSnd else CIQDirectRcv
toDirectChatItem :: TimeZone -> DirectChatItemRow -> Either StoreError (CChatItem 'CTDirect)
toDirectChatItem tz ((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, createdAt) :. quoteRow :. Only quotedSent) =
toQuote :: QuoteRow -> Maybe (CIQDirection c) -> Maybe (CIQuote c)
toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _) dir =
CIQuote <$> dir <*> pure quotedItemId <*> pure quotedSharedMsgId <*> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent)
toDirectChatItem :: TimeZone -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect)
toDirectChatItem tz ((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, createdAt) :. quoteRow) =
case (itemContent, itemStatus) of
(ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus) -> Right $ cItem SMDSnd CIDirectSnd ciStatus ciContent
(ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus) -> Right $ cItem SMDRcv CIDirectRcv ciStatus ciContent
@@ -2939,24 +2984,29 @@ toDirectChatItem tz ((itemId, itemTs, itemContent, itemText, itemStatus, sharedM
where
cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTDirect d -> CIStatus d -> CIContent d -> CChatItem 'CTDirect
cItem d chatDir ciStatus content =
let quotedItem = CIQuoteDirect <$> toQuoteData quoteRow <*> quotedSent
in CChatItem d ChatItem {chatDir, meta = ciMeta ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem}
CChatItem d ChatItem {chatDir, meta = ciMeta ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toDirectQuote quoteRow}
badItem = Left $ SEBadChatItem itemId
ciMeta :: CIStatus d -> CIMeta d
ciMeta status = mkCIMeta itemId itemText status sharedMsgId tz itemTs createdAt
toDirectChatItemList :: TimeZone -> MaybeDirectChatItemRow -> [CChatItem 'CTDirect]
toDirectChatItemList tz ((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just createdAt) :. quoteRow :. Only quotedSent) =
either (const []) (: []) $ toDirectChatItem tz ((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, createdAt) :. quoteRow :. Only quotedSent)
toDirectChatItemList :: TimeZone -> MaybeChatItemRow :. QuoteRow -> [CChatItem 'CTDirect]
toDirectChatItemList tz ((Just itemId, Just itemTs, Just itemContent, Just itemText, Just itemStatus, sharedMsgId, Just createdAt) :. quoteRow) =
either (const []) (: []) $ toDirectChatItem tz ((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, createdAt) :. quoteRow)
toDirectChatItemList _ _ = []
type GroupQuote = QuoteDataRow :. MaybeGroupMemberRow
type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
type GroupChatItemRow = ChatItemRow :. MaybeGroupMemberRow :. GroupQuote
type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow
type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow :. GroupQuote
toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup)
toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_
where
direction (Just True) _ = Just CIQGroupSnd
direction (Just False) (Just member) = Just . CIQGroupRcv $ Just member
direction (Just False) Nothing = Just $ CIQGroupRcv Nothing
direction _ _ = Nothing
toGroupChatItem :: TimeZone -> Int64 -> GroupChatItemRow -> Either StoreError (CChatItem 'CTGroup)
toGroupChatItem :: TimeZone -> Int64 -> ChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow -> Either StoreError (CChatItem 'CTGroup)
toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, itemStatus, sharedMsgId, createdAt) :. memberRow_ :. quoteRow :. quotedMemberRow_) = do
let member_ = toMaybeGroupMember userContactId memberRow_
let quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_
@@ -2967,8 +3017,7 @@ toGroupChatItem tz userContactId ((itemId, itemTs, itemContent, itemText, itemSt
where
cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe GroupMember -> CChatItem 'CTGroup
cItem d chatDir ciStatus content quotedMember_ =
let quotedItem = CIQuoteGroup <$> toQuoteData quoteRow <*> quotedMember_
in CChatItem d ChatItem {chatDir, meta = ciMeta ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem}
CChatItem d ChatItem {chatDir, meta = ciMeta ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_}
badItem = Left $ SEBadChatItem itemId
ciMeta :: CIStatus d -> CIMeta d
ciMeta status = mkCIMeta itemId itemText status sharedMsgId tz itemTs createdAt
+25 -13
View File
@@ -4,6 +4,9 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
module Simplex.Chat.View where
@@ -148,12 +151,16 @@ responseToView testView = \case
testViewChat :: AChat -> [StyledString]
testViewChat (AChat _ Chat {chatItems}) = [sShow $ map toChatView chatItems]
where
toChatView :: CChatItem c -> (Int, Text)
toChatView (CChatItem dir ChatItem {meta}) = (msgDirectionInt $ toMsgDirection dir, itemText meta)
toChatView :: CChatItem c -> ((Int, Text), Maybe (Int, Text))
toChatView (CChatItem dir ChatItem {meta, quotedItem}) =
((msgDirectionInt $ toMsgDirection dir, itemText meta),) $ case quotedItem of
Nothing -> Nothing
Just CIQuote {chatDir = quoteDir, content} ->
Just (msgDirectionInt $ quoteMsgDirection quoteDir, msgContentText content)
viewErrorsSummary :: [a] -> StyledString -> [StyledString]
viewErrorsSummary summary s = if null summary then [] else [ttyError (T.pack . show $ length summary) <> s <> " (run with -c option to show each error)"]
viewChatItem :: ChatInfo c -> ChatItem c d -> [StyledString]
viewChatItem :: MsgDirectionI d => ChatInfo c -> ChatItem c d -> [StyledString]
viewChatItem chat (ChatItem {chatDir, meta, content, quotedItem}) = case chat of
DirectChat c -> case chatDir of
CIDirectSnd -> case content of
@@ -161,13 +168,13 @@ viewChatItem chat (ChatItem {chatDir, meta, content, quotedItem}) = case chat of
CISndFileInvitation fId fPath -> viewSentFileInvitation to fId fPath meta
where
to = ttyToContact' c
quote = maybe [] (directQuote True) quotedItem
CIDirectRcv -> case content of
CIRcvMsgContent mc -> viewReceivedMessage from quote meta mc
CIRcvFileInvitation ft -> viewReceivedFileInvitation from meta ft
where
from = ttyFromContact' c
quote = maybe [] (directQuote False) quotedItem
where
quote = maybe [] (directQuote chatDir) quotedItem
GroupChat g -> case chatDir of
CIGroupSnd -> case content of
CISndMsgContent mc -> viewSentMessage to quote mc meta
@@ -180,14 +187,18 @@ viewChatItem chat (ChatItem {chatDir, meta, content, quotedItem}) = case chat of
where
from = ttyFromGroup' g m
where
quote = maybe [] groupQuote quotedItem
quote = maybe [] (groupQuote g) quotedItem
_ -> []
where
directQuote :: Bool -> CIQuote 'CTDirect -> [StyledString]
directQuote msgSent (CIQuoteDirect CIQuoteData {content = qmc} qouteSent) =
quoteText qmc $ if msgSent == qouteSent then ">>" else ">"
groupQuote :: CIQuote 'CTGroup -> [StyledString]
groupQuote (CIQuoteGroup CIQuoteData {content = qmc} m) = quoteText qmc $ ttyQuotedMember m
directQuote :: forall d'. MsgDirectionI d' => CIDirection 'CTDirect d' -> CIQuote 'CTDirect -> [StyledString]
directQuote _ (CIQuote {content = qmc, chatDir = qouteDir}) =
quoteText qmc $ if toMsgDirection (msgDirection @d') == quoteMsgDirection qouteDir then ">>" else ">"
groupQuote :: GroupInfo -> CIQuote 'CTGroup -> [StyledString]
groupQuote g (CIQuote {content = qmc, chatDir = quoteDir}) = quoteText qmc . ttyQuotedMember $ sentByMember g quoteDir
sentByMember :: GroupInfo -> CIQDirection 'CTGroup -> Maybe GroupMember
sentByMember GroupInfo {membership} = \case
CIQGroupSnd -> Just membership
CIQGroupRcv m -> m
quoteText qmc sentBy = prependFirst (sentBy <> " ") $ msgPreview qmc
msgPreview = msgPlain . preview . msgContentText
where
@@ -596,8 +607,9 @@ ttyToContact' Contact {localDisplayName = c} = ttyToContact c
ttyQuotedContact :: Contact -> StyledString
ttyQuotedContact Contact {localDisplayName = c} = ttyFrom $ c <> ">"
ttyQuotedMember :: GroupMember -> StyledString
ttyQuotedMember GroupMember {localDisplayName = c} = "> " <> ttyFrom c
ttyQuotedMember :: Maybe GroupMember -> StyledString
ttyQuotedMember (Just GroupMember {localDisplayName = c}) = "> " <> ttyFrom c
ttyQuotedMember _ = "> " <> ttyFrom "?"
ttyFromContact' :: Contact -> StyledString
ttyFromContact' Contact {localDisplayName = c} = ttyFromContact c