Revert "core: direct messages in group (#2994)"

This reverts commit 5fddf64adb.
This commit is contained in:
spaced4ndy 2023-09-12 17:36:47 +04:00
parent 75f18bc5f0
commit 01f99baaac
18 changed files with 428 additions and 1298 deletions

View File

@ -111,7 +111,6 @@ library
Simplex.Chat.Migrations.M20230827_file_encryption Simplex.Chat.Migrations.M20230827_file_encryption
Simplex.Chat.Migrations.M20230829_connections_chat_vrange Simplex.Chat.Migrations.M20230829_connections_chat_vrange
Simplex.Chat.Migrations.M20230903_connections_to_subscribe Simplex.Chat.Migrations.M20230903_connections_to_subscribe
Simplex.Chat.Migrations.M20230904_item_direct_group_member_id
Simplex.Chat.Mobile Simplex.Chat.Mobile
Simplex.Chat.Mobile.File Simplex.Chat.Mobile.File
Simplex.Chat.Mobile.Shared Simplex.Chat.Mobile.Shared

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,7 @@ sendComposedMessage cc = sendComposedMessage' cc . contactId'
sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO () sendComposedMessage' :: ChatController -> ContactId -> Maybe ChatItemId -> MsgContent -> IO ()
sendComposedMessage' cc ctId quotedItemId msgContent = do sendComposedMessage' cc ctId quotedItemId msgContent = do
let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent} let cm = ComposedMessage {fileSource = Nothing, quotedItemId, msgContent}
sendChatCmd cc (APISendMessage (SRDirect ctId) False Nothing cm) >>= \case sendChatCmd cc (APISendMessage (ChatRef CTDirect ctId) False Nothing cm) >>= \case
CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId CRNewChatItem {} -> printLog cc CLLInfo $ "sent message to contact ID " <> show ctId
r -> putStrLn $ "unexpected send message response: " <> show r r -> putStrLn $ "unexpected send message response: " <> show r

View File

@ -34,7 +34,6 @@ import Data.List.NonEmpty (NonEmpty)
import Data.Map.Strict (Map) import Data.Map.Strict (Map)
import Data.String import Data.String
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T
import Data.Time (NominalDiffTime) import Data.Time (NominalDiffTime)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Data.Version (showVersion) import Data.Version (showVersion)
@ -242,7 +241,7 @@ data ChatCommand
| APIGetChat ChatRef ChatPagination (Maybe String) | APIGetChat ChatRef ChatPagination (Maybe String)
| APIGetChatItems ChatPagination (Maybe String) | APIGetChatItems ChatPagination (Maybe String)
| APIGetChatItemInfo ChatRef ChatItemId | APIGetChatItemInfo ChatRef ChatItemId
| APISendMessage {sendRef :: SendRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessage :: ComposedMessage} | APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessage :: ComposedMessage}
| APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent} | APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent}
| APIDeleteChatItem ChatRef ChatItemId CIDeleteMode | APIDeleteChatItem ChatRef ChatItemId CIDeleteMode
| APIDeleteMemberChatItem GroupId GroupMemberId ChatItemId | APIDeleteMemberChatItem GroupId GroupMemberId ChatItemId
@ -353,14 +352,14 @@ data ChatCommand
| AddressAutoAccept (Maybe AutoAccept) | AddressAutoAccept (Maybe AutoAccept)
| AcceptContact IncognitoEnabled ContactName | AcceptContact IncognitoEnabled ContactName
| RejectContact ContactName | RejectContact ContactName
| SendMessage SendName Text | SendMessage ChatName Text
| SendLiveMessage SendName Text | SendLiveMessage ChatName Text
| SendMessageQuote {contactName :: ContactName, msgDir :: AMsgDirection, quotedMsg :: Text, message :: Text} | SendMessageQuote {contactName :: ContactName, msgDir :: AMsgDirection, quotedMsg :: Text, message :: Text}
| SendMessageBroadcast Text -- UserId (not used in UI) | SendMessageBroadcast Text -- UserId (not used in UI)
| DeleteMessage ChatName Text | DeleteMessage ChatName Text
| DeleteMemberMessage GroupName ContactName Text | DeleteMemberMessage GroupName ContactName Text
| EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text} | EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text}
| UpdateLiveMessage {sendName :: SendName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: Text} | UpdateLiveMessage {chatName :: ChatName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: Text}
| ReactToMessage {add :: Bool, reaction :: MsgReaction, chatName :: ChatName, reactToMessage :: Text} | ReactToMessage {add :: Bool, reaction :: MsgReaction, chatName :: ChatName, reactToMessage :: Text}
| APINewGroup UserId GroupProfile | APINewGroup UserId GroupProfile
| NewGroup GroupProfile | NewGroup GroupProfile
@ -382,17 +381,17 @@ data ChatCommand
| GroupLinkMemberRole GroupName GroupMemberRole | GroupLinkMemberRole GroupName GroupMemberRole
| DeleteGroupLink GroupName | DeleteGroupLink GroupName
| ShowGroupLink GroupName | ShowGroupLink GroupName
| SendGroupMessageQuote {groupName :: GroupName, contactName_ :: Maybe ContactName, directMemberName :: Maybe ContactName, quotedMsg :: Text, message :: Text} | SendGroupMessageQuote {groupName :: GroupName, contactName_ :: Maybe ContactName, quotedMsg :: Text, message :: Text}
| LastChats (Maybe Int) -- UserId (not used in UI) | LastChats (Maybe Int) -- UserId (not used in UI)
| LastMessages (Maybe ChatName) Int (Maybe String) -- UserId (not used in UI) | LastMessages (Maybe ChatName) Int (Maybe String) -- UserId (not used in UI)
| LastChatItemId (Maybe ChatName) Int -- UserId (not used in UI) | LastChatItemId (Maybe ChatName) Int -- UserId (not used in UI)
| ShowChatItem (Maybe ChatItemId) -- UserId (not used in UI) | ShowChatItem (Maybe ChatItemId) -- UserId (not used in UI)
| ShowChatItemInfo ChatName Text | ShowChatItemInfo ChatName Text
| ShowLiveItems Bool | ShowLiveItems Bool
| SendFile SendName FilePath | SendFile ChatName FilePath
| SendImage SendName FilePath | SendImage ChatName FilePath
| ForwardFile SendName FileTransferId | ForwardFile ChatName FileTransferId
| ForwardImage SendName FileTransferId | ForwardImage ChatName FileTransferId
| SendFileDescription ChatName FilePath | SendFileDescription ChatName FilePath
| ReceiveFile {fileId :: FileTransferId, storeEncrypted :: Bool, fileInline :: Maybe Bool, filePath :: Maybe FilePath} | ReceiveFile {fileId :: FileTransferId, storeEncrypted :: Bool, fileInline :: Maybe Bool, filePath :: Maybe FilePath}
| SetFileToReceive {fileId :: FileTransferId, storeEncrypted :: Bool} | SetFileToReceive {fileId :: FileTransferId, storeEncrypted :: Bool}
@ -613,37 +612,6 @@ instance ToJSON ChatResponse where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CR" toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CR"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CR" toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CR"
data SendRef
= SRDirect ContactId
| SRGroup GroupId (Maybe GroupMemberId)
deriving (Eq, Show)
sendToChatRef :: SendRef -> ChatRef
sendToChatRef = \case
SRDirect cId -> ChatRef CTDirect cId
SRGroup gId _ -> ChatRef CTGroup gId
data SendName
= SNDirect ContactName
| SNGroup GroupName (Maybe ContactName)
deriving (Eq, Show)
sendNameStr :: SendName -> String
sendNameStr = \case
SNDirect cName -> "@" <> T.unpack cName
SNGroup gName (Just cName) -> "#" <> T.unpack gName <> " @" <> T.unpack cName
SNGroup gName Nothing -> "#" <> T.unpack gName
data SendDirection
= SDDirect Contact
| SDGroup GroupInfo [GroupMember]
deriving (Eq, Show)
sendDirToContactOrGroup :: SendDirection -> ContactOrGroup
sendDirToContactOrGroup = \case
SDDirect c -> CGContact c
SDGroup g _ -> CGGroup g
newtype UserPwd = UserPwd {unUserPwd :: Text} newtype UserPwd = UserPwd {unUserPwd :: Text}
deriving (Eq, Show) deriving (Eq, Show)
@ -959,7 +927,6 @@ data ChatErrorType
| CEAgentCommandError {message :: String} | CEAgentCommandError {message :: String}
| CEInvalidFileDescription {message :: String} | CEInvalidFileDescription {message :: String}
| CEConnectionIncognitoChangeProhibited | CEConnectionIncognitoChangeProhibited
| CEPeerChatVRangeIncompatible
| CEInternalError {message :: String} | CEInternalError {message :: String}
| CEException {message :: String} | CEException {message :: String}
deriving (Show, Exception, Generic) deriving (Show, Exception, Generic)

View File

@ -50,6 +50,16 @@ data ChatType = CTDirect | CTGroup | CTContactRequest | CTContactConnection
data ChatName = ChatName ChatType Text data ChatName = ChatName ChatType Text
deriving (Show) deriving (Show)
chatTypeStr :: ChatType -> String
chatTypeStr = \case
CTDirect -> "@"
CTGroup -> "#"
CTContactRequest -> "<@"
CTContactConnection -> ":"
chatNameStr :: ChatName -> String
chatNameStr (ChatName cType name) = chatTypeStr cType <> T.unpack name
data ChatRef = ChatRef ChatType Int64 data ChatRef = ChatRef ChatType Int64
deriving (Eq, Show, Ord) deriving (Eq, Show, Ord)
@ -138,16 +148,16 @@ instance MsgDirectionI d => ToJSON (ChatItem c d) where
data CIDirection (c :: ChatType) (d :: MsgDirection) where data CIDirection (c :: ChatType) (d :: MsgDirection) where
CIDirectSnd :: CIDirection 'CTDirect 'MDSnd CIDirectSnd :: CIDirection 'CTDirect 'MDSnd
CIDirectRcv :: CIDirection 'CTDirect 'MDRcv CIDirectRcv :: CIDirection 'CTDirect 'MDRcv
CIGroupSnd :: Maybe GroupMember -> CIDirection 'CTGroup 'MDSnd CIGroupSnd :: CIDirection 'CTGroup 'MDSnd
CIGroupRcv :: GroupMember -> MessageScope -> CIDirection 'CTGroup 'MDRcv CIGroupRcv :: GroupMember -> CIDirection 'CTGroup 'MDRcv
deriving instance Show (CIDirection c d) deriving instance Show (CIDirection c d)
data JSONCIDirection data JSONCIDirection
= JCIDirectSnd = JCIDirectSnd
| JCIDirectRcv | JCIDirectRcv
| JCIGroupSnd {directMember :: Maybe GroupMember} | JCIGroupSnd
| JCIGroupRcv {groupMember :: GroupMember, messageScope :: MessageScope} | JCIGroupRcv {groupMember :: GroupMember}
deriving (Generic, Show) deriving (Generic, Show)
instance ToJSON JSONCIDirection where instance ToJSON JSONCIDirection where
@ -162,19 +172,8 @@ jsonCIDirection :: CIDirection c d -> JSONCIDirection
jsonCIDirection = \case jsonCIDirection = \case
CIDirectSnd -> JCIDirectSnd CIDirectSnd -> JCIDirectSnd
CIDirectRcv -> JCIDirectRcv CIDirectRcv -> JCIDirectRcv
CIGroupSnd dm -> JCIGroupSnd dm CIGroupSnd -> JCIGroupSnd
CIGroupRcv m ms -> JCIGroupRcv m ms CIGroupRcv m -> JCIGroupRcv m
ciDirDirectMember :: CIDirection 'CTGroup d -> Maybe GroupMember
ciDirDirectMember = \case
CIGroupSnd dm -> dm
CIGroupRcv _ MSGroup -> Nothing
CIGroupRcv m MSDirect -> Just m
directMemberToMsgScope :: Maybe GroupMember -> MessageScope
directMemberToMsgScope = \case
Nothing -> MSGroup
Just _ -> MSDirect
data CIReactionCount = CIReactionCount {reaction :: MsgReaction, userReacted :: Bool, totalReacted :: Int} data CIReactionCount = CIReactionCount {reaction :: MsgReaction, userReacted :: Bool, totalReacted :: Int}
deriving (Show, Generic) deriving (Show, Generic)
@ -209,8 +208,8 @@ timedDeleteAt' CITimed {deleteAt} = deleteAt
chatItemMember :: GroupInfo -> ChatItem 'CTGroup d -> GroupMember chatItemMember :: GroupInfo -> ChatItem 'CTGroup d -> GroupMember
chatItemMember GroupInfo {membership} ChatItem {chatDir} = case chatDir of chatItemMember GroupInfo {membership} ChatItem {chatDir} = case chatDir of
CIGroupSnd _ -> membership CIGroupSnd -> membership
CIGroupRcv m _ -> m CIGroupRcv m -> m
ciReactionAllowed :: ChatItem c d -> Bool ciReactionAllowed :: ChatItem c d -> Bool
ciReactionAllowed ChatItem {meta = CIMeta {itemDeleted = Just _}} = False ciReactionAllowed ChatItem {meta = CIMeta {itemDeleted = Just _}} = False
@ -239,22 +238,22 @@ chatItemDeletedState ChatItem {meta = CIMeta {itemDeleted}, content} =
data ChatDirection (c :: ChatType) (d :: MsgDirection) where data ChatDirection (c :: ChatType) (d :: MsgDirection) where
CDDirectSnd :: Contact -> ChatDirection 'CTDirect 'MDSnd CDDirectSnd :: Contact -> ChatDirection 'CTDirect 'MDSnd
CDDirectRcv :: Contact -> ChatDirection 'CTDirect 'MDRcv CDDirectRcv :: Contact -> ChatDirection 'CTDirect 'MDRcv
CDGroupSnd :: GroupInfo -> Maybe GroupMember -> ChatDirection 'CTGroup 'MDSnd CDGroupSnd :: GroupInfo -> ChatDirection 'CTGroup 'MDSnd
CDGroupRcv :: GroupInfo -> GroupMember -> MessageScope -> ChatDirection 'CTGroup 'MDRcv CDGroupRcv :: GroupInfo -> GroupMember -> ChatDirection 'CTGroup 'MDRcv
toCIDirection :: ChatDirection c d -> CIDirection c d toCIDirection :: ChatDirection c d -> CIDirection c d
toCIDirection = \case toCIDirection = \case
CDDirectSnd _ -> CIDirectSnd CDDirectSnd _ -> CIDirectSnd
CDDirectRcv _ -> CIDirectRcv CDDirectRcv _ -> CIDirectRcv
CDGroupSnd _ dm -> CIGroupSnd dm CDGroupSnd _ -> CIGroupSnd
CDGroupRcv _ m ms -> CIGroupRcv m ms CDGroupRcv _ m -> CIGroupRcv m
toChatInfo :: ChatDirection c d -> ChatInfo c toChatInfo :: ChatDirection c d -> ChatInfo c
toChatInfo = \case toChatInfo = \case
CDDirectSnd c -> DirectChat c CDDirectSnd c -> DirectChat c
CDDirectRcv c -> DirectChat c CDDirectRcv c -> DirectChat c
CDGroupSnd g _ -> GroupChat g CDGroupSnd g -> GroupChat g
CDGroupRcv g _ _ -> GroupChat g CDGroupRcv g _ -> GroupChat g
data NewChatItem d = NewChatItem data NewChatItem d = NewChatItem
{ createdByMsgId :: Maybe MessageId, { createdByMsgId :: Maybe MessageId,
@ -434,39 +433,29 @@ instance ToJSON (JSONCIReaction c d) where
data CIQDirection (c :: ChatType) where data CIQDirection (c :: ChatType) where
CIQDirectSnd :: CIQDirection 'CTDirect CIQDirectSnd :: CIQDirection 'CTDirect
CIQDirectRcv :: CIQDirection 'CTDirect CIQDirectRcv :: CIQDirection 'CTDirect
CIQGroupSnd :: MessageScope -> CIQDirection 'CTGroup CIQGroupSnd :: CIQDirection 'CTGroup
CIQGroupRcv :: Maybe GroupMember -> MessageScope -> CIQDirection 'CTGroup -- member can be Nothing in case MsgRef has memberId that the user is not notified about yet 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 (CIQDirection c) deriving instance Show (CIQDirection c)
data JSONCIQDirection
= JCIQDirectSnd
| JCIQDirectRcv
| JCIQGroupSnd {messageScope :: MessageScope}
| JCIQGroupRcv {groupMember :: Maybe GroupMember, messageScope :: MessageScope}
deriving (Generic, Show)
instance ToJSON JSONCIQDirection where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCIQ"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCIQ"
instance ToJSON (CIQDirection c) where instance ToJSON (CIQDirection c) where
toJSON = J.toJSON . jsonCIQDirection toJSON = J.toJSON . jsonCIQDirection
toEncoding = J.toEncoding . jsonCIQDirection toEncoding = J.toEncoding . jsonCIQDirection
jsonCIQDirection :: CIQDirection c -> JSONCIQDirection jsonCIQDirection :: CIQDirection c -> Maybe JSONCIDirection
jsonCIQDirection = \case jsonCIQDirection = \case
CIQDirectSnd -> JCIQDirectSnd CIQDirectSnd -> Just JCIDirectSnd
CIQDirectRcv -> JCIQDirectRcv CIQDirectRcv -> Just JCIDirectRcv
CIQGroupSnd ms -> JCIQGroupSnd ms CIQGroupSnd -> Just JCIGroupSnd
CIQGroupRcv m ms -> JCIQGroupRcv m ms CIQGroupRcv (Just m) -> Just $ JCIGroupRcv m
CIQGroupRcv Nothing -> Nothing
quoteMsgDirection :: CIQDirection c -> MsgDirection quoteMsgDirection :: CIQDirection c -> MsgDirection
quoteMsgDirection = \case quoteMsgDirection = \case
CIQDirectSnd -> MDSnd CIQDirectSnd -> MDSnd
CIQDirectRcv -> MDRcv CIQDirectRcv -> MDRcv
CIQGroupSnd _ -> MDSnd CIQGroupSnd -> MDSnd
CIQGroupRcv _ _ -> MDRcv CIQGroupRcv _ -> MDRcv
data CIFile (d :: MsgDirection) = CIFile data CIFile (d :: MsgDirection) = CIFile
{ fileId :: Int64, { fileId :: Int64,

View File

@ -1,24 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Migrations.M20230904_item_direct_group_member_id where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20230904_item_direct_group_member_id :: Query
m20230904_item_direct_group_member_id =
[sql|
ALTER TABLE chat_items ADD COLUMN item_direct_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL;
ALTER TABLE chat_items ADD COLUMN quoted_message_scope TEXT;
CREATE INDEX idx_chat_items_item_direct_group_member_id ON chat_items(item_direct_group_member_id);
|]
down_m20230904_item_direct_group_member_id :: Query
down_m20230904_item_direct_group_member_id =
[sql|
DROP INDEX idx_chat_items_item_direct_group_member_id;
ALTER TABLE chat_items DROP COLUMN quoted_message_scope;
ALTER TABLE chat_items DROP COLUMN item_direct_group_member_id;
|]

View File

@ -392,9 +392,7 @@ CREATE TABLE chat_items(
timed_delete_at TEXT, timed_delete_at TEXT,
item_live INTEGER, item_live INTEGER,
item_deleted_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL, item_deleted_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
item_deleted_ts TEXT, item_deleted_ts TEXT
item_direct_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
quoted_message_scope TEXT
); );
CREATE TABLE chat_item_messages( CREATE TABLE chat_item_messages(
chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE, chat_item_id INTEGER NOT NULL REFERENCES chat_items ON DELETE CASCADE,
@ -715,6 +713,3 @@ CREATE INDEX idx_chat_items_user_id_item_status ON chat_items(
item_status item_status
); );
CREATE INDEX idx_connections_to_subscribe ON connections(to_subscribe); CREATE INDEX idx_connections_to_subscribe ON connections(to_subscribe);
CREATE INDEX idx_chat_items_item_direct_group_member_id ON chat_items(
item_direct_group_member_id
);

View File

@ -44,7 +44,7 @@ import Simplex.Chat.Types
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
import Simplex.Messaging.Version hiding (version) import Simplex.Messaging.Version hiding (version)
@ -58,10 +58,6 @@ supportedChatVRange = mkVersionRange 1 currentChatVersion
groupNoDirectVRange :: VersionRange groupNoDirectVRange :: VersionRange
groupNoDirectVRange = mkVersionRange 2 currentChatVersion groupNoDirectVRange = mkVersionRange 2 currentChatVersion
-- version range that supports private messages from members in a group
groupPrivateMessagesVRange :: VersionRange
groupPrivateMessagesVRange = mkVersionRange 2 currentChatVersion
data ConnectionEntity data ConnectionEntity
= RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact} = RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact}
| RcvGroupMsgConnection {entityConnection :: Connection, groupInfo :: GroupInfo, groupMember :: GroupMember} | RcvGroupMsgConnection {entityConnection :: Connection, groupInfo :: GroupInfo, groupMember :: GroupMember}
@ -162,28 +158,11 @@ instance ToJSON SharedMsgId where
toJSON = strToJSON toJSON = strToJSON
toEncoding = strToJEncoding toEncoding = strToJEncoding
data MessageScope = MSGroup | MSDirect
deriving (Eq, Show, Generic)
instance FromJSON MessageScope where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "MS"
instance ToJSON MessageScope where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "MS"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "MS"
instance ToField MessageScope where
toField = toField . encodeJSON
instance FromField MessageScope where
fromField = fromTextField_ decodeJSON
data MsgRef = MsgRef data MsgRef = MsgRef
{ msgId :: Maybe SharedMsgId, { msgId :: Maybe SharedMsgId,
sentAt :: UTCTime, sentAt :: UTCTime,
sent :: Bool, sent :: Bool,
memberId :: Maybe MemberId, -- must be present in all group message references, both referencing sent and received memberId :: Maybe MemberId -- must be present in all group message references, both referencing sent and received
msgScope :: Maybe MessageScope
} }
deriving (Eq, Show, Generic) deriving (Eq, Show, Generic)
@ -468,13 +447,7 @@ msgContentTag = \case
MCFile {} -> MCFile_ MCFile {} -> MCFile_
MCUnknown {tag} -> MCUnknown_ tag MCUnknown {tag} -> MCUnknown_ tag
data ExtMsgContent = ExtMsgContent data ExtMsgContent = ExtMsgContent {content :: MsgContent, file :: Maybe FileInvitation, ttl :: Maybe Int, live :: Maybe Bool}
{ content :: MsgContent,
file :: Maybe FileInvitation,
ttl :: Maybe Int,
live :: Maybe Bool,
scope :: Maybe MessageScope
}
deriving (Eq, Show) deriving (Eq, Show)
parseMsgContainer :: J.Object -> JT.Parser MsgContainer parseMsgContainer :: J.Object -> JT.Parser MsgContainer
@ -483,10 +456,10 @@ parseMsgContainer v =
<|> (v .: "forward" >>= \f -> (if f then MCForward else MCSimple) <$> mc) <|> (v .: "forward" >>= \f -> (if f then MCForward else MCSimple) <$> mc)
<|> MCSimple <$> mc <|> MCSimple <$> mc
where where
mc = ExtMsgContent <$> v .: "content" <*> v .:? "file" <*> v .:? "ttl" <*> v .:? "live" <*> v .:? "scope" mc = ExtMsgContent <$> v .: "content" <*> v .:? "file" <*> v .:? "ttl" <*> v .:? "live"
extMsgContent :: MsgContent -> Maybe FileInvitation -> ExtMsgContent extMsgContent :: MsgContent -> Maybe FileInvitation -> ExtMsgContent
extMsgContent mc file = ExtMsgContent mc file Nothing Nothing Nothing extMsgContent mc file = ExtMsgContent mc file Nothing Nothing
justTrue :: Bool -> Maybe Bool justTrue :: Bool -> Maybe Bool
justTrue True = Just True justTrue True = Just True
@ -530,7 +503,7 @@ msgContainerJSON = \case
MCSimple mc -> o $ msgContent mc MCSimple mc -> o $ msgContent mc
where where
o = JM.fromList o = JM.fromList
msgContent (ExtMsgContent c file ttl live scope) = ("file" .=? file) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("scope" .=? scope) ["content" .= c] msgContent (ExtMsgContent c file ttl live) = ("file" .=? file) $ ("ttl" .=? ttl) $ ("live" .=? live) ["content" .= c]
instance ToJSON MsgContent where instance ToJSON MsgContent where
toJSON = \case toJSON = \case

View File

@ -25,7 +25,6 @@ module Simplex.Chat.Store.Messages
createRcvMsgDeliveryEvent, createRcvMsgDeliveryEvent,
createPendingGroupMessage, createPendingGroupMessage,
getPendingGroupMessages, getPendingGroupMessages,
deleteMessage,
deletePendingGroupMessage, deletePendingGroupMessage,
deleteOldMessages, deleteOldMessages,
updateChatTs, updateChatTs,
@ -290,10 +289,6 @@ getPendingGroupMessages db groupMemberId =
pendingGroupMessage (msgId, cmEventTag, msgBody, introId_) = pendingGroupMessage (msgId, cmEventTag, msgBody, introId_) =
PendingGroupMessage {msgId, cmEventTag, msgBody, introId_} PendingGroupMessage {msgId, cmEventTag, msgBody, introId_}
deleteMessage :: DB.Connection -> MessageId -> IO ()
deleteMessage db msgId = do
DB.execute db "DELETE FROM messages WHERE message_id = ?" (Only msgId)
deletePendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> IO () deletePendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> IO ()
deletePendingGroupMessage db groupMemberId messageId = deletePendingGroupMessage db groupMemberId messageId =
DB.execute db "DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ?" (groupMemberId, messageId) DB.execute db "DELETE FROM pending_group_messages WHERE group_member_id = ? AND message_id = ?" (groupMemberId, messageId)
@ -302,7 +297,7 @@ deleteOldMessages :: DB.Connection -> UTCTime -> IO ()
deleteOldMessages db createdAtCutoff = do deleteOldMessages db createdAtCutoff = do
DB.execute db "DELETE FROM messages WHERE created_at <= ?" (Only createdAtCutoff) DB.execute db "DELETE FROM messages WHERE created_at <= ?" (Only createdAtCutoff)
type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId, Maybe MessageScope) type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId)
updateChatTs :: DB.Connection -> User -> ChatDirection c d -> UTCTime -> IO () updateChatTs :: DB.Connection -> User -> ChatDirection c d -> UTCTime -> IO ()
updateChatTs db User {userId} chatDirection chatTs = case toChatInfo chatDirection of updateChatTs db User {userId} chatDirection chatTs = case toChatInfo chatDirection of
@ -325,15 +320,14 @@ createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciCon
createdByMsgId = if msgId == 0 then Nothing else Just msgId createdByMsgId = if msgId == 0 then Nothing else Just msgId
quoteRow :: NewQuoteRow quoteRow :: NewQuoteRow
quoteRow = case quotedItem of quoteRow = case quotedItem of
Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing)
Just CIQuote {chatDir, sharedMsgId = quotedSharedMsgId, sentAt, content} -> do Just CIQuote {chatDir, sharedMsgId = quotedSharedMsgId, sentAt, content} ->
let (quotedSent, quotedMemberId, quotedMsgScope) = case chatDir of uncurry (quotedSharedMsgId,Just sentAt,Just content,,) $ case chatDir of
CIQDirectSnd -> (Just True, Nothing, Nothing) CIQDirectSnd -> (Just True, Nothing)
CIQDirectRcv -> (Just False, Nothing, Nothing) CIQDirectRcv -> (Just False, Nothing)
CIQGroupSnd messageScope -> (Just True, Nothing, Just messageScope) CIQGroupSnd -> (Just True, Nothing)
CIQGroupRcv (Just GroupMember {memberId}) messageScope -> (Just False, Just memberId, Just messageScope) CIQGroupRcv (Just GroupMember {memberId}) -> (Just False, Just memberId)
CIQGroupRcv Nothing messageScope -> (Just False, Nothing, Just messageScope) CIQGroupRcv Nothing -> (Just False, Nothing)
(quotedSharedMsgId, Just sentAt, Just content, quotedSent, quotedMemberId, quotedMsgScope)
createNewRcvChatItem :: DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c)) createNewRcvChatItem :: DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c))
createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} sharedMsgId_ ciContent timed live itemTs createdAt = do createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} sharedMsgId_ ciContent timed live itemTs createdAt = do
@ -344,20 +338,19 @@ createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent} shar
quotedMsg = cmToQuotedMsg chatMsgEvent quotedMsg = cmToQuotedMsg chatMsgEvent
quoteRow :: NewQuoteRow quoteRow :: NewQuoteRow
quoteRow = case quotedMsg of quoteRow = case quotedMsg of
Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) Nothing -> (Nothing, Nothing, Nothing, Nothing, Nothing)
Just QuotedMsg {msgRef = MsgRef {msgId = sharedMsgId, sentAt, sent, memberId, msgScope}, content} -> do Just QuotedMsg {msgRef = MsgRef {msgId = sharedMsgId, sentAt, sent, memberId}, content} ->
let (quotedSent, quotedMemberId, quotedMsgScope) = case chatDirection of uncurry (sharedMsgId,Just sentAt,Just content,,) $ case chatDirection of
CDDirectRcv _ -> (Just $ not sent, Nothing, Nothing) CDDirectRcv _ -> (Just $ not sent, Nothing)
CDGroupRcv GroupInfo {membership = GroupMember {memberId = userMemberId}} _ _ -> CDGroupRcv GroupInfo {membership = GroupMember {memberId = userMemberId}} _ ->
(Just $ Just userMemberId == memberId, memberId, msgScope) (Just $ Just userMemberId == memberId, memberId)
(sharedMsgId, Just sentAt, Just content, quotedSent, quotedMemberId, quotedMsgScope)
createNewChatItemNoMsg :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> CIContent d -> UTCTime -> UTCTime -> IO ChatItemId createNewChatItemNoMsg :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> CIContent d -> UTCTime -> UTCTime -> IO ChatItemId
createNewChatItemNoMsg db user chatDirection ciContent = createNewChatItemNoMsg db user chatDirection ciContent =
createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow Nothing False createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow Nothing False
where where
quoteRow :: NewQuoteRow quoteRow :: NewQuoteRow
quoteRow = (Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) quoteRow = (Nothing, Nothing, Nothing, Nothing, Nothing)
createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO ChatItemId createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CITimed -> Bool -> UTCTime -> UTCTime -> IO ChatItemId
createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow timed live itemTs createdAt = do createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow timed live itemTs createdAt = do
@ -366,12 +359,12 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
[sql| [sql|
INSERT INTO chat_items ( INSERT INTO chat_items (
-- user and IDs -- user and IDs
user_id, created_by_msg_id, contact_id, group_id, group_member_id, item_direct_group_member_id, user_id, created_by_msg_id, contact_id, group_id, group_member_id,
-- meta -- meta
item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at, item_live, timed_ttl, timed_delete_at, item_sent, item_ts, item_content, item_text, item_status, shared_msg_id, created_at, updated_at, item_live, timed_ttl, timed_delete_at,
-- quote -- quote
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id, quoted_message_scope quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|] |]
((userId, msgId_) :. idsRow :. itemRow :. quoteRow) ((userId, msgId_) :. idsRow :. itemRow :. quoteRow)
ciId <- insertedRowId db ciId <- insertedRowId db
@ -380,16 +373,12 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
where where
itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId) :. (UTCTime, UTCTime, Maybe Bool) :. (Maybe Int, Maybe UTCTime) itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, CIStatus d, Maybe SharedMsgId) :. (UTCTime, UTCTime, Maybe Bool) :. (Maybe Int, Maybe UTCTime)
itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciCreateStatus ciContent, sharedMsgId) :. (createdAt, createdAt, justTrue live) :. ciTimedRow timed itemRow = (msgDirection @d, itemTs, ciContent, ciContentToText ciContent, ciCreateStatus ciContent, sharedMsgId) :. (createdAt, createdAt, justTrue live) :. ciTimedRow timed
idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64) idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64)
idsRow = case chatDirection of idsRow = case chatDirection of
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing, Nothing) CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDDirectSnd Contact {contactId} -> (Just contactId, Nothing, Nothing, Nothing) CDDirectSnd Contact {contactId} -> (Just contactId, Nothing, Nothing)
CDGroupRcv GroupInfo {groupId} GroupMember {groupMemberId} messageScope -> case messageScope of CDGroupRcv GroupInfo {groupId} GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId)
MSGroup -> (Nothing, Just groupId, Just groupMemberId, Nothing) CDGroupSnd GroupInfo {groupId} -> (Nothing, Just groupId, Nothing)
MSDirect -> (Nothing, Just groupId, Just groupMemberId, Just groupMemberId)
CDGroupSnd GroupInfo {groupId} directMember -> case directMember of
Nothing -> (Nothing, Just groupId, Nothing, Nothing)
Just GroupMember {groupMemberId} -> (Nothing, Just groupId, Nothing, Just groupMemberId)
ciTimedRow :: Maybe CITimed -> (Maybe Int, Maybe UTCTime) ciTimedRow :: Maybe CITimed -> (Maybe Int, Maybe UTCTime)
ciTimedRow (Just CITimed {ttl, deleteAt}) = (Just ttl, deleteAt) ciTimedRow (Just CITimed {ttl, deleteAt}) = (Just ttl, deleteAt)
@ -399,21 +388,19 @@ insertChatItemMessage_ :: DB.Connection -> ChatItemId -> MessageId -> UTCTime ->
insertChatItemMessage_ db ciId msgId ts = DB.execute db "INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?)" (ciId, msgId, ts, ts) insertChatItemMessage_ db ciId msgId ts = DB.execute db "INSERT INTO chat_item_messages (chat_item_id, message_id, created_at, updated_at) VALUES (?,?,?,?)" (ciId, msgId, ts, ts)
getChatItemQuote_ :: DB.Connection -> User -> ChatDirection c 'MDRcv -> QuotedMsg -> IO (CIQuote c) getChatItemQuote_ :: DB.Connection -> User -> ChatDirection c 'MDRcv -> QuotedMsg -> IO (CIQuote c)
getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId, msgScope}, content} = getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} =
case chatDirection of case chatDirection of
CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent) CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent)
CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} sender@GroupMember {memberId = senderMemberId} _directMember -> CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} sender@GroupMember {memberId = senderMemberId} ->
case memberId of case memberId of
Just mId Just mId
| mId == userMemberId -> (`ciQuote` CIQGroupSnd messageScope) <$> getUserGroupChatItemId_ groupId | mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId
| mId == senderMemberId -> (`ciQuote` CIQGroupRcv (Just sender) messageScope) <$> getGroupChatItemId_ groupId mId | mId == senderMemberId -> (`ciQuote` CIQGroupRcv (Just sender)) <$> getGroupChatItemId_ groupId mId
| otherwise -> getGroupChatItemQuote_ groupId mId | otherwise -> getGroupChatItemQuote_ groupId mId
_ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing messageScope _ -> pure . ciQuote Nothing $ CIQGroupRcv Nothing
where where
ciQuote :: Maybe ChatItemId -> CIQDirection c -> CIQuote c ciQuote :: Maybe ChatItemId -> CIQDirection c -> CIQuote c
ciQuote itemId dir = CIQuote dir itemId msgId sentAt content . parseMaybeMarkdownList $ msgContentText content ciQuote itemId dir = CIQuote dir itemId msgId sentAt content . parseMaybeMarkdownList $ msgContentText content
messageScope :: MessageScope
messageScope = fromMaybe MSGroup msgScope
getDirectChatItemQuote_ :: Int64 -> Bool -> IO (CIQuote 'CTDirect) getDirectChatItemQuote_ :: Int64 -> Bool -> IO (CIQuote 'CTDirect)
getDirectChatItemQuote_ contactId userSent = do getDirectChatItemQuote_ contactId userSent = do
fmap ciQuoteDirect . maybeFirstRow fromOnly $ fmap ciQuoteDirect . maybeFirstRow fromOnly $
@ -460,8 +447,8 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
[":user_id" := userId, ":group_id" := groupId, ":member_id" := mId, ":msg_id" := msgId] [":user_id" := userId, ":group_id" := groupId, ":member_id" := mId, ":msg_id" := msgId]
where where
ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup ciQuoteGroup :: [Only (Maybe ChatItemId) :. GroupMemberRow] -> CIQuote 'CTGroup
ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing messageScope ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing
ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId $ CIQGroupRcv (Just $ toGroupMember userContactId memberRow) messageScope ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow
getChatPreviews :: DB.Connection -> User -> Bool -> IO [AChat] getChatPreviews :: DB.Connection -> User -> Bool -> IO [AChat]
getChatPreviews db user withPCC = do getChatPreviews db user withPCC = do
@ -569,7 +556,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
-- quoted ChatItem -- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, i.quoted_message_scope, ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember -- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category, 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, rm.contact_profile_id, rp.contact_profile_id, rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
@ -577,11 +564,7 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
-- deleted by GroupMember -- deleted by GroupMember
dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category, dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category,
dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences, dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences
-- direct GroupMember
dirm.group_member_id, dirm.group_id, dirm.member_id, dirm.member_role, dirm.member_category,
dirm.member_status, dirm.invited_by, dirm.local_display_name, dirm.contact_id, dirm.contact_profile_id, dirp.contact_profile_id,
dirp.display_name, dirp.full_name, dirp.image, dirp.contact_link, dirp.local_alias, dirp.preferences
FROM groups g FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
JOIN group_members mu ON mu.group_id = g.group_id JOIN group_members mu ON mu.group_id = g.group_id
@ -607,8 +590,6 @@ getGroupChatPreviews_ db User {userId, userContactId} = do
LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id) LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id)
LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id
LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id) LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id)
LEFT JOIN group_members dirm ON dirm.group_member_id = i.item_direct_group_member_id
LEFT JOIN contact_profiles dirp ON dirp.contact_profile_id = COALESCE(dirm.member_profile_id, dirm.contact_profile_id)
WHERE g.user_id = ? AND mu.contact_id = ? WHERE g.user_id = ? AND mu.contact_id = ?
ORDER BY i.item_ts DESC ORDER BY i.item_ts DESC
|] |]
@ -986,8 +967,10 @@ toDirectQuote :: QuoteRow -> Maybe (CIQuote 'CTDirect)
toDirectQuote qr@(_, _, _, _, quotedSent) = toQuote qr $ direction <$> quotedSent toDirectQuote qr@(_, _, _, _, quotedSent) = toQuote qr $ direction <$> quotedSent
where where
direction sent = if sent then CIQDirectSnd else CIQDirectRcv direction sent = if sent then CIQDirectSnd else CIQDirectRcv
toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _) dir =
CIQuote <$> dir <*> pure quotedItemId <*> pure quotedSharedMsgId <*> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent) 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)
-- this function can be changed so it never fails, not only avoid failure on invalid json -- this function can be changed so it never fails, not only avoid failure on invalid json
toDirectChatItem :: UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect) toDirectChatItem :: UTCTime -> ChatItemRow :. QuoteRow -> Either StoreError (CChatItem 'CTDirect)
@ -1030,60 +1013,37 @@ toDirectChatItemList currentTs (((Just itemId, Just itemTs, Just msgDir, Just it
either (const []) (: []) $ toDirectChatItem currentTs (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. quoteRow) either (const []) (: []) $ toDirectChatItem currentTs (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. quoteRow)
toDirectChatItemList _ _ = [] toDirectChatItemList _ _ = []
type GroupQuoteRow = (Maybe ChatItemId, Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MessageScope) type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
type GroupQuoteMemberRow = GroupQuoteRow :. MaybeGroupMemberRow type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow
type MaybeGroupChatItemRow = MaybeChatItemRow :. MaybeGroupMemberRow :. GroupQuoteMemberRow :. MaybeGroupMemberRow :. MaybeGroupMemberRow toGroupQuote :: QuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup)
toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction quotedSent quotedMember_
toGroupQuote :: GroupQuoteRow -> Maybe GroupMember -> Maybe (CIQuote 'CTGroup)
toGroupQuote qr@(_, _, _, _, quotedSent, msgScope) quotedMember_ =
toQuote qr $ direction quotedSent quotedMember_
where where
direction (Just True) _ = Just $ CIQGroupSnd messageScope direction (Just True) _ = Just CIQGroupSnd
direction (Just False) (Just member) = Just $ CIQGroupRcv (Just member) messageScope direction (Just False) (Just member) = Just . CIQGroupRcv $ Just member
direction (Just False) Nothing = Just $ CIQGroupRcv Nothing messageScope direction (Just False) Nothing = Just $ CIQGroupRcv Nothing
direction _ _ = Nothing direction _ _ = Nothing
messageScope = fromMaybe MSGroup msgScope
toQuote (quotedItemId, quotedSharedMsgId, quotedSentAt, quotedMsgContent, _, _) dir =
CIQuote <$> dir <*> pure quotedItemId <*> pure quotedSharedMsgId <*> quotedSentAt <*> quotedMsgContent <*> (parseMaybeMarkdownList . msgContentText <$> quotedMsgContent)
-- this function can be changed so it never fails, not only avoid failure on invalid json -- this function can be changed so it never fails, not only avoid failure on invalid json
toGroupChatItem :: UTCTime -> Int64 -> ChatItemRow :. MaybeGroupMemberRow :. GroupQuoteMemberRow :. MaybeGroupMemberRow :. MaybeGroupMemberRow -> Either StoreError (CChatItem 'CTGroup) toGroupChatItem :: UTCTime -> Int64 -> ChatItemRow :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow -> Either StoreError (CChatItem 'CTGroup)
toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_ :. directMemberRow_) = do toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do
chatItem $ fromRight invalid $ dbParseACIContent itemContentText chatItem $ fromRight invalid $ dbParseACIContent itemContentText
where where
member_ = toMaybeGroupMember userContactId memberRow_ member_ = toMaybeGroupMember userContactId memberRow_
quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_ quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_
deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_ deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_
directMember_ = toMaybeGroupMember userContactId directMemberRow_
invalid = ACIContent msgDir $ CIInvalidJSON itemContentText invalid = ACIContent msgDir $ CIInvalidJSON itemContentText
chatItem itemContent = case (itemContent, itemStatus, member_, fileStatus_) of chatItem itemContent = case (itemContent, itemStatus, member_, fileStatus_) of
(ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) -> (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) ->
Right $ cItem SMDSnd (CIGroupSnd directMember_) ciStatus ciContent (maybeCIFile fileStatus) Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent (maybeCIFile fileStatus)
(ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Nothing) -> (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Nothing) ->
Right $ cItem SMDSnd (CIGroupSnd directMember_) ciStatus ciContent Nothing Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent Nothing
-- read of group chat item can be refactored so that direct member is not read for rcv items:
-- if item_direct_group_member_id is equal to group_member_id, then message scope is direct
(ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Just (AFS SMDRcv fileStatus)) -> (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Just (AFS SMDRcv fileStatus)) ->
case directMember_ of Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent (maybeCIFile fileStatus)
Just directMember
| sameMember member directMember ->
Right $ cItem SMDRcv (CIGroupRcv member MSDirect) ciStatus ciContent (maybeCIFile fileStatus)
| otherwise -> badItem
Nothing ->
Right $ cItem SMDRcv (CIGroupRcv member MSGroup) ciStatus ciContent (maybeCIFile fileStatus)
(ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Nothing) -> (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Nothing) ->
case directMember_ of Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent Nothing
Just directMember
| sameMember member directMember ->
Right $ cItem SMDRcv (CIGroupRcv member MSDirect) ciStatus ciContent Nothing
| otherwise -> badItem
Nothing ->
Right $ cItem SMDRcv (CIGroupRcv member MSGroup) ciStatus ciContent Nothing
_ -> badItem _ -> badItem
sameMember :: GroupMember -> GroupMember -> Bool
sameMember GroupMember {groupMemberId = gmId1} GroupMember {groupMemberId = gmId2} = gmId1 == gmId2
maybeCIFile :: CIFileStatus d -> Maybe (CIFile d) maybeCIFile :: CIFileStatus d -> Maybe (CIFile d)
maybeCIFile fileStatus = maybeCIFile fileStatus =
case (fileId_, fileName_, fileSize_, fileProtocol_) of case (fileId_, fileName_, fileSize_, fileProtocol_) of
@ -1108,8 +1068,8 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir,
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
toGroupChatItemList :: UTCTime -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup] toGroupChatItemList :: UTCTime -> Int64 -> MaybeGroupChatItemRow -> [CChatItem 'CTGroup]
toGroupChatItemList currentTs userContactId (((Just itemId, Just itemTs, Just msgDir, Just itemContent, Just itemText, Just itemStatus, sharedMsgId) :. (Just itemDeleted, deletedTs, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_ :. directMemberRow_) = toGroupChatItemList currentTs userContactId (((Just itemId, Just itemTs, Just msgDir, Just itemContent, Just itemText, Just itemStatus, sharedMsgId) :. (Just itemDeleted, deletedTs, itemEdited, Just createdAt, Just updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) =
either (const []) (: []) $ toGroupChatItem currentTs userContactId (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_ :. directMemberRow_) either (const []) (: []) $ toGroupChatItem currentTs userContactId (((itemId, itemTs, msgDir, itemContent, itemText, itemStatus, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. (timedTTL, timedDeleteAt, itemLive) :. fileRow) :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_)
toGroupChatItemList _ _ _ = [] toGroupChatItemList _ _ _ = []
getAllChatItems :: DB.Connection -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem] getAllChatItems :: DB.Connection -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem]
@ -1524,7 +1484,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, m.member_status, m.invited_by, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
-- quoted ChatItem -- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent, i.quoted_message_scope, ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember -- quoted GroupMember
rm.group_member_id, rm.group_id, rm.member_id, rm.member_role, rm.member_category, 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, rm.contact_profile_id, rp.contact_profile_id, rm.member_status, rm.invited_by, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
@ -1532,11 +1492,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
-- deleted by GroupMember -- deleted by GroupMember
dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category, dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.member_role, dbm.member_category,
dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id, dbm.member_status, dbm.invited_by, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences, dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences
-- direct GroupMember
dirm.group_member_id, dirm.group_id, dirm.member_id, dirm.member_role, dirm.member_category,
dirm.member_status, dirm.invited_by, dirm.local_display_name, dirm.contact_id, dirm.contact_profile_id, dirp.contact_profile_id,
dirp.display_name, dirp.full_name, dirp.image, dirp.contact_link, dirp.local_alias, dirp.preferences
FROM chat_items i FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
LEFT JOIN group_members m ON m.group_member_id = i.group_member_id LEFT JOIN group_members m ON m.group_member_id = i.group_member_id
@ -1546,8 +1502,6 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id) LEFT JOIN contact_profiles rp ON rp.contact_profile_id = COALESCE(rm.member_profile_id, rm.contact_profile_id)
LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id LEFT JOIN group_members dbm ON dbm.group_member_id = i.item_deleted_by_group_member_id
LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id) LEFT JOIN contact_profiles dbp ON dbp.contact_profile_id = COALESCE(dbm.member_profile_id, dbm.contact_profile_id)
LEFT JOIN group_members dirm ON dirm.group_member_id = i.item_direct_group_member_id
LEFT JOIN contact_profiles dirp ON dirp.contact_profile_id = COALESCE(dirm.member_profile_id, dirm.contact_profile_id)
WHERE i.user_id = ? AND i.group_id = ? AND i.chat_item_id = ? WHERE i.user_id = ? AND i.group_id = ? AND i.chat_item_id = ?
|] |]
(userId, groupId, itemId) (userId, groupId, itemId)

View File

@ -79,7 +79,6 @@ import Simplex.Chat.Migrations.M20230814_indexes
import Simplex.Chat.Migrations.M20230827_file_encryption import Simplex.Chat.Migrations.M20230827_file_encryption
import Simplex.Chat.Migrations.M20230829_connections_chat_vrange import Simplex.Chat.Migrations.M20230829_connections_chat_vrange
import Simplex.Chat.Migrations.M20230903_connections_to_subscribe import Simplex.Chat.Migrations.M20230903_connections_to_subscribe
import Simplex.Chat.Migrations.M20230904_item_direct_group_member_id
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)] schemaMigrations :: [(String, Query, Maybe Query)]
@ -158,8 +157,7 @@ schemaMigrations =
("20230814_indexes", m20230814_indexes, Just down_m20230814_indexes), ("20230814_indexes", m20230814_indexes, Just down_m20230814_indexes),
("20230827_file_encryption", m20230827_file_encryption, Just down_m20230827_file_encryption), ("20230827_file_encryption", m20230827_file_encryption, Just down_m20230827_file_encryption),
("20230829_connections_chat_vrange", m20230829_connections_chat_vrange, Just down_m20230829_connections_chat_vrange), ("20230829_connections_chat_vrange", m20230829_connections_chat_vrange, Just down_m20230829_connections_chat_vrange),
("20230903_connections_to_subscribe", m20230903_connections_to_subscribe, Just down_m20230903_connections_to_subscribe), ("20230903_connections_to_subscribe", m20230903_connections_to_subscribe, Just down_m20230903_connections_to_subscribe)
("20230904_item_direct_group_member_id", m20230904_item_direct_group_member_id, Just down_m20230904_item_direct_group_member_id)
] ]
-- | The list of migrations in ascending order by date -- | The list of migrations in ascending order by date

View File

@ -73,19 +73,19 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do
Right SendMessageBroadcast {} -> True Right SendMessageBroadcast {} -> True
_ -> False _ -> False
startLiveMessage :: Either a ChatCommand -> ChatResponse -> IO () startLiveMessage :: Either a ChatCommand -> ChatResponse -> IO ()
startLiveMessage (Right (SendLiveMessage sendName msg)) (CRNewChatItem _ (AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}})) = do startLiveMessage (Right (SendLiveMessage chatName msg)) (CRNewChatItem _ (AChatItem cType SMDSnd _ ChatItem {meta = CIMeta {itemId}})) = do
whenM (isNothing <$> readTVarIO liveMessageState) $ do whenM (isNothing <$> readTVarIO liveMessageState) $ do
let s = T.unpack msg let s = T.unpack msg
int = case cType of SCTGroup -> 5000000; _ -> 3000000 :: Int int = case cType of SCTGroup -> 5000000; _ -> 3000000 :: Int
liveThreadId <- mkWeakThreadId =<< runLiveMessage int `forkFinally` const (atomically $ writeTVar liveMessageState Nothing) liveThreadId <- mkWeakThreadId =<< runLiveMessage int `forkFinally` const (atomically $ writeTVar liveMessageState Nothing)
promptThreadId <- mkWeakThreadId =<< forkIO blinkLivePrompt promptThreadId <- mkWeakThreadId =<< forkIO blinkLivePrompt
atomically $ do atomically $ do
let lm = LiveMessage {sendName, chatItemId = itemId, livePrompt = True, sentMsg = s, typedMsg = s, liveThreadId, promptThreadId} let lm = LiveMessage {chatName, chatItemId = itemId, livePrompt = True, sentMsg = s, typedMsg = s, liveThreadId, promptThreadId}
writeTVar liveMessageState (Just lm) writeTVar liveMessageState (Just lm)
modifyTVar termState $ \ts -> ts {inputString = s, inputPosition = length s, inputPrompt = liveInputPrompt lm} modifyTVar termState $ \ts -> ts {inputString = s, inputPosition = length s, inputPrompt = liveInputPrompt lm}
where where
liveInputPrompt LiveMessage {sendName = n, livePrompt} = liveInputPrompt LiveMessage {chatName = n, livePrompt} =
"> " <> sendNameStr n <> " [" <> (if livePrompt then "LIVE" else " ") <> "] " "> " <> chatNameStr n <> " [" <> (if livePrompt then "LIVE" else " ") <> "] "
runLiveMessage :: Int -> IO () runLiveMessage :: Int -> IO ()
runLiveMessage int = do runLiveMessage int = do
threadDelay int threadDelay int
@ -123,8 +123,8 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do
startLiveMessage _ _ = pure () startLiveMessage _ _ = pure ()
sendUpdatedLiveMessage :: ChatController -> String -> LiveMessage -> Bool -> IO ChatResponse sendUpdatedLiveMessage :: ChatController -> String -> LiveMessage -> Bool -> IO ChatResponse
sendUpdatedLiveMessage cc sentMsg LiveMessage {sendName, chatItemId} live = do sendUpdatedLiveMessage cc sentMsg LiveMessage {chatName, chatItemId} live = do
let cmd = UpdateLiveMessage sendName chatItemId live $ T.pack sentMsg let cmd = UpdateLiveMessage chatName chatItemId live $ T.pack sentMsg
either (CRChatCmdError Nothing) id <$> runExceptT (processChatCommand cmd) `runReaderT` cc either (CRChatCmdError Nothing) id <$> runExceptT (processChatCommand cmd) `runReaderT` cc
runTerminalInput :: ChatTerminal -> ChatController -> IO () runTerminalInput :: ChatTerminal -> ChatController -> IO ()
@ -174,14 +174,14 @@ receiveFromTTY cc@ChatController {inputQ, activeTo, currentUser, chatStore} ct@C
let s = inputString ts let s = inputString ts
lm_ <- readTVar liveMessageState lm_ <- readTVar liveMessageState
case lm_ of case lm_ of
Just LiveMessage {sendName} Just LiveMessage {chatName}
| live -> do | live -> do
writeTVar termState ts' {previousInput} writeTVar termState ts' {previousInput}
writeTBQueue inputQ $ "/live " <> sendNameStr sendName writeTBQueue inputQ $ "/live " <> chatNameStr chatName
| otherwise -> | otherwise ->
writeTVar termState ts' {inputPrompt = "> ", previousInput} writeTVar termState ts' {inputPrompt = "> ", previousInput}
where where
previousInput = sendNameStr sendName <> " " <> s previousInput = chatNameStr chatName <> " " <> s
_ _
| live -> when (isSend s) $ do | live -> when (isSend s) $ do
writeTVar termState ts' {previousInput = s} writeTVar termState ts' {previousInput = s}

View File

@ -55,7 +55,7 @@ data AutoCompleteState = ACState
} }
data LiveMessage = LiveMessage data LiveMessage = LiveMessage
{ sendName :: SendName, { chatName :: ChatName,
chatItemId :: ChatItemId, chatItemId :: ChatItemId,
livePrompt :: Bool, livePrompt :: Bool,
sentMsg :: String, sentMsg :: String,

View File

@ -330,12 +330,12 @@ data GroupSummary = GroupSummary
instance ToJSON GroupSummary where toEncoding = J.genericToEncoding J.defaultOptions instance ToJSON GroupSummary where toEncoding = J.genericToEncoding J.defaultOptions
data ContactOrGroup = CGContact Contact | CGGroup GroupInfo data ContactOrGroup = CGContact Contact | CGGroup Group
contactAndGroupIds :: ContactOrGroup -> (Maybe ContactId, Maybe GroupId) contactAndGroupIds :: ContactOrGroup -> (Maybe ContactId, Maybe GroupId)
contactAndGroupIds = \case contactAndGroupIds = \case
CGContact Contact {contactId} -> (Just contactId, Nothing) CGContact Contact {contactId} -> (Just contactId, Nothing)
CGGroup GroupInfo {groupId} -> (Nothing, Just groupId) CGGroup (Group GroupInfo {groupId} _) -> (Nothing, Just groupId)
-- TODO when more settings are added we should create another type to allow partial setting updates (with all Maybe properties) -- TODO when more settings are added we should create another type to allow partial setting updates (with all Maybe properties)
data ChatSettings = ChatSettings data ChatSettings = ChatSettings

View File

@ -322,35 +322,14 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
testViewChat :: AChat -> [StyledString] testViewChat :: AChat -> [StyledString]
testViewChat (AChat _ Chat {chatInfo, chatItems}) = [sShow $ map toChatView chatItems] testViewChat (AChat _ Chat {chatInfo, chatItems}) = [sShow $ map toChatView chatItems]
where where
toChatView :: CChatItem c -> ((Int, String, Text), Maybe (Int, String, Text), Maybe String) toChatView :: CChatItem c -> ((Int, Text), Maybe (Int, Text), Maybe String)
toChatView ci@(CChatItem dir ChatItem {chatDir, quotedItem, file}) = toChatView ci@(CChatItem dir ChatItem {quotedItem, file}) =
(item, qItem, fPath) ((msgDirectionInt $ toMsgDirection dir, testViewItem ci (chatInfoMembership chatInfo)), qItem, fPath)
where where
item =
( msgDirectionInt $ toMsgDirection dir,
directMemberName,
testViewItem ci (chatInfoMembership chatInfo)
)
directMemberName = case chatDir of
CIGroupSnd (Just GroupMember {localDisplayName = n}) -> T.unpack n
CIGroupRcv GroupMember {localDisplayName = n} MSDirect -> T.unpack n
_ -> ""
qItem = case quotedItem of qItem = case quotedItem of
Nothing -> Nothing Nothing -> Nothing
Just CIQuote {chatDir = quoteDir, content} -> Just CIQuote {chatDir = quoteDir, content} ->
Just Just (msgDirectionInt $ quoteMsgDirection quoteDir, msgContentText content)
( msgDirectionInt $ quoteMsgDirection quoteDir,
qMsgScope,
msgContentText content
)
where
qMsgScope = case quoteDir of
CIQGroupSnd ms -> msgScopeText ms
CIQGroupRcv _ ms -> msgScopeText ms
_ -> ""
msgScopeText ms = case ms of
MSGroup -> "group"
MSDirect -> "direct"
fPath = case file of fPath = case file of
Just CIFile {fileSource = Just (CryptoFile fp _)} -> Just fp Just CIFile {fileSource = Just (CryptoFile fp _)} -> Just fp
_ -> Nothing _ -> Nothing
@ -401,7 +380,7 @@ viewUsersList = mapMaybe userInfo . sortOn ldn
muted :: ChatInfo c -> CIDirection c d -> Bool muted :: ChatInfo c -> CIDirection c d -> Bool
muted chat chatDir = case (chat, chatDir) of muted chat chatDir = case (chat, chatDir) of
(DirectChat Contact {chatSettings = DisableNtfs}, CIDirectRcv) -> True (DirectChat Contact {chatSettings = DisableNtfs}, CIDirectRcv) -> True
(GroupChat GroupInfo {chatSettings = DisableNtfs}, CIGroupRcv _ _) -> True (GroupChat GroupInfo {chatSettings = DisableNtfs}, CIGroupRcv _) -> True
_ -> False _ -> False
viewGroupSubscribed :: GroupInfo -> [StyledString] viewGroupSubscribed :: GroupInfo -> [StyledString]
@ -424,9 +403,8 @@ viewChats ts tz = concatMap chatPreview . reverse
where where
chatName = case chat of chatName = case chat of
DirectChat ct -> [" " <> ttyToContact' ct] DirectChat ct -> [" " <> ttyToContact' ct]
GroupChat g -> [" " <> ttyToGroup' g] GroupChat g -> [" " <> ttyToGroup g]
_ -> [] _ -> []
ttyToGroup' g@GroupInfo {localDisplayName = n} = membershipIncognito g <> ttyTo ("#" <> n <> " ")
viewChatItem :: forall c d. MsgDirectionI d => ChatInfo c -> ChatItem c d -> Bool -> CurrentTime -> TimeZone -> [StyledString] viewChatItem :: forall c d. MsgDirectionI d => ChatInfo c -> ChatItem c d -> Bool -> CurrentTime -> TimeZone -> [StyledString]
viewChatItem chat ci@ChatItem {chatDir, meta = meta, content, quotedItem, file} doShow ts tz = viewChatItem chat ci@ChatItem {chatDir, meta = meta, content, quotedItem, file} doShow ts tz =
@ -448,20 +426,20 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta, content, quotedItem, file}
where where
quote = maybe [] (directQuote chatDir) quotedItem quote = maybe [] (directQuote chatDir) quotedItem
GroupChat g -> case chatDir of GroupChat g -> case chatDir of
CIGroupSnd directMember -> case content of CIGroupSnd -> case content of
CISndMsgContent mc -> hideLive meta $ withSndFile to $ sndMsg to quote mc CISndMsgContent mc -> hideLive meta $ withSndFile to $ sndMsg to quote mc
CISndGroupInvitation {} -> showSndItemProhibited to CISndGroupInvitation {} -> showSndItemProhibited to
_ -> showSndItem to _ -> showSndItem to
where where
to = ttyToGroup g directMember to = ttyToGroup g
CIGroupRcv m msgScope -> case content of CIGroupRcv m -> case content of
CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from quote mc CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from quote mc
CIRcvIntegrityError err -> viewRcvIntegrityError from err ts tz meta CIRcvIntegrityError err -> viewRcvIntegrityError from err ts tz meta
CIRcvGroupInvitation {} -> showRcvItemProhibited from CIRcvGroupInvitation {} -> showRcvItemProhibited from
CIRcvModerated {} -> receivedWithTime_ ts tz (ttyFromGroup g m msgScope) quote meta [plainContent content] False CIRcvModerated {} -> receivedWithTime_ ts tz (ttyFromGroup g m) quote meta [plainContent content] False
_ -> showRcvItem from _ -> showRcvItem from
where where
from = ttyFromGroup g m msgScope from = ttyFromGroup g m
where where
quote = maybe [] (groupQuote g) quotedItem quote = maybe [] (groupQuote g) quotedItem
_ -> [] _ -> []
@ -553,18 +531,18 @@ viewItemUpdate chat ChatItem {chatDir, meta = meta@CIMeta {itemEdited, itemLive}
where where
quote = maybe [] (directQuote chatDir) quotedItem quote = maybe [] (directQuote chatDir) quotedItem
GroupChat g -> case chatDir of GroupChat g -> case chatDir of
CIGroupRcv m msgScope -> case content of CIGroupRcv m -> case content of
CIRcvMsgContent mc CIRcvMsgContent mc
| itemLive == Just True && not liveItems -> [] | itemLive == Just True && not liveItems -> []
| otherwise -> viewReceivedUpdatedMessage from quote mc ts tz meta | otherwise -> viewReceivedUpdatedMessage from quote mc ts tz meta
_ -> [] _ -> []
where where
from = if itemEdited then ttyFromGroupEdited g m msgScope else ttyFromGroup g m msgScope from = if itemEdited then ttyFromGroupEdited g m else ttyFromGroup g m
CIGroupSnd directMember -> case content of CIGroupSnd -> case content of
CISndMsgContent mc -> hideLive meta $ viewSentMessage to quote mc ts tz meta CISndMsgContent mc -> hideLive meta $ viewSentMessage to quote mc ts tz meta
_ -> [] _ -> []
where where
to = if itemEdited then ttyToGroupEdited g directMember else ttyToGroup g directMember to = if itemEdited then ttyToGroupEdited g else ttyToGroup g
where where
quote = maybe [] (groupQuote g) quotedItem quote = maybe [] (groupQuote g) quotedItem
_ -> [] _ -> []
@ -589,8 +567,7 @@ viewItemDelete chat ci@ChatItem {chatDir, meta, content = deletedContent} toItem
GroupChat g -> case ciMsgContent deletedContent of GroupChat g -> case ciMsgContent deletedContent of
Just mc -> Just mc ->
let m = chatItemMember g ci let m = chatItemMember g ci
msgScope = directMemberToMsgScope $ ciDirDirectMember chatDir in viewReceivedMessage (ttyFromGroupDeleted g m deletedText_) [] mc ts tz meta
in viewReceivedMessage (ttyFromGroupDeleted g m msgScope deletedText_) [] mc ts tz meta
_ -> prohibited _ -> prohibited
_ -> prohibited _ -> prohibited
where where
@ -609,14 +586,14 @@ viewItemReaction showReactions chat CIReaction {chatDir, chatItem = CChatItem md
where where
from = ttyFromContact c from = ttyFromContact c
reactionMsg mc = quoteText mc $ if toMsgDirection md == MDSnd then ">>" else ">" reactionMsg mc = quoteText mc $ if toMsgDirection md == MDSnd then ">>" else ">"
(GroupChat g, CIGroupRcv m messageScope) -> case ciMsgContent content of (GroupChat g, CIGroupRcv m) -> case ciMsgContent content of
Just mc -> view from $ reactionMsg mc Just mc -> view from $ reactionMsg mc
_ -> [] _ -> []
where where
from = ttyFromGroup g m messageScope from = ttyFromGroup g m
reactionMsg mc = quoteText mc . ttyQuotedMember . Just $ sentByMember' g itemDir reactionMsg mc = quoteText mc . ttyQuotedMember . Just $ sentByMember' g itemDir
(_, CIDirectSnd) -> [sentText] (_, CIDirectSnd) -> [sentText]
(_, CIGroupSnd _) -> [sentText] (_, CIGroupSnd) -> [sentText]
where where
view from msg view from msg
| showReactions = viewReceivedReaction from msg reactionText ts tz sentAt | showReactions = viewReceivedReaction from msg reactionText ts tz sentAt
@ -644,13 +621,13 @@ groupQuote g CIQuote {content = qmc, chatDir = quoteDir} = quoteText qmc . ttyQu
sentByMember :: GroupInfo -> CIQDirection 'CTGroup -> Maybe GroupMember sentByMember :: GroupInfo -> CIQDirection 'CTGroup -> Maybe GroupMember
sentByMember GroupInfo {membership} = \case sentByMember GroupInfo {membership} = \case
CIQGroupSnd _ -> Just membership CIQGroupSnd -> Just membership
CIQGroupRcv m _ -> m CIQGroupRcv m -> m
sentByMember' :: GroupInfo -> CIDirection 'CTGroup d -> GroupMember sentByMember' :: GroupInfo -> CIDirection 'CTGroup d -> GroupMember
sentByMember' GroupInfo {membership} = \case sentByMember' GroupInfo {membership} = \case
CIGroupSnd _ -> membership CIGroupSnd -> membership
CIGroupRcv m _ -> m CIGroupRcv m -> m
quoteText :: MsgContent -> StyledString -> [StyledString] quoteText :: MsgContent -> StyledString -> [StyledString]
quoteText qmc sentBy = prependFirst (sentBy <> " ") $ msgPreview qmc quoteText qmc sentBy = prependFirst (sentBy <> " ") $ msgPreview qmc
@ -1342,9 +1319,8 @@ sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} =
uploadingFile :: StyledString -> AChatItem -> [StyledString] uploadingFile :: StyledString -> AChatItem -> [StyledString]
uploadingFile status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) = uploadingFile status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) =
[status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c]
uploadingFile status (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd directMember}) = uploadingFile status (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) =
let forMember = maybe "" (\GroupMember {localDisplayName = m} -> styled (colored Blue) $ " @" <> m <> " (direct)") directMember [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g]
in [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g <> forMember]
uploadingFile status _ = [status <> " uploading file"] -- shouldn't happen uploadingFile status _ = [status <> " uploading file"] -- shouldn't happen
sndFile :: SndFileTransfer -> StyledString sndFile :: SndFileTransfer -> StyledString
@ -1376,7 +1352,7 @@ savingFile' :: Bool -> AChatItem -> [StyledString]
savingFile' testView (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource = Just (CryptoFile filePath cfArgs_)}, chatDir}) = savingFile' testView (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileSource = Just (CryptoFile filePath cfArgs_)}, chatDir}) =
let from = case (chat, chatDir) of let from = case (chat, chatDir) of
(DirectChat Contact {localDisplayName = c}, CIDirectRcv) -> " from " <> ttyContact c (DirectChat Contact {localDisplayName = c}, CIDirectRcv) -> " from " <> ttyContact c
(_, CIGroupRcv GroupMember {localDisplayName = m} _) -> " from " <> ttyContact m (_, CIGroupRcv GroupMember {localDisplayName = m}) -> " from " <> ttyContact m
_ -> "" _ -> ""
in ["saving file " <> sShow fileId <> from <> " to " <> plain filePath] <> cfArgsStr in ["saving file " <> sShow fileId <> from <> " to " <> plain filePath] <> cfArgsStr
where where
@ -1390,7 +1366,7 @@ savingFile' _ _ = ["saving file"] -- shouldn't happen
receivingFile_' :: StyledString -> AChatItem -> [StyledString] receivingFile_' :: StyledString -> AChatItem -> [StyledString]
receivingFile_' status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectRcv}) = receivingFile_' status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectRcv}) =
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact c] [status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact c]
receivingFile_' status (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupRcv GroupMember {localDisplayName = m} _}) = receivingFile_' status (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupRcv GroupMember {localDisplayName = m}}) =
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact m] [status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact m]
receivingFile_' status _ = [status <> " receiving file"] -- shouldn't happen receivingFile_' status _ = [status <> " receiving file"] -- shouldn't happen
@ -1606,7 +1582,7 @@ viewChatError logLevel = \case
CEXFTPSndFile fileId aFileId e -> ["error sending XFTP file " <> sShow fileId <> ", agent file id " <> sShow aFileId <> ": " <> sShow e | logLevel == CLLError] CEXFTPSndFile fileId aFileId e -> ["error sending XFTP file " <> sShow fileId <> ", agent file id " <> sShow aFileId <> ": " <> sShow e | logLevel == CLLError]
CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"] CEFallbackToSMPProhibited fileId -> ["recipient tried to accept file " <> sShow fileId <> " via old protocol, prohibited"]
CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."] CEInlineFileProhibited _ -> ["A small file sent without acceptance - you can enable receiving such files with -f option."]
CEInvalidQuote -> ["invalid message reply"] CEInvalidQuote -> ["cannot reply to this message"]
CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemUpdate -> ["cannot update this item"]
CEInvalidChatItemDelete -> ["cannot delete this item"] CEInvalidChatItemDelete -> ["cannot delete this item"]
CEHasCurrentCall -> ["call already in progress"] CEHasCurrentCall -> ["call already in progress"]
@ -1621,7 +1597,6 @@ viewChatError logLevel = \case
CEAgentCommandError e -> ["agent command error: " <> plain e] CEAgentCommandError e -> ["agent command error: " <> plain e]
CEInvalidFileDescription e -> ["invalid file description: " <> plain e] CEInvalidFileDescription e -> ["invalid file description: " <> plain e]
CEConnectionIncognitoChangeProhibited -> ["incognito mode change prohibited"] CEConnectionIncognitoChangeProhibited -> ["incognito mode change prohibited"]
CEPeerChatVRangeIncompatible -> ["peer chat protocol version range incompatible"]
CEInternalError e -> ["internal chat error: " <> plain e] CEInternalError e -> ["internal chat error: " <> plain e]
CEException e -> ["exception: " <> plain e] CEException e -> ["exception: " <> plain e]
-- e -> ["chat error: " <> sShow e] -- e -> ["chat error: " <> sShow e]
@ -1762,24 +1737,19 @@ ttyFullGroup :: GroupInfo -> StyledString
ttyFullGroup GroupInfo {localDisplayName = g, groupProfile = GroupProfile {fullName}} = ttyFullGroup GroupInfo {localDisplayName = g, groupProfile = GroupProfile {fullName}} =
ttyGroup g <> optFullName g fullName ttyGroup g <> optFullName g fullName
ttyFromGroup :: GroupInfo -> GroupMember -> MessageScope -> StyledString ttyFromGroup :: GroupInfo -> GroupMember -> StyledString
ttyFromGroup g m ms = membershipIncognito g <> ttyFrom (fromGroup_ g m ms) ttyFromGroup g m = membershipIncognito g <> ttyFrom (fromGroup_ g m)
ttyFromGroupEdited :: GroupInfo -> GroupMember -> MessageScope -> StyledString ttyFromGroupEdited :: GroupInfo -> GroupMember -> StyledString
ttyFromGroupEdited g m ms = membershipIncognito g <> ttyFrom (fromGroup_ g m ms <> "[edited] ") ttyFromGroupEdited g m = membershipIncognito g <> ttyFrom (fromGroup_ g m <> "[edited] ")
ttyFromGroupDeleted :: GroupInfo -> GroupMember -> MessageScope -> Maybe Text -> StyledString ttyFromGroupDeleted :: GroupInfo -> GroupMember -> Maybe Text -> StyledString
ttyFromGroupDeleted g m ms deletedText_ = ttyFromGroupDeleted g m deletedText_ =
membershipIncognito g <> ttyFrom (fromGroup_ g m ms <> maybe "" (\t -> "[" <> t <> "] ") deletedText_) membershipIncognito g <> ttyFrom (fromGroup_ g m <> maybe "" (\t -> "[" <> t <> "] ") deletedText_)
fromGroup_ :: GroupInfo -> GroupMember -> MessageScope -> Text fromGroup_ :: GroupInfo -> GroupMember -> Text
fromGroup_ GroupInfo {localDisplayName = g} GroupMember {localDisplayName = m} ms = fromGroup_ GroupInfo {localDisplayName = g} GroupMember {localDisplayName = m} =
"#" <> g <> " " <> m <> fromGroupScope ms <> "> " "#" <> g <> " " <> m <> "> "
fromGroupScope :: MessageScope -> Text
fromGroupScope = \case
MSGroup -> ""
MSDirect -> " (direct)"
ttyFrom :: Text -> StyledString ttyFrom :: Text -> StyledString
ttyFrom = styled $ colored Yellow ttyFrom = styled $ colored Yellow
@ -1787,18 +1757,13 @@ ttyFrom = styled $ colored Yellow
ttyTo :: Text -> StyledString ttyTo :: Text -> StyledString
ttyTo = styled $ colored Cyan ttyTo = styled $ colored Cyan
ttyToGroup :: GroupInfo -> Maybe GroupMember -> StyledString ttyToGroup :: GroupInfo -> StyledString
ttyToGroup g@GroupInfo {localDisplayName = n} dirMem = ttyToGroup g@GroupInfo {localDisplayName = n} =
membershipIncognito g <> ttyTo ("#" <> n <> toDirectMember dirMem <> " ") membershipIncognito g <> ttyTo ("#" <> n <> " ")
ttyToGroupEdited :: GroupInfo -> Maybe GroupMember -> StyledString ttyToGroupEdited :: GroupInfo -> StyledString
ttyToGroupEdited g@GroupInfo {localDisplayName = n} dirMem = ttyToGroupEdited g@GroupInfo {localDisplayName = n} =
membershipIncognito g <> ttyTo ("#" <> n <> toDirectMember dirMem <> " [edited] ") membershipIncognito g <> ttyTo ("#" <> n <> " [edited] ")
toDirectMember :: Maybe GroupMember -> Text
toDirectMember = \case
Nothing -> ""
Just GroupMember {localDisplayName = m} -> " @" <> m <> " (direct)"
ttyFilePath :: FilePath -> StyledString ttyFilePath :: FilePath -> StyledString
ttyFilePath = plain ttyFilePath = plain

View File

@ -259,7 +259,7 @@ getTermLine cc =
Just s -> do Just s -> do
-- remove condition to always echo virtual terminal -- remove condition to always echo virtual terminal
when (printOutput cc) $ do when (printOutput cc) $ do
-- when True $ do -- when True $ do
name <- userName cc name <- userName cc
putStrLn $ name <> ": " <> s putStrLn $ name <> ": " <> s
pure s pure s

View File

@ -8,9 +8,8 @@ import ChatTests.Utils
import Control.Concurrent (threadDelay) import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (concurrently_) import Control.Concurrent.Async (concurrently_)
import Control.Monad (when) import Control.Monad (when)
import qualified Data.ByteString.Char8 as B
import qualified Data.Text as T import qualified Data.Text as T
import Simplex.Chat.Controller (ChatConfig (..), XFTPFileConfig (..)) import Simplex.Chat.Controller (ChatConfig (..))
import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Store (agentStoreFile, chatStoreFile) import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
import Simplex.Chat.Types (GroupMemberRole (..)) import Simplex.Chat.Types (GroupMemberRole (..))
@ -82,21 +81,6 @@ chatGroupTests = do
testNoDirect4 _1 _0 _1 False False False -- False False True testNoDirect4 _1 _0 _1 False False False -- False False True
testNoDirect4 _1 _1 _0 False False False testNoDirect4 _1 _1 _0 False False False
testNoDirect4 _1 _1 _1 False False False testNoDirect4 _1 _1 _1 False False False
describe "group direct messages" $ do
it "should send group direct messages" testGroupDirectMessages
it "should create group direct messages chat items" testGroupDirectMessagesItems
it "should send group direct quotes" testGroupDirectQuotes
it "should create group direct quotes chat items" testGroupDirectQuotesItems
it "should send group direct XFTP files" testGroupDirectFilesXFTP
it "should send group direct SMP files" testGroupDirectFilesSMP
it "should cancel sent group direct XFTP file" testGroupDirectCancelFileXFTP
it "should send group direct quotes with files" testGroupDirectQuotesFiles
it "should update group direct message" testGroupDirectUpdate
it "should delete group direct message" testGroupDirectDelete
it "should send group direct live message" testGroupDirectLiveMessage
it "should send group direct message reactions" testGroupDirectReactions
it "should prohibit group direct messages based on preference" testGroupDirectProhibitPreference
it "should prohibit group direct messages if peer version doesn't support" testGroupDirectProhibitNotSupported
where where
_0 = supportedChatVRange -- don't create direct connections _0 = supportedChatVRange -- don't create direct connections
_1 = groupCreateDirectVRange _1 = groupCreateDirectVRange
@ -820,7 +804,7 @@ testGroupMessageQuotedReply =
(bob <# "#team alice> hello! how are you?") (bob <# "#team alice> hello! how are you?")
(cath <# "#team alice> hello! how are you?") (cath <# "#team alice> hello! how are you?")
threadDelay 1000000 threadDelay 1000000
bob `send` "> #team >@alice (hello) hello, all good, you?" bob `send` "> #team @alice (hello) hello, all good, you?"
bob <# "#team > alice hello! how are you?" bob <# "#team > alice hello! how are you?"
bob <## " hello, all good, you?" bob <## " hello, all good, you?"
concurrently_ concurrently_
@ -835,7 +819,7 @@ testGroupMessageQuotedReply =
bob #$> ("/_get chat #1 count=2", chat', [((0, "hello! how are you?"), Nothing), ((1, "hello, all good, you?"), Just (0, "hello! how are you?"))]) bob #$> ("/_get chat #1 count=2", chat', [((0, "hello! how are you?"), Nothing), ((1, "hello, all good, you?"), Just (0, "hello! how are you?"))])
alice #$> ("/_get chat #1 count=2", chat', [((1, "hello! how are you?"), Nothing), ((0, "hello, all good, you?"), Just (1, "hello! how are you?"))]) alice #$> ("/_get chat #1 count=2", chat', [((1, "hello! how are you?"), Nothing), ((0, "hello, all good, you?"), Just (1, "hello! how are you?"))])
cath #$> ("/_get chat #1 count=2", chat', [((0, "hello! how are you?"), Nothing), ((0, "hello, all good, you?"), Just (0, "hello! how are you?"))]) cath #$> ("/_get chat #1 count=2", chat', [((0, "hello! how are you?"), Nothing), ((0, "hello, all good, you?"), Just (0, "hello! how are you?"))])
bob `send` "> #team >@bob (hello, all good) will tell more" bob `send` "> #team bob (hello, all good) will tell more"
bob <# "#team > bob hello, all good, you?" bob <# "#team > bob hello, all good, you?"
bob <## " will tell more" bob <## " will tell more"
concurrently_ concurrently_
@ -851,7 +835,7 @@ testGroupMessageQuotedReply =
alice #$> ("/_get chat #1 count=1", chat', [((0, "will tell more"), Just (0, "hello, all good, you?"))]) alice #$> ("/_get chat #1 count=1", chat', [((0, "will tell more"), Just (0, "hello, all good, you?"))])
cath #$> ("/_get chat #1 count=1", chat', [((0, "will tell more"), Just (0, "hello, all good, you?"))]) cath #$> ("/_get chat #1 count=1", chat', [((0, "will tell more"), Just (0, "hello, all good, you?"))])
threadDelay 1000000 threadDelay 1000000
cath `send` "> #team >@bob (hello) hi there!" cath `send` "> #team bob (hello) hi there!"
cath <# "#team > bob hello, all good, you?" cath <# "#team > bob hello, all good, you?"
cath <## " hi there!" cath <## " hi there!"
concurrently_ concurrently_
@ -907,7 +891,7 @@ testGroupMessageUpdate =
threadDelay 1000000 threadDelay 1000000
-- alice, bob: msg id 6, cath: msg id 5 -- alice, bob: msg id 6, cath: msg id 5
bob `send` "> #team >@alice (hey) hi alice" bob `send` "> #team @alice (hey) hi alice"
bob <# "#team > alice hey 👋" bob <# "#team > alice hey 👋"
bob <## " hi alice" bob <## " hi alice"
concurrently_ concurrently_
@ -934,7 +918,7 @@ testGroupMessageUpdate =
alice #$> ("/_update item #1 " <> msgItemId2 <> " text updating bob's message", id, "cannot update this item") alice #$> ("/_update item #1 " <> msgItemId2 <> " text updating bob's message", id, "cannot update this item")
threadDelay 1000000 threadDelay 1000000
cath `send` "> #team >@alice (greetings) greetings!" cath `send` "> #team @alice (greetings) greetings!"
cath <# "#team > alice greetings 🤝" cath <# "#team > alice greetings 🤝"
cath <## " greetings!" cath <## " greetings!"
concurrently_ concurrently_
@ -1010,6 +994,7 @@ testGroupMessageEditHistory =
alice ##> ("/_update item #1 " <> aliceItemId <> " text hey there") alice ##> ("/_update item #1 " <> aliceItemId <> " text hey there")
alice <# "#team [edited] hey there" alice <# "#team [edited] hey there"
bob <# "#team alice> [edited] hey there"
alice ##> "/item info #team hey" alice ##> "/item info #team hey"
alice <##. "sent at: " alice <##. "sent at: "
@ -1019,7 +1004,10 @@ testGroupMessageEditHistory =
alice .<## ": hey 👋" alice .<## ": hey 👋"
alice .<## ": hello!" alice .<## ": hello!"
bob ##> "/item info #team hey" bob ##> "/item info #team hey"
bob <## "message not found by text: hey" bob <##. "sent at: "
bob <##. "received at: "
bob <## "message history:"
bob .<## ": hey there"
testGroupMessageDelete :: HasCallStack => FilePath -> IO () testGroupMessageDelete :: HasCallStack => FilePath -> IO ()
testGroupMessageDelete = testGroupMessageDelete =
@ -1043,7 +1031,7 @@ testGroupMessageDelete =
threadDelay 1000000 threadDelay 1000000
-- alice: msg id 5, bob: msg id 6, cath: msg id 5 -- alice: msg id 5, bob: msg id 6, cath: msg id 5
bob `send` "> #team >@alice (hello) hi alic" bob `send` "> #team @alice (hello) hi alic"
bob <# "#team > alice hello!" bob <# "#team > alice hello!"
bob <## " hi alic" bob <## " hi alic"
concurrently_ concurrently_
@ -1072,10 +1060,14 @@ testGroupMessageDelete =
bob ##> ("/_update item #1 " <> msgItemId3 <> " text hi alice") bob ##> ("/_update item #1 " <> msgItemId3 <> " text hi alice")
bob <# "#team [edited] > alice hello!" bob <# "#team [edited] > alice hello!"
bob <## " hi alice" bob <## " hi alice"
cath <# "#team bob> [edited] > alice hello!" concurrently_
cath <## " hi alice" (alice <# "#team bob> [edited] hi alice")
( do
cath <# "#team bob> [edited] > alice hello!"
cath <## " hi alice"
)
alice #$> ("/_get chat #1 count=1", chat', [((0, "connected"), Nothing)]) alice #$> ("/_get chat #1 count=1", chat', [((0, "hi alice"), Nothing)])
bob #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((1, "hi alice"), Just (0, "hello!"))]) bob #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((1, "hi alice"), Just (0, "hello!"))])
cath #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((0, "hi alice"), Just (0, "hello!"))]) cath #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((0, "hi alice"), Just (0, "hello!"))])
@ -2694,534 +2686,3 @@ testNoGroupDirectConns4Members hostVRange mem2VRange mem3VRange mem4VRange noCon
cc1 <## ("no contact " <> name2) cc1 <## ("no contact " <> name2)
cc2 ##> ("@" <> name1 <> " hi") cc2 ##> ("@" <> name1 <> " hi")
cc2 <## ("no contact " <> name1) cc2 <## ("no contact " <> name1)
testGroupDirectMessages :: HasCallStack => FilePath -> IO ()
testGroupDirectMessages =
testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do
createGroup3 "team" alice bob cath
connectUsers alice dan
addMember "team" alice dan GRMember
dan ##> "/j team"
concurrentlyN_
[ alice <## "#team: dan joined the group",
do
dan <## "#team: you joined the group"
dan
<### [ "#team: member bob (Bob) is connected",
"#team: member cath (Catherine) is connected"
],
aliceAddedDan bob,
aliceAddedDan cath
]
alice #> "#team hi"
bob <# "#team alice> hi"
cath <# "#team alice> hi"
dan <# "#team alice> hi"
alice `send` "#team @bob hi bob"
alice <# "#team @bob (direct) hi bob"
bob <# "#team alice (direct)> hi bob"
bob `send` "#team @alice hi alice"
bob <# "#team @alice (direct) hi alice"
alice <# "#team bob (direct)> hi alice"
dan #> "#team hello"
alice <# "#team dan> hello"
bob <# "#team dan> hello"
cath <# "#team dan> hello"
bob `send` "#team @cath hi cath"
bob <# "#team @cath (direct) hi cath"
cath <# "#team bob (direct)> hi cath"
cath `send` "#team @bob hello bob"
cath <# "#team @bob (direct) hello bob"
bob <# "#team cath (direct)> hello bob"
where
aliceAddedDan :: HasCallStack => TestCC -> IO ()
aliceAddedDan cc = do
cc <## "#team: alice added dan (Daniel) to the group (connecting...)"
cc <## "#team: new member dan is connected"
testGroupDirectMessagesItems :: HasCallStack => FilePath -> IO ()
testGroupDirectMessagesItems =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
threadDelay 1000000
alice #> "#team hi"
bob <# "#team alice> hi"
cath <# "#team alice> hi"
threadDelay 1000000
alice `send` "#team @bob hi bob"
alice <# "#team @bob (direct) hi bob"
bob <# "#team alice (direct)> hi bob"
threadDelay 1000000
bob `send` "#team @alice hi alice"
bob <# "#team @alice (direct) hi alice"
alice <# "#team bob (direct)> hi alice"
threadDelay 1000000
alice #$> ("/_get chat #1 count=4", mapChat, [(0, "", "connected"), (1, "", "hi"), (1, "bob", "hi bob"), (0, "bob", "hi alice")])
bob #$> ("/_get chat #1 count=4", mapChat, [(0, "", "connected"), (0, "", "hi"), (0, "alice", "hi bob"), (1, "alice", "hi alice")])
cath #$> ("/_get chat #1 count=2", mapChat, [(0, "", "connected"), (0, "", "hi")])
where
mapChat = map (\(a, _, _) -> a) . chat'''
testGroupDirectQuotes :: HasCallStack => FilePath -> IO ()
testGroupDirectQuotes =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
alice #> "#team 1-g-a"
bob <# "#team alice> 1-g-a"
cath <# "#team alice> 1-g-a"
bob #> "#team 2-g-b"
alice <# "#team bob> 2-g-b"
cath <# "#team bob> 2-g-b"
cath #> "#team 3-g-c"
alice <# "#team cath> 3-g-c"
bob <# "#team cath> 3-g-c"
alice `send` "#team @bob 4-p-ab"
alice <# "#team @bob (direct) 4-p-ab"
bob <# "#team alice (direct)> 4-p-ab"
bob `send` "#team @alice 5-p-ba"
bob <# "#team @alice (direct) 5-p-ba"
alice <# "#team bob (direct)> 5-p-ba"
alice `send` "#team @cath 6-p-ac"
alice <# "#team @cath (direct) 6-p-ac"
cath <# "#team alice (direct)> 6-p-ac"
cath `send` "#team @alice 7-p-ca"
cath <# "#team @alice (direct) 7-p-ca"
alice <# "#team cath (direct)> 7-p-ca"
-- quotes
alice `send` "> #team @bob (1-g-a) 8-pq-ab"
alice <# "#team @bob (direct) > alice 1-g-a"
alice <## " 8-pq-ab"
bob <# "#team alice (direct)> > alice 1-g-a"
bob <## " 8-pq-ab"
alice `send` "> #team @bob (2-g-b) 9-pq-ab"
alice <# "#team @bob (direct) > bob 2-g-b"
alice <## " 9-pq-ab"
bob <# "#team alice (direct)> > bob 2-g-b"
bob <## " 9-pq-ab"
alice `send` "> #team >@cath @bob (3-g-c) 10-pq-ab"
alice <# "#team @bob (direct) > cath 3-g-c"
alice <## " 10-pq-ab"
bob <# "#team alice (direct)> > cath 3-g-c"
bob <## " 10-pq-ab"
alice `send` "> #team @bob (4-p-ab) 11-pq-ab"
alice <# "#team @bob (direct) > alice 4-p-ab"
alice <## " 11-pq-ab"
bob <# "#team alice (direct)> > alice 4-p-ab"
bob <## " 11-pq-ab"
alice `send` "> #team >@bob @bob (5-p-ba) 12-pq-ab"
alice <# "#team @bob (direct) > bob 5-p-ba"
alice <## " 12-pq-ab"
bob <# "#team alice (direct)> > bob 5-p-ba"
bob <## " 12-pq-ab"
alice `send` "> #team @bob (6-p-ac) 13-pq-ab"
alice <## "> #team @bob (6-p-ac) 13-pq-ab"
alice <## "invalid message reply"
alice `send` "> #team @bob (7-p-ca) 14-pq-ab"
alice <## "> #team @bob (7-p-ca) 14-pq-ab"
alice <## "invalid message reply"
alice `send` "> #team (4-p-ab) 15-gq-a"
alice <## "> #team (4-p-ab) 15-gq-a"
alice <## "invalid message reply"
alice `send` "> #team (5-p-ba) 16-gq-a"
alice <## "> #team (5-p-ba) 16-gq-a"
alice <## "invalid message reply"
testGroupDirectQuotesItems :: HasCallStack => FilePath -> IO ()
testGroupDirectQuotesItems =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
alice #> "#team 1-g-a"
bob <# "#team alice> 1-g-a"
cath <# "#team alice> 1-g-a"
alice `send` "#team @bob 2-p-ab"
alice <# "#team @bob (direct) 2-p-ab"
bob <# "#team alice (direct)> 2-p-ab"
bob `send` "#team @alice 3-p-ba"
bob <# "#team @alice (direct) 3-p-ba"
alice <# "#team bob (direct)> 3-p-ba"
threadDelay 1000000
-- quotes
alice `send` "> #team @bob (1-g-a) 4-pq-ab"
alice <# "#team @bob (direct) > alice 1-g-a"
alice <## " 4-pq-ab"
bob <# "#team alice (direct)> > alice 1-g-a"
bob <## " 4-pq-ab"
threadDelay 1000000
alice `send` "> #team @bob (2-p-ab) 5-pq-ab"
alice <# "#team @bob (direct) > alice 2-p-ab"
alice <## " 5-pq-ab"
bob <# "#team alice (direct)> > alice 2-p-ab"
bob <## " 5-pq-ab"
threadDelay 1000000
alice `send` "> #team >@bob @bob (3-p-ba) 6-pq-ab"
alice <# "#team @bob (direct) > bob 3-p-ba"
alice <## " 6-pq-ab"
bob <# "#team alice (direct)> > bob 3-p-ba"
bob <## " 6-pq-ab"
alice
#$> ( "/_get chat #1 count=3",
mapChat,
[ ((1, "bob", "4-pq-ab"), Just (1, "group", "1-g-a")),
((1, "bob", "5-pq-ab"), Just (1, "direct", "2-p-ab")),
((1, "bob", "6-pq-ab"), Just (0, "direct", "3-p-ba"))
]
)
bob
#$> ( "/_get chat #1 count=3",
mapChat,
[ ((0, "alice", "4-pq-ab"), Just (0, "group", "1-g-a")),
((0, "alice", "5-pq-ab"), Just (0, "direct", "2-p-ab")),
((0, "alice", "6-pq-ab"), Just (1, "direct", "3-p-ba"))
]
)
where
mapChat = map (\(a, b, _) -> (a, b)) . chat'''
testGroupDirectFilesXFTP :: HasCallStack => FilePath -> IO ()
testGroupDirectFilesXFTP =
testChatCfg3 cfg aliceProfile bobProfile cathProfile $ \alice bob cath -> do
withXFTPServer $ do
createGroup3 "team" alice bob cath
threadDelay 1000000
alice `send` "/f #team @bob ./tests/fixtures/test.pdf"
alice <# "/f #team @bob (direct) ./tests/fixtures/test.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "#team alice (direct)> sends file test.pdf (266.0 KiB / 272376 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
alice <## "completed uploading file 1 (test.pdf) for #team @bob (direct)"
bob ##> "/fr 1 ./tests/tmp"
bob
<### [ "saving file 1 from alice to ./tests/tmp/test.pdf",
"started receiving file 1 (test.pdf) from alice"
]
bob <## "completed receiving file 1 (test.pdf) from alice"
src <- B.readFile "./tests/fixtures/test.pdf"
dest <- B.readFile "./tests/tmp/test.pdf"
dest `shouldBe` src
cath <// 50000
cath ##> "/fr 1 ./tests/tmp"
cath <##. "chat db error: SEUserNotFoundByFileId"
alice `send` "/f #team @cath ./tests/fixtures/test.jpg"
alice <# "/f #team @cath (direct) ./tests/fixtures/test.jpg"
alice <## "use /fc 2 to cancel sending"
cath <# "#team alice (direct)> sends file test.jpg (136.5 KiB / 139737 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
alice <## "completed uploading file 2 (test.jpg) for #team @cath (direct)"
cath ##> "/fr 1 ./tests/tmp"
cath
<### [ "saving file 1 from alice to ./tests/tmp/test.jpg",
"started receiving file 1 (test.jpg) from alice"
]
cath <## "completed receiving file 1 (test.jpg) from alice"
src2 <- B.readFile "./tests/fixtures/test.jpg"
dest2 <- B.readFile "./tests/tmp/test.jpg"
dest2 `shouldBe` src2
bob <// 50000
alice #$> ("/_get chat #1 count=2", mapChat, [((1, "bob", ""), Just "./tests/fixtures/test.pdf"), ((1, "cath", ""), Just "./tests/fixtures/test.jpg")])
bob #$> ("/_get chat #1 count=2", mapChat, [((0, "", "connected"), Nothing), ((0, "alice", ""), Just "./tests/tmp/test.pdf")])
cath #$> ("/_get chat #1 count=2", mapChat, [((0, "", "connected"), Nothing), ((0, "alice", ""), Just "./tests/tmp/test.jpg")])
where
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
mapChat = map (\(a, _, c) -> (a, c)) . chat'''
testGroupDirectFilesSMP :: HasCallStack => FilePath -> IO ()
testGroupDirectFilesSMP =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
threadDelay 1000000
alice `send` "/f #team @bob ./tests/fixtures/test.pdf"
alice <# "/f #team @bob (direct) ./tests/fixtures/test.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "#team alice (direct)> sends file test.pdf (266.0 KiB / 272376 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
bob ##> "/fr 1 ./tests/tmp"
bob <## "saving file 1 from alice to ./tests/tmp/test.pdf"
concurrently_
(alice <## "started sending file 1 (test.pdf) to bob")
(bob <## "started receiving file 1 (test.pdf) from alice")
concurrently_
(alice <## "completed sending file 1 (test.pdf) to bob")
(bob <## "completed receiving file 1 (test.pdf) from alice")
src <- B.readFile "./tests/fixtures/test.pdf"
dest <- B.readFile "./tests/tmp/test.pdf"
dest `shouldBe` src
cath <// 50000
cath ##> "/fr 1 ./tests/tmp"
cath <##. "chat db error: SEUserNotFoundByFileId"
alice `send` "/f #team @cath ./tests/fixtures/test.jpg"
alice <# "/f #team @cath (direct) ./tests/fixtures/test.jpg"
alice <## "use /fc 2 to cancel sending"
cath <# "#team alice (direct)> sends file test.jpg (136.5 KiB / 139737 bytes)"
cath <## "use /fr 1 [<dir>/ | <path>] to receive it"
cath ##> "/fr 1 ./tests/tmp"
cath <## "saving file 1 from alice to ./tests/tmp/test.jpg"
concurrently_
(alice <## "started sending file 2 (test.jpg) to cath")
(cath <## "started receiving file 1 (test.jpg) from alice")
concurrently_
(alice <## "completed sending file 2 (test.jpg) to cath")
(cath <## "completed receiving file 1 (test.jpg) from alice")
src2 <- B.readFile "./tests/fixtures/test.jpg"
dest2 <- B.readFile "./tests/tmp/test.jpg"
dest2 `shouldBe` src2
bob <// 50000
alice #$> ("/_get chat #1 count=2", mapChat, [((1, "bob", ""), Just "./tests/fixtures/test.pdf"), ((1, "cath", ""), Just "./tests/fixtures/test.jpg")])
bob #$> ("/_get chat #1 count=2", mapChat, [((0, "", "connected"), Nothing), ((0, "alice", ""), Just "./tests/tmp/test.pdf")])
cath #$> ("/_get chat #1 count=2", mapChat, [((0, "", "connected"), Nothing), ((0, "alice", ""), Just "./tests/tmp/test.jpg")])
where
mapChat = map (\(a, _, c) -> (a, c)) . chat'''
testGroupDirectCancelFileXFTP :: HasCallStack => FilePath -> IO ()
testGroupDirectCancelFileXFTP =
testChatCfg3 cfg aliceProfile bobProfile cathProfile $ \alice bob cath -> do
withXFTPServer $ do
createGroup3 "team" alice bob cath
alice `send` "/f #team @bob ./tests/fixtures/test.pdf"
alice <# "/f #team @bob (direct) ./tests/fixtures/test.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "#team alice (direct)> sends file test.pdf (266.0 KiB / 272376 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
alice <## "completed uploading file 1 (test.pdf) for #team @bob (direct)"
cath <// 50000
alice ##> "/fc 1"
alice <## "cancelled sending file 1 (test.pdf) to bob"
bob <## "alice cancelled sending file 1 (test.pdf)"
cath <// 50000
bob ##> "/fr 1 ./tests/tmp"
bob <## "file cancelled: test.pdf"
cath ##> "/fr 1 ./tests/tmp"
cath <##. "chat db error: SEUserNotFoundByFileId"
where
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
testGroupDirectQuotesFiles :: HasCallStack => FilePath -> IO ()
testGroupDirectQuotesFiles =
testChatCfg3 cfg aliceProfile bobProfile cathProfile $ \alice bob cath -> do
withXFTPServer $ do
createGroup3 "team" alice bob cath
threadDelay 1000000
bob `send` "#team @alice hi alice"
bob <# "#team @alice (direct) hi alice"
alice <# "#team bob (direct)> hi alice"
threadDelay 1000000
msgItemId1 <- lastItemId alice
alice ##> ("/_send #1 @2 json {\"filePath\": \"./tests/fixtures/test.pdf\", \"quotedItemId\": " <> msgItemId1 <> ", \"msgContent\": {\"text\":\"hey bob\",\"type\":\"file\"}}")
alice <# "#team @bob (direct) > bob hi alice"
alice <## " hey bob"
alice <# "/f #team @bob (direct) ./tests/fixtures/test.pdf"
alice <## "use /fc 1 to cancel sending"
bob <# "#team alice (direct)> > bob hi alice"
bob <## " hey bob"
bob <# "#team alice (direct)> sends file test.pdf (266.0 KiB / 272376 bytes)"
bob <## "use /fr 1 [<dir>/ | <path>] to receive it"
alice <## "completed uploading file 1 (test.pdf) for #team @bob (direct)"
bob ##> "/fr 1 ./tests/tmp"
bob
<### [ "saving file 1 from alice to ./tests/tmp/test.pdf",
"started receiving file 1 (test.pdf) from alice"
]
bob <## "completed receiving file 1 (test.pdf) from alice"
src <- B.readFile "./tests/fixtures/test.pdf"
dest <- B.readFile "./tests/tmp/test.pdf"
dest `shouldBe` src
cath <// 50000
cath ##> "/fr 1 ./tests/tmp"
cath <##. "chat db error: SEUserNotFoundByFileId"
alice
#$> ( "/_get chat #1 count=2",
chat''',
[ ((0, "bob", "hi alice"), Nothing, Nothing),
((1, "bob", "hey bob"), Just (0, "direct", "hi alice"), Just "./tests/fixtures/test.pdf")
]
)
bob
#$> ( "/_get chat #1 count=2",
chat''',
[ ((1, "alice", "hi alice"), Nothing, Nothing),
((0, "alice", "hey bob"), Just (1, "direct", "hi alice"), Just "./tests/tmp/test.pdf")
]
)
cath #$> ("/_get chat #1 count=1", chat''', [((0, "", "connected"), Nothing, Nothing)])
where
cfg = testCfg {xftpFileConfig = Just $ XFTPFileConfig {minFileSize = 0}, tempDir = Just "./tests/tmp"}
testGroupDirectUpdate :: HasCallStack => FilePath -> IO ()
testGroupDirectUpdate =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
alice `send` "#team @bob hi bob"
alice <# "#team @bob (direct) hi bob"
bob <# "#team alice (direct)> hi bob"
msgItemId1 <- lastItemId alice
alice ##> ("/_update item #1 " <> msgItemId1 <> " text hey 👋")
alice <# "#team @bob (direct) [edited] hey 👋"
bob <# "#team alice (direct)> [edited] hey 👋"
cath <// 50000
alice ##> "! #team (hey 👋) hello there"
alice <# "#team @bob (direct) [edited] hello there"
bob <# "#team alice (direct)> [edited] hello there"
cath <// 50000
testGroupDirectDelete :: HasCallStack => FilePath -> IO ()
testGroupDirectDelete =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
alice `send` "#team @bob hi bob"
alice <# "#team @bob (direct) hi bob"
bob <# "#team alice (direct)> hi bob"
msgItemId1 <- lastItemId alice
alice #$> ("/_delete item #1 " <> msgItemId1 <> " broadcast", id, "message marked deleted")
bob <# "#team alice (direct)> [marked deleted] hi bob"
cath <// 50000
testGroupDirectLiveMessage :: HasCallStack => FilePath -> IO ()
testGroupDirectLiveMessage =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
alice `send` "/live #team @bob hello"
msgItemId1 <- lastItemId alice
bob <#. "#team alice (direct)> [LIVE started]"
alice ##> ("/_update item #1 " <> msgItemId1 <> " text hello there")
alice <# "#team @bob (direct) [LIVE] hello there"
bob <# "#team alice (direct)> [LIVE ended] hello there"
cath <// 50000
testGroupDirectReactions :: HasCallStack => FilePath -> IO ()
testGroupDirectReactions =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3 "team" alice bob cath
alice `send` "#team @bob hi bob"
alice <# "#team @bob (direct) hi bob"
bob <# "#team alice (direct)> hi bob"
bob ##> "+1 #team hi"
bob <## "added 👍"
alice <# "#team bob (direct)> > alice hi bob"
alice <## " + 👍"
cath <// 50000
alice ##> "+^ #team hi"
alice <## "added 🚀"
bob <# "#team alice (direct)> > alice hi bob"
bob <## " + 🚀"
cath <// 50000
testGroupDirectProhibitPreference :: HasCallStack => FilePath -> IO ()
testGroupDirectProhibitPreference =
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
createGroup3' "team" alice bob cath GRMember
alice ##> "/set direct #team off"
alice <## "updated group preferences:"
alice <## "Direct messages: off"
directProhibited bob
directProhibited cath
bob ##> "#team @cath hi cath"
bob <## "bad chat command: direct messages not allowed"
cath ##> "#team @bob hi cath"
cath <## "bad chat command: direct messages not allowed"
alice ##> "/mr team bob admin"
alice <## "#team: you changed the role of bob from member to admin"
concurrentlyN_
[ bob <## "#team: alice changed your role from member to admin",
cath <## "#team: alice changed the role of bob from member to admin"
]
-- admin can send & can send to admin
bob `send` "#team @cath hi cath, as admin"
bob <# "#team @cath (direct) hi cath, as admin"
cath <# "#team bob (direct)> hi cath, as admin"
cath `send` "#team @bob hi bob, to admin"
cath <# "#team @bob (direct) hi bob, to admin"
bob <# "#team cath (direct)> hi bob, to admin"
where
directProhibited :: HasCallStack => TestCC -> IO ()
directProhibited cc = do
cc <## "alice updated group #team:"
cc <## "updated group preferences:"
cc <## "Direct messages: off"
testGroupDirectProhibitNotSupported :: HasCallStack => FilePath -> IO ()
testGroupDirectProhibitNotSupported tmp =
withNewTestChat tmp "alice" aliceProfile $ \alice -> do
withNewTestChat tmp "bob" bobProfile $ \bob -> do
withNewTestChatCfg tmp testCfg {chatVRange = mkVersionRange 1 1} "cath" cathProfile $ \cath -> do
createGroup3 "team" alice bob cath
bob ##> "#team @cath hi cath"
bob <## "peer chat protocol version range incompatible"

View File

@ -181,12 +181,7 @@ chatF :: String -> [((Int, String), Maybe String)]
chatF = map (\(a, _, c) -> (a, c)) . chat'' chatF = map (\(a, _, c) -> (a, c)) . chat''
chat'' :: String -> [((Int, String), Maybe (Int, String), Maybe String)] chat'' :: String -> [((Int, String), Maybe (Int, String), Maybe String)]
chat'' = map (\(a, b, c) -> (mapNoDirect a, mapNoDirect <$> b, c)) . chat''' chat'' = read
where
mapNoDirect (a1, _, a3) = (a1, a3)
chat''' :: String -> [((Int, String, String), Maybe (Int, String, String), Maybe String)]
chat''' = read
chatFeatures :: [(Int, String)] chatFeatures :: [(Int, String)]
chatFeatures = map (\(a, _, _) -> a) chatFeatures'' chatFeatures = map (\(a, _, _) -> a) chatFeatures''
@ -461,33 +456,27 @@ showName (TestCC ChatController {currentUser} _ _ _ _ _) = do
pure . T.unpack $ localDisplayName <> optionalFullName localDisplayName fullName pure . T.unpack $ localDisplayName <> optionalFullName localDisplayName fullName
createGroup2 :: HasCallStack => String -> TestCC -> TestCC -> IO () createGroup2 :: HasCallStack => String -> TestCC -> TestCC -> IO ()
createGroup2 gName cc1 cc2 = createGroup2' gName cc1 cc2 GRAdmin createGroup2 gName cc1 cc2 = do
createGroup2' :: HasCallStack => String -> TestCC -> TestCC -> GroupMemberRole -> IO ()
createGroup2' gName cc1 cc2 memberRole = do
connectUsers cc1 cc2 connectUsers cc1 cc2
name2 <- userName cc2 name2 <- userName cc2
cc1 ##> ("/g " <> gName) cc1 ##> ("/g " <> gName)
cc1 <## ("group #" <> gName <> " is created") cc1 <## ("group #" <> gName <> " is created")
cc1 <## ("to add members use /a " <> gName <> " <name> or /create link #" <> gName) cc1 <## ("to add members use /a " <> gName <> " <name> or /create link #" <> gName)
addMember gName cc1 cc2 memberRole addMember gName cc1 cc2 GRAdmin
cc2 ##> ("/j " <> gName) cc2 ##> ("/j " <> gName)
concurrently_ concurrently_
(cc1 <## ("#" <> gName <> ": " <> name2 <> " joined the group")) (cc1 <## ("#" <> gName <> ": " <> name2 <> " joined the group"))
(cc2 <## ("#" <> gName <> ": you joined the group")) (cc2 <## ("#" <> gName <> ": you joined the group"))
createGroup3 :: HasCallStack => String -> TestCC -> TestCC -> TestCC -> IO () createGroup3 :: HasCallStack => String -> TestCC -> TestCC -> TestCC -> IO ()
createGroup3 gName cc1 cc2 cc3 = createGroup3' gName cc1 cc2 cc3 GRAdmin createGroup3 gName cc1 cc2 cc3 = do
createGroup2 gName cc1 cc2
createGroup3' :: HasCallStack => String -> TestCC -> TestCC -> TestCC -> GroupMemberRole -> IO ()
createGroup3' gName cc1 cc2 cc3 memberRole = do
createGroup2' gName cc1 cc2 memberRole
connectUsers cc1 cc3 connectUsers cc1 cc3
name1 <- userName cc1 name1 <- userName cc1
name3 <- userName cc3 name3 <- userName cc3
sName2 <- showName cc2 sName2 <- showName cc2
sName3 <- showName cc3 sName3 <- showName cc3
addMember gName cc1 cc3 memberRole addMember gName cc1 cc3 GRAdmin
cc3 ##> ("/j " <> gName) cc3 ##> ("/j " <> gName)
concurrentlyN_ concurrentlyN_
[ cc1 <## ("#" <> gName <> ": " <> name3 <> " joined the group"), [ cc1 <## ("#" <> gName <> ": " <> name3 <> " joined the group"),

View File

@ -57,7 +57,7 @@ testConnReq = CRInvitationUri connReqData testE2ERatchetParams
quotedMsg :: QuotedMsg quotedMsg :: QuotedMsg
quotedMsg = quotedMsg =
QuotedMsg QuotedMsg
(MsgRef (Just $ SharedMsgId "\5\6\7\8") (systemToUTCTime $ MkSystemTime 1 1) True Nothing Nothing) (MsgRef (Just $ SharedMsgId "\5\6\7\8") (systemToUTCTime $ MkSystemTime 1 1) True Nothing)
$ MCText "hello there!" $ MCText "hello there!"
(==##) :: MsgEncodingI e => ByteString -> ChatMessage e -> Expectation (==##) :: MsgEncodingI e => ByteString -> ChatMessage e -> Expectation
@ -105,13 +105,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
#==# XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)) #==# XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))
it "x.msg.new simple text - timed message TTL" $ it "x.msg.new simple text - timed message TTL" $
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"ttl\":3600}}" "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"ttl\":3600}}"
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing Nothing)) #==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing))
it "x.msg.new simple text - live message" $ it "x.msg.new simple text - live message" $
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"live\":true}}" "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"live\":true}}"
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing Nothing (Just True) Nothing)) #==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing Nothing (Just True)))
it "x.msg.new simple text - direct message scope" $
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"scope\":\"direct\"}}"
#==# XMsgNew (MCSimple (ExtMsgContent (MCText "hello") Nothing Nothing Nothing (Just MSDirect)))
it "x.msg.new simple link" $ it "x.msg.new simple link" $
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"https://simplex.chat\",\"type\":\"link\",\"preview\":{\"description\":\"SimpleX Chat\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA\",\"title\":\"SimpleX Chat\",\"uri\":\"https://simplex.chat\"}}}}" "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"https://simplex.chat\",\"type\":\"link\",\"preview\":{\"description\":\"SimpleX Chat\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA\",\"title\":\"SimpleX Chat\",\"uri\":\"https://simplex.chat\"}}}}"
#==# XMsgNew (MCSimple (extMsgContent (MCLink "https://simplex.chat" $ LinkPreview {uri = "https://simplex.chat", title = "SimpleX Chat", description = "SimpleX Chat", image = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA", content = Nothing}) Nothing)) #==# XMsgNew (MCSimple (extMsgContent (MCLink "https://simplex.chat" $ LinkPreview {uri = "https://simplex.chat", title = "SimpleX Chat", description = "SimpleX Chat", image = ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgA", content = Nothing}) Nothing))
@ -133,41 +130,27 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
chatInitialVRange chatInitialVRange
(Just $ SharedMsgId "\1\2\3\4") (Just $ SharedMsgId "\1\2\3\4")
(XMsgNew (MCQuote quotedMsg (extMsgContent (MCText "hello to you too") Nothing))) (XMsgNew (MCQuote quotedMsg (extMsgContent (MCText "hello to you too") Nothing)))
it "x.msg.new quote - direct referenced message scope" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\",\"msgScope\":\"direct\"}}}}"
##==## ChatMessage
chatInitialVRange
(Just $ SharedMsgId "\1\2\3\4")
( XMsgNew
( MCQuote
( QuotedMsg
(MsgRef (Just $ SharedMsgId "\5\6\7\8") (systemToUTCTime $ MkSystemTime 1 1) True Nothing (Just MSDirect))
$ MCText "hello there!"
)
(extMsgContent (MCText "hello to you too") Nothing)
)
)
it "x.msg.new quote - timed message TTL" $ it "x.msg.new quote - timed message TTL" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"ttl\":3600}}" "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"ttl\":3600}}"
##==## ChatMessage ##==## ChatMessage
chatInitialVRange chatInitialVRange
(Just $ SharedMsgId "\1\2\3\4") (Just $ SharedMsgId "\1\2\3\4")
(XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing (Just 3600) Nothing Nothing))) (XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing (Just 3600) Nothing)))
it "x.msg.new quote - live message" $ it "x.msg.new quote - live message" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"live\":true}}" "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}},\"live\":true}}"
##==## ChatMessage ##==## ChatMessage
chatInitialVRange chatInitialVRange
(Just $ SharedMsgId "\1\2\3\4") (Just $ SharedMsgId "\1\2\3\4")
(XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing Nothing (Just True) Nothing))) (XMsgNew (MCQuote quotedMsg (ExtMsgContent (MCText "hello to you too") Nothing Nothing (Just True))))
it "x.msg.new forward" $ it "x.msg.new forward" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true}}" "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true}}"
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (extMsgContent (MCText "hello") Nothing)) ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (extMsgContent (MCText "hello") Nothing))
it "x.msg.new forward - timed message TTL" $ it "x.msg.new forward - timed message TTL" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"ttl\":3600}}" "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"ttl\":3600}}"
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing Nothing)) ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing (Just 3600) Nothing))
it "x.msg.new forward - live message" $ it "x.msg.new forward - live message" $
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"live\":true}}" "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"forward\":true,\"live\":true}}"
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing Nothing (Just True) Nothing)) ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew $ MCForward (ExtMsgContent (MCText "hello") Nothing Nothing (Just True)))
it "x.msg.new simple text with file" $ it "x.msg.new simple text with file" $
"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" "{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}"
#==# XMsgNew (MCSimple (extMsgContent (MCText "hello") (Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing}))) #==# XMsgNew (MCSimple (extMsgContent (MCText "hello") (Just FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Nothing, fileInline = Nothing, fileDescr = Nothing})))