mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 11:54:07 +00:00
core: create business chat when preparing via contact link data with business flag (#6008)
* core: create business chat when preparing via contact link data with business flag * add to test * plans * ios * ios
This commit is contained in:
@@ -767,13 +767,21 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
private var connectingText: LocalizedStringKey? {
|
||||
if let contact = chat.chatInfo.contact,
|
||||
!contact.sndReady && contact.active && !contact.sendMsgToConnect && !contact.nextAcceptContactRequest {
|
||||
contact.preparedContact?.uiConnLinkType == .con
|
||||
? "contact should accept…"
|
||||
: "connecting…"
|
||||
} else {
|
||||
nil
|
||||
switch (chat.chatInfo) {
|
||||
case let .direct(contact):
|
||||
if !contact.sndReady && contact.active && !contact.sendMsgToConnect && !contact.nextAcceptContactRequest {
|
||||
contact.preparedContact?.uiConnLinkType == .con
|
||||
? "contact should accept…"
|
||||
: "connecting…"
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
case let .group(groupInfo, _):
|
||||
switch (groupInfo.membership.memberStatus) {
|
||||
case .memAccepted: "connecting…" // TODO [short links] add member status to show transition from prepared group to started connection earlier?
|
||||
default: nil
|
||||
}
|
||||
default: nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -402,7 +402,11 @@ struct ComposeView: View {
|
||||
|
||||
if chat.chatInfo.groupInfo?.nextConnectPrepared == true {
|
||||
Button(action: connectPreparedGroup) {
|
||||
Label("Join group", systemImage: "person.2.fill")
|
||||
if chat.chatInfo.groupInfo?.businessChat == nil {
|
||||
Label("Join group", systemImage: "person.2.fill")
|
||||
} else {
|
||||
Label("Connect", systemImage: "briefcase.fill")
|
||||
}
|
||||
}
|
||||
.frame(height: 60)
|
||||
} else if contact?.nextSendGrpInv == true {
|
||||
|
||||
@@ -1014,7 +1014,12 @@ private func showPrepareContactAlert(
|
||||
) {
|
||||
showOpenChatAlert(
|
||||
profileName: contactShortLinkData.profile.displayName,
|
||||
profileImage: ProfileImage(imageStr: contactShortLinkData.profile.image, size: 60),
|
||||
profileImage:
|
||||
ProfileImage(
|
||||
imageStr: contactShortLinkData.profile.image,
|
||||
iconName: contactShortLinkData.business ? "briefcase.circle.fill" : "person.crop.circle.fill",
|
||||
size: 60
|
||||
),
|
||||
theme: theme,
|
||||
cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"),
|
||||
confirmTitle: NSLocalizedString("Open chat", comment: "new chat action"),
|
||||
|
||||
@@ -1735,22 +1735,38 @@ processChatCommand' vr = \case
|
||||
pure conn'
|
||||
APIConnectPlan userId cLink -> withUserId userId $ \user ->
|
||||
uncurry (CRConnectionPlan user) <$> connectPlan user cLink
|
||||
APIPrepareContact userId accLink@(ACCL _ (CCLink cReq _)) contactSLinkData -> withUserId userId $ \user -> do
|
||||
APIPrepareContact userId accLink contactSLinkData -> withUserId userId $ \user -> do
|
||||
let ContactShortLinkData {profile, message, business} = contactSLinkData
|
||||
-- TODO [short links] create business contact as group
|
||||
ct <- withStore $ \db -> createPreparedContact db user profile accLink
|
||||
let createItem content = createInternalItemForChat user (CDDirectRcv ct) False content Nothing
|
||||
cInfo = DirectChat ct
|
||||
void $ createItem $ CIRcvDirectE2EEInfo $ E2EInfo $ connRequestPQEncryption cReq
|
||||
void $ createFeatureEnabledItems_ user ct
|
||||
aci <- mapM (createItem . CIRcvMsgContent . MCText) message
|
||||
let chat = case aci of
|
||||
Just (AChatItem SCTDirect dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci}
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTDirect chat
|
||||
case accLink of
|
||||
ACCL SCMContact ccLink
|
||||
| business -> do
|
||||
let Profile {preferences} = profile
|
||||
groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences
|
||||
groupProfile = businessGroupProfile profile groupPreferences
|
||||
(gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user groupProfile True ccLink
|
||||
let cd = CDGroupRcv gInfo Nothing hostMember
|
||||
createItem content = createInternalItemForChat user cd True content Nothing
|
||||
cInfo = GroupChat gInfo Nothing
|
||||
void $ createGroupFeatureItems_ user cd True CIRcvGroupFeature gInfo
|
||||
aci <- mapM (createItem . CIRcvMsgContent . MCText) message
|
||||
let chat = case aci of
|
||||
Just (AChatItem SCTGroup dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci}
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTGroup chat
|
||||
ACCL _ (CCLink cReq _) -> do
|
||||
ct <- withStore $ \db -> createPreparedContact db user profile accLink
|
||||
let createItem content = createInternalItemForChat user (CDDirectRcv ct) False content Nothing
|
||||
cInfo = DirectChat ct
|
||||
void $ createItem $ CIRcvDirectE2EEInfo $ E2EInfo $ connRequestPQEncryption cReq
|
||||
void $ createFeatureEnabledItems_ user ct
|
||||
aci <- mapM (createItem . CIRcvMsgContent . MCText) message
|
||||
let chat = case aci of
|
||||
Just (AChatItem SCTDirect dir _ ci) -> Chat cInfo [CChatItem dir ci] emptyChatStats {unreadCount = 1, minUnreadItemId = chatItemId' ci}
|
||||
_ -> Chat cInfo [] emptyChatStats
|
||||
pure $ CRNewPreparedChat user $ AChat SCTDirect chat
|
||||
APIPrepareGroup userId ccLink groupSLinkData -> withUserId userId $ \user -> do
|
||||
let GroupShortLinkData {groupProfile = gp@GroupProfile {description}} = groupSLinkData
|
||||
(gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user gp ccLink
|
||||
(gInfo, hostMember) <- withStore $ \db -> createPreparedGroup db vr user gp False ccLink
|
||||
let cd = CDGroupRcv gInfo Nothing hostMember
|
||||
createItem content = createInternalItemForChat user cd True content Nothing
|
||||
cInfo = GroupChat gInfo Nothing
|
||||
|
||||
@@ -1023,10 +1023,10 @@ acceptBusinessJoinRequestAsync
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createGroupFeatureItems user cd CISndGroupFeature gInfo
|
||||
pure (gInfo, clientMember)
|
||||
where
|
||||
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
|
||||
businessGroupProfile Profile {displayName, fullName, image} groupPreferences =
|
||||
GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
|
||||
|
||||
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
|
||||
businessGroupProfile Profile {displayName, fullName, image} groupPreferences =
|
||||
GroupProfile {displayName, fullName, description = Nothing, image, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
|
||||
|
||||
profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile
|
||||
profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing
|
||||
@@ -2355,7 +2355,7 @@ sameGroupProfileInfo p p' = p {groupPreferences = Nothing} == p' {groupPreferenc
|
||||
createGroupFeatureItems :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM ()
|
||||
createGroupFeatureItems user cd ciContent g = createGroupFeatureItems_ user cd False ciContent g >>= toView . CEvtNewChatItems user
|
||||
|
||||
createGroupFeatureItems_ :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> Bool -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM [AChatItem]
|
||||
createGroupFeatureItems_ :: MsgDirectionI d => User -> ChatDirection 'CTGroup d -> ShowGroupAsSender -> (GroupFeature -> GroupPreference -> Maybe Int -> Maybe GroupMemberRole -> CIContent d) -> GroupInfo -> CM [AChatItem]
|
||||
createGroupFeatureItems_ user cd showGroupAsSender ciContent GroupInfo {fullGroupPreferences} =
|
||||
forM allGroupFeatures $ \(AGF f) -> do
|
||||
let p = getGroupPreference f fullGroupPreferences
|
||||
@@ -2367,7 +2367,7 @@ createInternalChatItem user cd content itemTs_ = do
|
||||
ci <- createInternalItemForChat user cd False content itemTs_
|
||||
toView $ CEvtNewChatItems user [ci]
|
||||
|
||||
createInternalItemForChat :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> Bool -> CIContent d -> Maybe UTCTime -> CM AChatItem
|
||||
createInternalItemForChat :: (ChatTypeI c, MsgDirectionI d) => User -> ChatDirection c d -> ShowGroupAsSender -> CIContent d -> Maybe UTCTime -> CM AChatItem
|
||||
createInternalItemForChat user cd showGroupAsSender content itemTs_ =
|
||||
lift (createInternalItemsForChats user itemTs_ [(cd, showGroupAsSender, [content])]) >>= \case
|
||||
[Right ci] -> pure ci
|
||||
|
||||
@@ -525,15 +525,16 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {
|
||||
DB.execute db "DELETE FROM contacts WHERE contact_id = ?" (Only contactId)
|
||||
DB.execute db "DELETE FROM contact_profiles WHERE contact_profile_id = ?" (Only profileId)
|
||||
|
||||
createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> CreatedLinkContact -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createPreparedGroup db vr user@User {userId, userContactId} groupProfile connLinkToConnect = do
|
||||
createPreparedGroup :: DB.Connection -> VersionRangeChat -> User -> GroupProfile -> Bool -> CreatedLinkContact -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createPreparedGroup db vr user@User {userId, userContactId} groupProfile business connLinkToConnect = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
(groupId, groupLDN) <- createGroup_ db userId groupProfile (Just connLinkToConnect) Nothing currentTs
|
||||
hostMemberId <- insertHost_ currentTs groupId groupLDN
|
||||
let userMember = MemberIdRole (MemberId $ encodeUtf8 groupLDN <> "_user_unknown_id") GRMember
|
||||
void $ createContactMemberInv_ db user groupId (Just hostMemberId) user userMember GCUserMember GSMemUnknown IBUnknown Nothing currentTs vr
|
||||
g <- getGroupInfo db vr user groupId
|
||||
membership <- createContactMemberInv_ db user groupId (Just hostMemberId) user userMember GCUserMember GSMemUnknown IBUnknown Nothing currentTs vr
|
||||
hostMember <- getGroupMember db vr user groupId hostMemberId
|
||||
when business $ liftIO $ setGroupBusinessChatInfo groupId membership hostMember
|
||||
g <- getGroupInfo db vr user groupId
|
||||
pure (g, hostMember)
|
||||
where
|
||||
insertHost_ currentTs groupId groupLDN = do
|
||||
@@ -553,6 +554,23 @@ createPreparedGroup db vr user@User {userId, userContactId} groupProfile connLin
|
||||
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
|
||||
)
|
||||
insertedRowId db
|
||||
setGroupBusinessChatInfo :: GroupId -> GroupMember -> GroupMember -> IO ()
|
||||
setGroupBusinessChatInfo groupId membership hostMember = do
|
||||
let businessChatInfo = Just BusinessChatInfo {chatType = BCBusiness, businessId = memberId' hostMember, customerId = memberId' membership}
|
||||
updateBusinessChatInfo db groupId businessChatInfo
|
||||
|
||||
updateBusinessChatInfo :: DB.Connection -> GroupId -> Maybe BusinessChatInfo -> IO ()
|
||||
updateBusinessChatInfo db groupId businessChatInfo =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET business_chat = ?,
|
||||
business_member_id = ?,
|
||||
customer_member_id = ?
|
||||
WHERE group_id = ?
|
||||
|]
|
||||
(businessChatInfoRow businessChatInfo :. (Only groupId))
|
||||
|
||||
updatePreparedGroupUser :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> User -> ExceptT StoreError IO GroupInfo
|
||||
updatePreparedGroupUser db vr user gInfo@GroupInfo {groupId, membership} hostMember newUser@User {userId = newUserId} = do
|
||||
@@ -619,33 +637,36 @@ updatePreparedGroupUser db vr user gInfo@GroupInfo {groupId, membership} hostMem
|
||||
safeDeleteLDN db user oldHostLDN
|
||||
|
||||
updatePreparedUserAndHostMembersInvited :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
updatePreparedUserAndHostMembersInvited db vr user gInfo hostMember GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted} = do
|
||||
updatePreparedUserAndHostMembersInvited db vr user gInfo hostMember GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted, business} = do
|
||||
let fromMemberProfile = profileFromName fromMemberName
|
||||
initialStatus = maybe GSMemAccepted (acceptanceToStatus $ memberAdmission groupProfile) accepted
|
||||
updatePreparedUserAndHostMembers' db vr user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile initialStatus
|
||||
updatePreparedUserAndHostMembers' db vr user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile business initialStatus
|
||||
|
||||
updatePreparedUserAndHostMembersRejected :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
updatePreparedUserAndHostMembersRejected db vr user gInfo hostMember GroupLinkRejection {fromMember = fromMember@MemberIdRole {memberId}, invitedMember, groupProfile} = do
|
||||
let fromMemberProfile = profileFromName $ nameFromMemberId memberId
|
||||
updatePreparedUserAndHostMembers' db vr user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile GSMemRejected
|
||||
updatePreparedUserAndHostMembers' db vr user gInfo hostMember fromMember fromMemberProfile invitedMember groupProfile Nothing GSMemRejected
|
||||
|
||||
updatePreparedUserAndHostMembers' :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
updatePreparedUserAndHostMembers' :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupMember -> MemberIdRole -> Profile -> MemberIdRole -> GroupProfile -> Maybe BusinessChatInfo -> GroupMemberStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
updatePreparedUserAndHostMembers'
|
||||
db
|
||||
vr
|
||||
user
|
||||
gInfo@GroupInfo {groupId, groupProfile = gp}
|
||||
gInfo@GroupInfo {groupId, groupProfile = gp, businessChat}
|
||||
hostMember
|
||||
fromMember
|
||||
fromMemberProfile
|
||||
invitedMember
|
||||
groupProfile
|
||||
business
|
||||
membershipStatus = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
liftIO $ updateUserMember currentTs
|
||||
hostMember' <- updateHostMember currentTs
|
||||
when (gp /= groupProfile) $
|
||||
void $ updateGroupProfile db user gInfo groupProfile
|
||||
when (isJust businessChat && isJust business) $
|
||||
liftIO $ updateBusinessChatInfo db groupId business
|
||||
gInfo' <- getGroupInfo db vr user groupId
|
||||
pure (gInfo', hostMember')
|
||||
where
|
||||
|
||||
@@ -4562,6 +4562,16 @@ Query:
|
||||
Plan:
|
||||
SEARCH group_snd_item_statuses USING INDEX idx_group_snd_item_statuses_chat_item_id_group_member_id (chat_item_id=? AND group_member_id=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET business_chat = ?,
|
||||
business_member_id = ?,
|
||||
customer_member_id = ?
|
||||
WHERE group_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET members_require_attention = members_require_attention + 1
|
||||
|
||||
@@ -1888,7 +1888,7 @@ viewGroupUserChanged
|
||||
viewConnectionPlan :: ChatConfig -> ACreatedConnLink -> ConnectionPlan -> [StyledString]
|
||||
viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
CPInvitationLink ilp -> case ilp of
|
||||
ILPOk contactSLinkData -> [invLink "ok to connect"] <> [viewJSON contactSLinkData | testView]
|
||||
ILPOk contactSLinkData -> [invOrBiz contactSLinkData "ok to connect"] <> [viewJSON contactSLinkData | testView]
|
||||
ILPOwnLink -> [invLink "own link"]
|
||||
ILPConnecting Nothing -> [invLink "connecting"]
|
||||
ILPConnecting (Just ct) -> [invLink ("connecting to contact " <> ttyContact' ct)]
|
||||
@@ -1898,8 +1898,12 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
]
|
||||
where
|
||||
invLink = ("invitation link: " <>)
|
||||
invOrBiz = \case
|
||||
Just ContactShortLinkData {business}
|
||||
| business -> ("business link: " <>)
|
||||
_ -> ("invitation link: " <>)
|
||||
CPContactAddress cap -> case cap of
|
||||
CAPOk contactSLinkData -> [ctAddr "ok to connect"] <> [viewJSON contactSLinkData | testView]
|
||||
CAPOk contactSLinkData -> [addrOrBiz contactSLinkData "ok to connect"] <> [viewJSON contactSLinkData | testView]
|
||||
CAPOwnLink -> [ctAddr "own address"]
|
||||
CAPConnectingConfirmReconnect -> [ctAddr "connecting, allowed to reconnect"]
|
||||
CAPConnectingProhibit ct -> [ctAddr ("connecting to contact " <> ttyContact' ct)]
|
||||
@@ -1910,6 +1914,10 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
|
||||
CAPContactViaAddress ct -> [ctAddr ("known contact without connection " <> ttyContact' ct)]
|
||||
where
|
||||
ctAddr = ("contact address: " <>)
|
||||
addrOrBiz = \case
|
||||
Just ContactShortLinkData {business}
|
||||
| business -> ("business link: " <>)
|
||||
_ -> ("contact address: " <>)
|
||||
CPGroupLink glp -> case glp of
|
||||
GLPOk groupSLinkData -> [grpLink "ok to connect"] <> [viewJSON groupSLinkData | testView]
|
||||
GLPOwnLink g -> [grpLink "own link for group " <> ttyGroup' g]
|
||||
|
||||
@@ -114,6 +114,7 @@ chatProfileTests = do
|
||||
it "prepare contact with image in profile" testShortLinkInvitationImage
|
||||
it "prepare contact with a long name in profile" testShortLinkInvitationLongName
|
||||
it "prepare contact using address short link data and connect" testShortLinkAddressPrepareContact
|
||||
it "prepare business chat using address short link data and connect" testShortLinkAddressPrepareBusiness
|
||||
it "prepare group using group short link data and connect" testShortLinkPrepareGroup
|
||||
it "prepare group using group short link data and connect, host rejects" testShortLinkPrepareGroupReject
|
||||
it "connect to prepared contact incognito (via invitation)" testShortLinkInvitationConnectPreparedContactIncognito
|
||||
@@ -2979,6 +2980,59 @@ testShortLinkAddressPrepareContact =
|
||||
(alice <## "bob (Bob): contact is connected")
|
||||
alice <##> bob
|
||||
|
||||
testShortLinkAddressPrepareBusiness :: HasCallStack => TestParams -> IO ()
|
||||
testShortLinkAddressPrepareBusiness =
|
||||
testChat3 businessProfile aliceProfile {fullName = "Alice @ Biz"} bobProfile $
|
||||
\biz alice bob -> do
|
||||
biz ##> "/ad"
|
||||
(shortLink, fullLink) <- getContactLinks biz True
|
||||
biz ##> "/auto_accept on business"
|
||||
biz <## "auto_accept on, business"
|
||||
bob ##> ("/_connect plan 1 " <> shortLink)
|
||||
bob <## "business link: ok to connect"
|
||||
contactSLinkData <- getTermLine bob
|
||||
bob ##> ("/_prepare contact 1 " <> fullLink <> " " <> shortLink <> " " <> contactSLinkData)
|
||||
bob <## "#biz: group is prepared"
|
||||
bob ##> "/_connect group #1"
|
||||
bob <## "#biz: connection started"
|
||||
biz <## "#bob (Bob): accepting business address request..."
|
||||
bob <## "#biz: joining the group..."
|
||||
-- the next command can be prone to race conditions
|
||||
bob ##> ("/_connect plan 1 " <> shortLink)
|
||||
bob <## "business link: connecting to business #biz"
|
||||
biz <## "#bob: bob_1 joined the group"
|
||||
bob <## "#biz: you joined the group"
|
||||
biz #> "#bob hi"
|
||||
bob <# "#biz biz_1> hi"
|
||||
bob #> "#biz hello"
|
||||
biz <# "#bob bob_1> hello"
|
||||
|
||||
connectUsers biz alice
|
||||
biz <##> alice
|
||||
biz ##> "/a #bob alice"
|
||||
biz <## "invitation to join the group #bob sent to alice"
|
||||
alice <## "#bob (Bob): biz invites you to join the group as member"
|
||||
alice <## "use /j bob to accept"
|
||||
alice ##> "/j bob"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
alice <## "#bob: you joined the group"
|
||||
alice <### [WithTime "#bob biz> hi [>>]", WithTime "#bob bob_1> hello [>>]"]
|
||||
alice <## "#bob: member bob_1 (Bob) is connected",
|
||||
biz <## "#bob: alice joined the group",
|
||||
do
|
||||
bob <## "#biz: biz_1 added alice (Alice @ Biz) to the group (connecting...)"
|
||||
bob <## "#biz: new member alice is connected"
|
||||
]
|
||||
alice #> "#bob hey"
|
||||
concurrently_
|
||||
(bob <# "#biz alice> hey")
|
||||
(biz <# "#bob alice> hey")
|
||||
bob #> "#biz hey there"
|
||||
concurrently_
|
||||
(alice <# "#bob bob_1> hey there")
|
||||
(biz <# "#bob bob_1> hey there")
|
||||
|
||||
testShortLinkPrepareGroup :: HasCallStack => TestParams -> IO ()
|
||||
testShortLinkPrepareGroup =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
|
||||
Reference in New Issue
Block a user