mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-14 16:55:27 +00:00
core: api to delete multiple messages (#4452)
* core: api proposal (not implemeted) to delete multiple messages * core: batch delete multiple messages; allow to moderate self (#4513) * allow to moderate self, remove saving item-message record on mark delete * direct batched * local batched * group batched * moderate batched * refactor * fix * fix test * remove unused event * direct message batching wip * direct test * more tests * trunk * batch compressed * remove unused function * new agent api * sendGroupMessages * forward batched * refactor * remove comment * rename, comment * refactor * many chat batches test (doesn't pass) * refactor * comment * rename * comment * linearize * fix * fix --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com> * core: check item deletable with margin (#4533) * simplexmq * remove L.singleton (ghc 8.10.7) * test delay --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ab058d7222
commit
8bda64a5c1
@@ -72,7 +72,7 @@ crDirectoryEvent = \case
|
||||
CRDeletedMemberUser {groupInfo} -> Just $ DEServiceRemovedFromGroup groupInfo
|
||||
CRGroupDeleted {groupInfo} -> Just $ DEGroupDeleted groupInfo
|
||||
CRChatItemUpdated {chatItem = AChatItem _ SMDRcv (DirectChat ct) _} -> Just $ DEItemEditIgnored ct
|
||||
CRChatItemDeleted {deletedChatItem = AChatItem _ SMDRcv (DirectChat ct) _, byUser = False} -> Just $ DEItemDeleteIgnored ct
|
||||
CRChatItemsDeleted {chatItemDeletions = ((ChatItemDeletion (AChatItem _ SMDRcv (DirectChat ct) _) _) : _), byUser = False} -> Just $ DEItemDeleteIgnored ct
|
||||
CRNewChatItem {chatItem = AChatItem _ SMDRcv (DirectChat ct) ci@ChatItem {content = CIRcvMsgContent mc, meta = CIMeta {itemLive}}} ->
|
||||
Just $ case (mc, itemLive) of
|
||||
(MCText t, Nothing) -> DEContactCommand ct ciId $ fromRight err $ A.parseOnly (directoryCmdP <* A.endOfInput) $ T.dropWhileEnd isSpace t
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 83f8622b2397afe2635c8f60f1ec5f6fdc16ef7c
|
||||
tag: 03ea151be5dce9a4b8683f9f28805bdc8ba66758
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."83f8622b2397afe2635c8f60f1ec5f6fdc16ef7c" = "1dn7b0pjlk32crp943l6lz4r376nf444kjfi167mrv1pgccri6ns";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."03ea151be5dce9a4b8683f9f28805bdc8ba66758" = "1gfkqzda6vw3fsd34c08jygwg0m5v211sm7v7jdvj0sx1p8428d4";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
||||
+433
-275
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@
|
||||
{-# LANGUAGE GADTs #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE OverloadedLists #-}
|
||||
|
||||
module Simplex.Chat.Bot where
|
||||
|
||||
@@ -73,9 +74,9 @@ sendComposedMessage' cc ctId quotedItemId msgContent = do
|
||||
|
||||
deleteMessage :: ChatController -> Contact -> ChatItemId -> IO ()
|
||||
deleteMessage cc ct chatItemId = do
|
||||
let cmd = APIDeleteChatItem (contactRef ct) chatItemId CIDMInternal
|
||||
let cmd = APIDeleteChatItem (contactRef ct) [chatItemId] CIDMInternal
|
||||
sendChatCmd cc cmd >>= \case
|
||||
CRChatItemDeleted {} -> printLog cc CLLInfo $ "deleted message from " <> contactInfo ct
|
||||
CRChatItemsDeleted {} -> printLog cc CLLInfo $ "deleted message(s) from " <> contactInfo ct
|
||||
r -> putStrLn $ "unexpected delete message response: " <> show r
|
||||
|
||||
contactRef :: Contact -> ChatRef
|
||||
|
||||
@@ -69,7 +69,7 @@ import Simplex.Chat.Types.UITheme
|
||||
import Simplex.Chat.Util (liftIOEither)
|
||||
import Simplex.FileTransfer.Description (FileDescriptionURI)
|
||||
import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo)
|
||||
import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, ServerQueueInfo, SMPServerSubs, UserNetworkInfo)
|
||||
import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, SMPServerSubs, ServerQueueInfo, UserNetworkInfo)
|
||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig, ServerCfg)
|
||||
import Simplex.Messaging.Agent.Lock
|
||||
import Simplex.Messaging.Agent.Protocol
|
||||
@@ -293,8 +293,8 @@ data ChatCommand
|
||||
| APISendMessage {chatRef :: ChatRef, liveMessage :: Bool, ttl :: Maybe Int, composedMessage :: ComposedMessage}
|
||||
| APICreateChatItem {noteFolderId :: NoteFolderId, composedMessage :: ComposedMessage}
|
||||
| APIUpdateChatItem {chatRef :: ChatRef, chatItemId :: ChatItemId, liveMessage :: Bool, msgContent :: MsgContent}
|
||||
| APIDeleteChatItem ChatRef ChatItemId CIDeleteMode
|
||||
| APIDeleteMemberChatItem GroupId GroupMemberId ChatItemId
|
||||
| APIDeleteChatItem ChatRef (NonEmpty ChatItemId) CIDeleteMode
|
||||
| APIDeleteMemberChatItem GroupId (NonEmpty ChatItemId)
|
||||
| APIChatItemReaction {chatRef :: ChatRef, chatItemId :: ChatItemId, add :: Bool, reaction :: MsgReaction}
|
||||
| APIForwardChatItem {toChatRef :: ChatRef, fromChatRef :: ChatRef, chatItemId :: ChatItemId, ttl :: Maybe Int}
|
||||
| APIUserRead UserId
|
||||
@@ -599,7 +599,7 @@ data ChatResponse
|
||||
| CRChatItemUpdated {user :: User, chatItem :: AChatItem}
|
||||
| CRChatItemNotChanged {user :: User, chatItem :: AChatItem}
|
||||
| CRChatItemReaction {user :: User, added :: Bool, reaction :: ACIReaction}
|
||||
| CRChatItemDeleted {user :: User, deletedChatItem :: AChatItem, toChatItem :: Maybe AChatItem, byUser :: Bool, timed :: Bool}
|
||||
| CRChatItemsDeleted {user :: User, chatItemDeletions :: [ChatItemDeletion], byUser :: Bool, timed :: Bool}
|
||||
| CRChatItemDeletedNotFound {user :: User, contact :: Contact, sharedMsgId :: SharedMsgId}
|
||||
| CRBroadcastSent {user :: User, msgContent :: MsgContent, successes :: Int, failures :: Int, timestamp :: UTCTime}
|
||||
| CRMsgIntegrityError {user :: User, msgError :: MsgErrorType}
|
||||
@@ -1081,6 +1081,12 @@ tmeToPref currentTTL tme = uncurry TimedMessagesPreference $ case tme of
|
||||
TMEEnableKeepTTL -> (FAYes, currentTTL)
|
||||
TMEDisableKeepTTL -> (FANo, currentTTL)
|
||||
|
||||
data ChatItemDeletion = ChatItemDeletion
|
||||
{ deletedChatItem :: AChatItem,
|
||||
toChatItem :: Maybe AChatItem
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
data ChatLogLevel = CLLDebug | CLLInfo | CLLWarning | CLLError | CLLImportant
|
||||
deriving (Eq, Ord, Show)
|
||||
|
||||
@@ -1487,6 +1493,8 @@ $(JQ.deriveJSON defaultJSON ''ServerAddress)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''ParsedServerAddress)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''ChatItemDeletion)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''CoreVersionInfo)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''SlowSQLQuery)
|
||||
|
||||
@@ -35,7 +35,7 @@ import Data.Maybe (fromMaybe, isJust, isNothing)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Text.Encoding (decodeLatin1, encodeUtf8)
|
||||
import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay)
|
||||
import Data.Time.Clock (UTCTime, diffUTCTime, nominalDay, NominalDiffTime)
|
||||
import Data.Type.Equality
|
||||
import Data.Typeable (Typeable)
|
||||
import Database.SQLite.Simple.FromField (FromField (..))
|
||||
@@ -364,15 +364,19 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
|
||||
|
||||
mkCIMeta :: forall c d. ChatTypeI c => ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe Bool -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> UTCTime -> ChatItemTs -> Maybe GroupMemberId -> UTCTime -> UTCTime -> CIMeta c d
|
||||
mkCIMeta itemId itemContent itemText itemStatus sentViaProxy itemSharedMsgId itemForwarded itemDeleted itemEdited itemTimed itemLive currentTs itemTs forwardedByMember createdAt updatedAt =
|
||||
let deletable = case itemContent of
|
||||
CISndMsgContent _ ->
|
||||
case chatTypeI @c of
|
||||
SCTLocal -> isNothing itemDeleted
|
||||
_ -> diffUTCTime currentTs itemTs < nominalDay && isNothing itemDeleted
|
||||
_ -> False
|
||||
let deletable = deletable' itemContent itemDeleted itemTs nominalDay currentTs
|
||||
editable = deletable && isNothing itemForwarded
|
||||
in CIMeta {itemId, itemTs, itemText, itemStatus, sentViaProxy, itemSharedMsgId, itemForwarded, itemDeleted, itemEdited, itemTimed, itemLive, deletable, editable, forwardedByMember, createdAt, updatedAt}
|
||||
|
||||
deletable' :: forall c d. ChatTypeI c => CIContent d -> Maybe (CIDeleted c) -> UTCTime -> NominalDiffTime -> UTCTime -> Bool
|
||||
deletable' itemContent itemDeleted itemTs allowedInterval currentTs =
|
||||
case itemContent of
|
||||
CISndMsgContent _ ->
|
||||
case chatTypeI @c of
|
||||
SCTLocal -> isNothing itemDeleted
|
||||
_ -> diffUTCTime currentTs itemTs < allowedInterval && isNothing itemDeleted
|
||||
_ -> False
|
||||
|
||||
dummyMeta :: ChatItemId -> UTCTime -> Text -> CIMeta c 'MDSnd
|
||||
dummyMeta itemId ts itemText =
|
||||
CIMeta
|
||||
|
||||
@@ -31,8 +31,9 @@ import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.ByteString.Internal (c2w, w2c)
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.List.NonEmpty (NonEmpty (..))
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Maybe (fromMaybe, mapMaybe)
|
||||
import Data.String
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
@@ -63,12 +64,14 @@ import Simplex.Messaging.Version hiding (version)
|
||||
-- 5 - batch sending messages (12/23/2023)
|
||||
-- 6 - send group welcome message after history (12/29/2023)
|
||||
-- 7 - update member profiles (1/15/2024)
|
||||
-- 8 - compress messages and PQ e2e encryption (2024-03-08)
|
||||
-- 9 - batch sending in direct connections (2024-07-24)
|
||||
|
||||
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
|
||||
-- This indirection is needed for backward/forward compatibility testing.
|
||||
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
|
||||
currentChatVersion :: VersionChat
|
||||
currentChatVersion = VersionChat 8
|
||||
currentChatVersion = VersionChat 9
|
||||
|
||||
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
|
||||
supportedChatVRange :: VersionRangeChat
|
||||
@@ -103,6 +106,10 @@ memberProfileUpdateVersion = VersionChat 7
|
||||
pqEncryptionCompressionVersion :: VersionChat
|
||||
pqEncryptionCompressionVersion = VersionChat 8
|
||||
|
||||
-- version range that supports batch sending in direct connections, and forwarding batched messages in groups
|
||||
batchSend2Version :: VersionChat
|
||||
batchSend2Version = VersionChat 9
|
||||
|
||||
agentToChatVersion :: VersionSMPA -> VersionChat
|
||||
agentToChatVersion v
|
||||
| v < pqdrSMPAgentVersion = initialChatVersion
|
||||
@@ -323,13 +330,20 @@ forwardedGroupMsg msg@ChatMessage {chatMsgEvent} = case encoding @e of
|
||||
SJson | isForwardedGroupMsg chatMsgEvent -> Just msg
|
||||
_ -> Nothing
|
||||
|
||||
-- applied after checking forwardedGroupMsg and building list of group members to forward to, see Chat
|
||||
forwardedToGroupMembers :: forall e. MsgEncodingI e => [GroupMember] -> ChatMessage e -> [GroupMember]
|
||||
forwardedToGroupMembers ms ChatMessage {chatMsgEvent} = case encoding @e of
|
||||
SJson -> case chatMsgEvent of
|
||||
XGrpMemRestrict mId _ -> filter (\GroupMember {memberId} -> memberId /= mId) ms
|
||||
_ -> ms
|
||||
_ -> []
|
||||
-- applied after checking forwardedGroupMsg and building list of group members to forward to, see Chat;
|
||||
-- this filters out members if any of forwarded events in batch is an XGrpMemRestrict event referring to them,
|
||||
-- but practically XGrpMemRestrict is not batched with other events so it wouldn't prevent forwarding of other events
|
||||
-- to these members
|
||||
forwardedToGroupMembers :: forall e. MsgEncodingI e => [GroupMember] -> NonEmpty (ChatMessage e) -> [GroupMember]
|
||||
forwardedToGroupMembers ms forwardedMsgs =
|
||||
filter (\GroupMember {memberId} -> memberId `notElem` restrictMemberIds) ms
|
||||
where
|
||||
restrictMemberIds = mapMaybe restrictMemberId $ L.toList forwardedMsgs
|
||||
restrictMemberId ChatMessage {chatMsgEvent} = case encoding @e of
|
||||
SJson -> case chatMsgEvent of
|
||||
XGrpMemRestrict mId _ -> Just mId
|
||||
_ -> Nothing
|
||||
_ -> Nothing
|
||||
|
||||
data MsgReaction = MREmoji {emoji :: MREmojiChar} | MRUnknown {tag :: Text, json :: J.Object}
|
||||
deriving (Eq, Show)
|
||||
|
||||
@@ -19,7 +19,7 @@ module Simplex.Chat.Store.Messages
|
||||
-- * Message and chat item functions
|
||||
deleteContactCIs,
|
||||
getGroupFileInfo,
|
||||
deleteGroupCIs,
|
||||
deleteGroupChatItemsMessages,
|
||||
createNewSndMessage,
|
||||
createSndMsgDelivery,
|
||||
createNewMessageAndRcvMsgDelivery,
|
||||
@@ -170,8 +170,8 @@ getGroupFileInfo db User {userId} GroupInfo {groupId} =
|
||||
map toFileInfo
|
||||
<$> DB.query db (fileInfoQuery <> " WHERE i.user_id = ? AND i.group_id = ?") (userId, groupId)
|
||||
|
||||
deleteGroupCIs :: DB.Connection -> User -> GroupInfo -> IO ()
|
||||
deleteGroupCIs db User {userId} GroupInfo {groupId} = do
|
||||
deleteGroupChatItemsMessages :: DB.Connection -> User -> GroupInfo -> IO ()
|
||||
deleteGroupChatItemsMessages db User {userId} GroupInfo {groupId} = do
|
||||
DB.execute db "DELETE FROM messages WHERE group_id = ?" (Only groupId)
|
||||
DB.execute db "DELETE FROM chat_item_reactions WHERE group_id = ?" (Only groupId)
|
||||
DB.execute db "DELETE FROM chat_items WHERE user_id = ? AND group_id = ?" (userId, groupId)
|
||||
@@ -1733,11 +1733,10 @@ deleteChatItemVersions_ :: DB.Connection -> ChatItemId -> IO ()
|
||||
deleteChatItemVersions_ db itemId =
|
||||
DB.execute db "DELETE FROM chat_item_versions WHERE chat_item_id = ?" (Only itemId)
|
||||
|
||||
markDirectChatItemDeleted :: DB.Connection -> User -> Contact -> ChatItem 'CTDirect d -> MessageId -> UTCTime -> IO (ChatItem 'CTDirect d)
|
||||
markDirectChatItemDeleted db User {userId} Contact {contactId} ci@ChatItem {meta} msgId deletedTs = do
|
||||
markDirectChatItemDeleted :: DB.Connection -> User -> Contact -> ChatItem 'CTDirect d -> UTCTime -> IO (ChatItem 'CTDirect d)
|
||||
markDirectChatItemDeleted db User {userId} Contact {contactId} ci@ChatItem {meta} deletedTs = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let itemId = chatItemId' ci
|
||||
insertChatItemMessage_ db itemId msgId currentTs
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
@@ -1900,7 +1899,7 @@ updateGroupChatItemModerated db User {userId} GroupInfo {groupId} ci m@GroupMemb
|
||||
WHERE user_id = ? AND group_id = ? AND chat_item_id = ?
|
||||
|]
|
||||
(deletedTs, groupMemberId, toContent, toText, currentTs, userId, groupId, itemId)
|
||||
pure $ ci {content = toContent, meta = (meta ci) {itemText = toText, itemDeleted = Just (CIModerated (Just deletedTs) m), editable = False, deletable = False}, formattedText = Nothing}
|
||||
pure ci {content = toContent, meta = (meta ci) {itemText = toText, itemDeleted = Just (CIModerated (Just deletedTs) m), editable = False, deletable = False}, formattedText = Nothing}
|
||||
|
||||
updateGroupCIBlockedByAdmin :: DB.Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> UTCTime -> IO (ChatItem 'CTGroup d)
|
||||
updateGroupCIBlockedByAdmin db User {userId} GroupInfo {groupId} ci deletedTs = do
|
||||
@@ -1931,14 +1930,13 @@ pattern DBCIBlocked = 2
|
||||
pattern DBCIBlockedByAdmin :: Int
|
||||
pattern DBCIBlockedByAdmin = 3
|
||||
|
||||
markGroupChatItemDeleted :: DB.Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> MessageId -> Maybe GroupMember -> UTCTime -> IO (ChatItem 'CTGroup d)
|
||||
markGroupChatItemDeleted db User {userId} GroupInfo {groupId} ci@ChatItem {meta} msgId byGroupMember_ deletedTs = do
|
||||
markGroupChatItemDeleted :: DB.Connection -> User -> GroupInfo -> ChatItem 'CTGroup d -> Maybe GroupMember -> UTCTime -> IO (ChatItem 'CTGroup d)
|
||||
markGroupChatItemDeleted db User {userId} GroupInfo {groupId} ci@ChatItem {meta} byGroupMember_ deletedTs = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let itemId = chatItemId' ci
|
||||
(deletedByGroupMemberId, itemDeleted) = case byGroupMember_ of
|
||||
Just m@GroupMember {groupMemberId} -> (Just groupMemberId, Just $ CIModerated (Just deletedTs) m)
|
||||
_ -> (Nothing, Just $ CIDeleted @'CTGroup (Just deletedTs))
|
||||
insertChatItemMessage_ db itemId msgId currentTs
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
|
||||
@@ -71,7 +71,7 @@ runInputLoop ct@ChatTerminal {termState, liveMessageState} cc = forever $ do
|
||||
CRChatItems u chatName_ _ -> whenCurrUser cc u $ mapM_ (setActive ct . chatActiveTo) chatName_
|
||||
CRNewChatItem u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo
|
||||
CRChatItemUpdated u (AChatItem _ SMDSnd cInfo _) -> whenCurrUser cc u $ setActiveChat ct cInfo
|
||||
CRChatItemDeleted u (AChatItem _ _ cInfo _) _ _ _ -> whenCurrUser cc u $ setActiveChat ct cInfo
|
||||
CRChatItemsDeleted u ((ChatItemDeletion (AChatItem _ _ cInfo _) _) : _) _ _ -> whenCurrUser cc u $ setActiveChat ct cInfo
|
||||
CRContactDeleted u c -> whenCurrUser cc u $ unsetActiveContact ct c
|
||||
CRGroupDeletedUser u g -> whenCurrUser cc u $ unsetActiveGroup ct g
|
||||
CRSentGroupInvitation u g _ _ -> whenCurrUser cc u $ setActiveGroup ct g
|
||||
|
||||
@@ -127,7 +127,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
||||
CRChatItemStatusUpdated u ci -> ttyUser u $ viewChatItemStatusUpdated ci ts tz testView showReceipts
|
||||
CRChatItemUpdated u (AChatItem _ _ chat item) -> ttyUser u $ unmuted u chat item $ viewItemUpdate chat item liveItems ts tz
|
||||
CRChatItemNotChanged u ci -> ttyUser u $ viewItemNotChanged ci
|
||||
CRChatItemDeleted u (AChatItem _ _ chat deletedItem) toItem byUser timed -> ttyUser u $ unmuted u chat deletedItem $ viewItemDelete chat deletedItem toItem byUser timed ts tz testView
|
||||
CRChatItemsDeleted u deletions byUser timed -> case deletions of
|
||||
[ChatItemDeletion (AChatItem _ _ chat deletedItem) toItem] ->
|
||||
ttyUser u $ unmuted u chat deletedItem $ viewItemDelete chat deletedItem toItem byUser timed ts tz testView
|
||||
deletions' -> ttyUser u [sShow (length deletions') <> " messages deleted"]
|
||||
CRChatItemReaction u added (ACIReaction _ _ chat reaction) -> ttyUser u $ unmutedReaction u chat reaction $ viewItemReaction showReactions chat reaction added ts tz
|
||||
CRChatItemDeletedNotFound u Contact {localDisplayName = c} _ -> ttyUser u [ttyFrom $ c <> "> [deleted - original message not found]"]
|
||||
CRBroadcastSent u mc s f t -> ttyUser u $ viewSentBroadcast mc s f ts tz t
|
||||
|
||||
+5
-4
@@ -224,10 +224,11 @@ testCfgCreateGroupDirect =
|
||||
mkCfgCreateGroupDirect testCfg
|
||||
|
||||
mkCfgCreateGroupDirect :: ChatConfig -> ChatConfig
|
||||
mkCfgCreateGroupDirect cfg = cfg {
|
||||
chatVRange = groupCreateDirectVRange,
|
||||
agentConfig = testAgentCfgSlow
|
||||
}
|
||||
mkCfgCreateGroupDirect cfg =
|
||||
cfg
|
||||
{ chatVRange = groupCreateDirectVRange,
|
||||
agentConfig = testAgentCfgSlow
|
||||
}
|
||||
|
||||
groupCreateDirectVRange :: VersionRangeChat
|
||||
groupCreateDirectVRange = mkVersionRange (VersionChat 1) (VersionChat 1)
|
||||
|
||||
@@ -15,6 +15,7 @@ import Data.Aeson (ToJSON)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.List (intercalate)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.AppSettings (defaultAppSettings)
|
||||
import qualified Simplex.Chat.AppSettings as AS
|
||||
@@ -44,6 +45,8 @@ chatDirectTests = do
|
||||
it "direct message update" testDirectMessageUpdate
|
||||
it "direct message edit history" testDirectMessageEditHistory
|
||||
it "direct message delete" testDirectMessageDelete
|
||||
it "direct message delete multiple" testDirectMessageDeleteMultiple
|
||||
it "direct message delete multiple (many chat batches)" testDirectMessageDeleteMultipleManyBatches
|
||||
it "direct live message" testDirectLiveMessage
|
||||
it "direct timed message" testDirectTimedMessage
|
||||
it "repeat AUTH errors disable contact" testRepeatAuthErrorsDisableContact
|
||||
@@ -685,6 +688,52 @@ testDirectMessageDelete =
|
||||
bob #$> ("/_delete item @2 " <> itemId 4 <> " internal", id, "message deleted")
|
||||
bob #$> ("/_get chat @2 count=100", chat', chatFeatures' <> [((0, "hello 🙂"), Nothing), ((1, "do you receive my messages?"), Just (0, "hello 🙂"))])
|
||||
|
||||
testDirectMessageDeleteMultiple :: HasCallStack => FilePath -> IO ()
|
||||
testDirectMessageDeleteMultiple =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
connectUsers alice bob
|
||||
|
||||
alice #> "@bob hello"
|
||||
bob <# "alice> hello"
|
||||
msgId1 <- lastItemId alice
|
||||
|
||||
alice #> "@bob hey"
|
||||
bob <# "alice> hey"
|
||||
msgId2 <- lastItemId alice
|
||||
|
||||
alice ##> ("/_delete item @2 " <> msgId1 <> "," <> msgId2 <> " broadcast")
|
||||
alice <## "2 messages deleted"
|
||||
bob <# "alice> [marked deleted] hello"
|
||||
bob <# "alice> [marked deleted] hey"
|
||||
|
||||
alice #$> ("/_get chat @2 count=2", chat, [(1, "hello [marked deleted]"), (1, "hey [marked deleted]")])
|
||||
bob #$> ("/_get chat @2 count=2", chat, [(0, "hello [marked deleted]"), (0, "hey [marked deleted]")])
|
||||
|
||||
testDirectMessageDeleteMultipleManyBatches :: HasCallStack => FilePath -> IO ()
|
||||
testDirectMessageDeleteMultipleManyBatches =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
connectUsers alice bob
|
||||
|
||||
alice #> "@bob message 0"
|
||||
bob <# "alice> message 0"
|
||||
msgIdFirst <- lastItemId alice
|
||||
|
||||
forM_ [(1 :: Int) .. 300] $ \i -> do
|
||||
alice #> ("@bob message " <> show i)
|
||||
bob <# ("alice> message " <> show i)
|
||||
msgIdLast <- lastItemId alice
|
||||
|
||||
let mIdFirst = read msgIdFirst :: Int
|
||||
mIdLast = read msgIdLast :: Int
|
||||
deleteIds = intercalate "," (map show [mIdFirst .. mIdLast])
|
||||
alice `send` ("/_delete item @2 " <> deleteIds <> " broadcast")
|
||||
_ <- getTermLine alice
|
||||
alice <## "301 messages deleted"
|
||||
forM_ [(0 :: Int) .. 300] $ \i -> do
|
||||
bob <# ("alice> [marked deleted] message " <> show i)
|
||||
|
||||
testDirectLiveMessage :: HasCallStack => FilePath -> IO ()
|
||||
testDirectLiveMessage =
|
||||
testChat2 aliceProfile bobProfile $ \alice bob -> do
|
||||
@@ -1138,6 +1187,7 @@ testSubscribeAppNSE tmp =
|
||||
alice <## "chat suspended"
|
||||
nseAlice ##> "/_start main=off"
|
||||
nseAlice <## "chat started"
|
||||
threadDelay 100000
|
||||
nseAlice ##> "/ad"
|
||||
cLink <- getContactLink nseAlice True
|
||||
bob ##> ("/c " <> cLink)
|
||||
|
||||
+127
-2
@@ -11,7 +11,7 @@ import ChatClient
|
||||
import ChatTests.Utils
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Control.Concurrent.Async (concurrently_)
|
||||
import Control.Monad (void, when)
|
||||
import Control.Monad (forM_, void, when)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.List (isInfixOf)
|
||||
import qualified Data.Text as T
|
||||
@@ -19,6 +19,7 @@ import Simplex.Chat.Controller (ChatConfig (..))
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Protocol (supportedChatVRange)
|
||||
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
|
||||
import Data.List (intercalate)
|
||||
import Simplex.Chat.Types (VersionRangeChat)
|
||||
import Simplex.Chat.Types.Shared (GroupMemberRole (..))
|
||||
import Simplex.Messaging.Agent.Env.SQLite
|
||||
@@ -52,12 +53,16 @@ chatGroupTests = do
|
||||
it "group message update" testGroupMessageUpdate
|
||||
it "group message edit history" testGroupMessageEditHistory
|
||||
it "group message delete" testGroupMessageDelete
|
||||
it "group message delete multiple" testGroupMessageDeleteMultiple
|
||||
it "group message delete multiple (many chat batches)" testGroupMessageDeleteMultipleManyBatches
|
||||
it "group live message" testGroupLiveMessage
|
||||
it "update group profile" testUpdateGroupProfile
|
||||
it "update member role" testUpdateMemberRole
|
||||
it "unused contacts are deleted after all their groups are deleted" testGroupDeleteUnusedContacts
|
||||
it "group description is shown as the first message to new members" testGroupDescription
|
||||
it "moderate message of another group member" testGroupModerate
|
||||
it "moderate own message (should process as deletion)" testGroupModerateOwn
|
||||
it "moderate multiple messages" testGroupModerateMultiple
|
||||
it "moderate message of another group member (full delete)" testGroupModerateFullDelete
|
||||
it "moderate message that arrives after the event of moderation" testGroupDelayedModeration
|
||||
it "moderate message that arrives after the event of moderation (full delete)" testGroupDelayedModerationFullDelete
|
||||
@@ -1254,6 +1259,77 @@ testGroupMessageDelete =
|
||||
bob #$> ("/_get chat #1 count=3", chat', [((0, "hello!"), Nothing), ((1, "hi alice"), Just (0, "hello!")), ((0, "how are you? [marked deleted]"), Nothing)])
|
||||
cath #$> ("/_get chat #1 count=3", chat', [((0, "hello!"), Nothing), ((0, "hi alice"), Just (0, "hello!")), ((1, "how are you? [marked deleted]"), Nothing)])
|
||||
|
||||
testGroupMessageDeleteMultiple :: HasCallStack => FilePath -> IO ()
|
||||
testGroupMessageDeleteMultiple =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
createGroup3 "team" alice bob cath
|
||||
|
||||
threadDelay 1000000
|
||||
alice #> "#team hello"
|
||||
concurrently_
|
||||
(bob <# "#team alice> hello")
|
||||
(cath <# "#team alice> hello")
|
||||
msgId1 <- lastItemId alice
|
||||
|
||||
threadDelay 1000000
|
||||
alice #> "#team hey"
|
||||
concurrently_
|
||||
(bob <# "#team alice> hey")
|
||||
(cath <# "#team alice> hey")
|
||||
msgId2 <- lastItemId alice
|
||||
|
||||
threadDelay 1000000
|
||||
alice ##> ("/_delete item #1 " <> msgId1 <> "," <> msgId2 <> " broadcast")
|
||||
alice <## "2 messages deleted"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
bob <# "#team alice> [marked deleted] hello"
|
||||
bob <# "#team alice> [marked deleted] hey",
|
||||
do
|
||||
cath <# "#team alice> [marked deleted] hello"
|
||||
cath <# "#team alice> [marked deleted] hey"
|
||||
]
|
||||
|
||||
alice #$> ("/_get chat #1 count=2", chat, [(1, "hello [marked deleted]"), (1, "hey [marked deleted]")])
|
||||
bob #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted]"), (0, "hey [marked deleted]")])
|
||||
cath #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted]"), (0, "hey [marked deleted]")])
|
||||
|
||||
testGroupMessageDeleteMultipleManyBatches :: HasCallStack => FilePath -> IO ()
|
||||
testGroupMessageDeleteMultipleManyBatches =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
createGroup3 "team" alice bob cath
|
||||
|
||||
bob ##> "/set receipts all off"
|
||||
bob <## "ok"
|
||||
cath ##> "/set receipts all off"
|
||||
cath <## "ok"
|
||||
|
||||
alice #> "#team message 0"
|
||||
concurrently_
|
||||
(bob <# "#team alice> message 0")
|
||||
(cath <# "#team alice> message 0")
|
||||
msgIdFirst <- lastItemId alice
|
||||
|
||||
forM_ [(1 :: Int) .. 300] $ \i -> do
|
||||
alice #> ("#team message " <> show i)
|
||||
concurrently_
|
||||
(bob <# ("#team alice> message " <> show i))
|
||||
(cath <# ("#team alice> message " <> show i))
|
||||
msgIdLast <- lastItemId alice
|
||||
|
||||
let mIdFirst = read msgIdFirst :: Int
|
||||
mIdLast = read msgIdLast :: Int
|
||||
deleteIds = intercalate "," (map show [mIdFirst .. mIdLast])
|
||||
alice `send` ("/_delete item #1 " <> deleteIds <> " broadcast")
|
||||
_ <- getTermLine alice
|
||||
alice <## "301 messages deleted"
|
||||
forM_ [(0 :: Int) .. 300] $ \i ->
|
||||
concurrently_
|
||||
(bob <# ("#team alice> [marked deleted] message " <> show i))
|
||||
(cath <# ("#team alice> [marked deleted] message " <> show i))
|
||||
|
||||
testGroupLiveMessage :: HasCallStack => FilePath -> IO ()
|
||||
testGroupLiveMessage =
|
||||
testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do
|
||||
@@ -1539,7 +1615,7 @@ testGroupModerate =
|
||||
(bob <# "#team alice> hello")
|
||||
(cath <# "#team alice> hello")
|
||||
bob ##> "\\\\ #team @alice hello"
|
||||
bob <## "#team: you have insufficient permissions for this action, the required role is owner"
|
||||
bob <## "cannot delete this item"
|
||||
threadDelay 1000000
|
||||
cath #> "#team hi"
|
||||
concurrently_
|
||||
@@ -1554,6 +1630,55 @@ testGroupModerate =
|
||||
bob #$> ("/_get chat #1 count=1", chat, [(0, "hi [marked deleted by you]")])
|
||||
cath #$> ("/_get chat #1 count=1", chat, [(1, "hi [marked deleted by bob]")])
|
||||
|
||||
testGroupModerateOwn :: HasCallStack => FilePath -> IO ()
|
||||
testGroupModerateOwn =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
\alice bob -> do
|
||||
createGroup2 "team" alice bob
|
||||
threadDelay 1000000
|
||||
alice #> "#team hello"
|
||||
bob <# "#team alice> hello"
|
||||
alice ##> "\\\\ #team @alice hello"
|
||||
alice <## "message marked deleted by you"
|
||||
bob <# "#team alice> [marked deleted by alice] hello"
|
||||
alice #$> ("/_get chat #1 count=1", chat, [(1, "hello [marked deleted by you]")])
|
||||
bob #$> ("/_get chat #1 count=1", chat, [(0, "hello [marked deleted by alice]")])
|
||||
|
||||
testGroupModerateMultiple :: HasCallStack => FilePath -> IO ()
|
||||
testGroupModerateMultiple =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
createGroup3 "team" alice bob cath
|
||||
|
||||
threadDelay 1000000
|
||||
alice #> "#team hello"
|
||||
concurrently_
|
||||
(bob <# "#team alice> hello")
|
||||
(cath <# "#team alice> hello")
|
||||
msgId1 <- lastItemId alice
|
||||
|
||||
threadDelay 1000000
|
||||
bob #> "#team hey"
|
||||
concurrently_
|
||||
(alice <# "#team bob> hey")
|
||||
(cath <# "#team bob> hey")
|
||||
msgId2 <- lastItemId alice
|
||||
|
||||
alice ##> ("/_delete member item #1 " <> msgId1 <> "," <> msgId2)
|
||||
alice <## "2 messages deleted"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
bob <# "#team alice> [marked deleted by alice] hello"
|
||||
bob <# "#team bob> [marked deleted by alice] hey",
|
||||
do
|
||||
cath <# "#team alice> [marked deleted by alice] hello"
|
||||
cath <# "#team bob> [marked deleted by alice] hey"
|
||||
]
|
||||
|
||||
alice #$> ("/_get chat #1 count=2", chat, [(1, "hello [marked deleted by you]"), (0, "hey [marked deleted by you]")])
|
||||
bob #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted by alice]"), (1, "hey [marked deleted by alice]")])
|
||||
cath #$> ("/_get chat #1 count=2", chat, [(0, "hello [marked deleted by alice]"), (0, "hey [marked deleted by alice]")])
|
||||
|
||||
testGroupModerateFullDelete :: HasCallStack => FilePath -> IO ()
|
||||
testGroupModerateFullDelete =
|
||||
testChatCfg3 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile $
|
||||
|
||||
@@ -2169,7 +2169,7 @@ testSetUITheme =
|
||||
a <## "you've shared main profile with this contact"
|
||||
a <## "connection not verified, use /code command to see security code"
|
||||
a <## "quantum resistant end-to-end encryption"
|
||||
a <## "peer chat protocol version range: (Version 1, Version 8)"
|
||||
a <## "peer chat protocol version range: (Version 1, Version 9)"
|
||||
groupInfo a = do
|
||||
a <## "group ID: 1"
|
||||
a <## "current members: 1"
|
||||
|
||||
@@ -133,7 +133,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||
it "x.msg.new chat message with chat version range" $
|
||||
"{\"v\":\"1-8\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
"{\"v\":\"1-9\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||
it "x.msg.new quote" $
|
||||
"{\"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\"}}}}"
|
||||
@@ -243,13 +243,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
|
||||
it "x.grp.mem.new with member chat version range" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-8\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile}
|
||||
it "x.grp.mem.intro" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing
|
||||
it "x.grp.mem.intro with member chat version range" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-8\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing
|
||||
it "x.grp.mem.intro with member restrictions" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
@@ -264,7 +264,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
|
||||
it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-8\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-9\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
|
||||
it "x.grp.mem.info" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
||||
|
||||
Reference in New Issue
Block a user