core, ui: create dummy member record when admin forwards a message from an unknown member (#3651)
* core: create dummy member record when admin forwards a message from an unknown member * comments * update unknown member if announced * change removed * change unknown name, revert diff * revert diff * ios * update ios library * android * remove changes in iOS project file * rename event * remove unknown category * android --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
parent
d9d270f00e
commit
bfe5d51df7
@ -1822,9 +1822,16 @@ public struct GroupMember: Identifiable, Decodable {
|
|||||||
public var chatViewName: String {
|
public var chatViewName: String {
|
||||||
get {
|
get {
|
||||||
let p = memberProfile
|
let p = memberProfile
|
||||||
return p.localAlias == ""
|
let name = (
|
||||||
? p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)")
|
p.localAlias == ""
|
||||||
: p.localAlias
|
? p.displayName + (p.fullName == "" || p.fullName == p.displayName ? "" : " / \(p.fullName)")
|
||||||
|
: p.localAlias
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
memberStatus == .memUnknown
|
||||||
|
? String.localizedStringWithFormat(NSLocalizedString("_Previous member_ %@", comment: "previous/unknown group member"), name)
|
||||||
|
: name
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1833,6 +1840,7 @@ public struct GroupMember: Identifiable, Decodable {
|
|||||||
case .memRemoved: return false
|
case .memRemoved: return false
|
||||||
case .memLeft: return false
|
case .memLeft: return false
|
||||||
case .memGroupDeleted: return false
|
case .memGroupDeleted: return false
|
||||||
|
case .memUnknown: return false
|
||||||
case .memInvited: return false
|
case .memInvited: return false
|
||||||
case .memIntroduced: return false
|
case .memIntroduced: return false
|
||||||
case .memIntroInvited: return false
|
case .memIntroInvited: return false
|
||||||
@ -1849,6 +1857,7 @@ public struct GroupMember: Identifiable, Decodable {
|
|||||||
case .memRemoved: return false
|
case .memRemoved: return false
|
||||||
case .memLeft: return false
|
case .memLeft: return false
|
||||||
case .memGroupDeleted: return false
|
case .memGroupDeleted: return false
|
||||||
|
case .memUnknown: return false
|
||||||
case .memInvited: return false
|
case .memInvited: return false
|
||||||
case .memIntroduced: return true
|
case .memIntroduced: return true
|
||||||
case .memIntroInvited: return true
|
case .memIntroInvited: return true
|
||||||
@ -1953,6 +1962,7 @@ public enum GroupMemberStatus: String, Decodable {
|
|||||||
case memRemoved = "removed"
|
case memRemoved = "removed"
|
||||||
case memLeft = "left"
|
case memLeft = "left"
|
||||||
case memGroupDeleted = "deleted"
|
case memGroupDeleted = "deleted"
|
||||||
|
case memUnknown = "unknown"
|
||||||
case memInvited = "invited"
|
case memInvited = "invited"
|
||||||
case memIntroduced = "introduced"
|
case memIntroduced = "introduced"
|
||||||
case memIntroInvited = "intro-inv"
|
case memIntroInvited = "intro-inv"
|
||||||
@ -1967,6 +1977,7 @@ public enum GroupMemberStatus: String, Decodable {
|
|||||||
case .memRemoved: return "removed"
|
case .memRemoved: return "removed"
|
||||||
case .memLeft: return "left"
|
case .memLeft: return "left"
|
||||||
case .memGroupDeleted: return "group deleted"
|
case .memGroupDeleted: return "group deleted"
|
||||||
|
case .memUnknown: return "unknown status"
|
||||||
case .memInvited: return "invited"
|
case .memInvited: return "invited"
|
||||||
case .memIntroduced: return "connecting (introduced)"
|
case .memIntroduced: return "connecting (introduced)"
|
||||||
case .memIntroInvited: return "connecting (introduction invitation)"
|
case .memIntroInvited: return "connecting (introduction invitation)"
|
||||||
@ -1983,6 +1994,7 @@ public enum GroupMemberStatus: String, Decodable {
|
|||||||
case .memRemoved: return "removed"
|
case .memRemoved: return "removed"
|
||||||
case .memLeft: return "left"
|
case .memLeft: return "left"
|
||||||
case .memGroupDeleted: return "group deleted"
|
case .memGroupDeleted: return "group deleted"
|
||||||
|
case .memUnknown: return "unknown"
|
||||||
case .memInvited: return "invited"
|
case .memInvited: return "invited"
|
||||||
case .memIntroduced: return "connecting"
|
case .memIntroduced: return "connecting"
|
||||||
case .memIntroInvited: return "connecting"
|
case .memIntroInvited: return "connecting"
|
||||||
|
@ -1268,12 +1268,19 @@ data class GroupMember (
|
|||||||
val verified get() = activeConn?.connectionCode != null
|
val verified get() = activeConn?.connectionCode != null
|
||||||
|
|
||||||
val chatViewName: String
|
val chatViewName: String
|
||||||
get() = memberProfile.localAlias.ifEmpty { displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName") }
|
get() {
|
||||||
|
val name = memberProfile.localAlias.ifEmpty { displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName") }
|
||||||
|
return if (memberStatus == GroupMemberStatus.MemUnknown)
|
||||||
|
String.format(generalGetString(MR.strings.previous_member_vName), name)
|
||||||
|
else
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
val memberActive: Boolean get() = when (this.memberStatus) {
|
val memberActive: Boolean get() = when (this.memberStatus) {
|
||||||
GroupMemberStatus.MemRemoved -> false
|
GroupMemberStatus.MemRemoved -> false
|
||||||
GroupMemberStatus.MemLeft -> false
|
GroupMemberStatus.MemLeft -> false
|
||||||
GroupMemberStatus.MemGroupDeleted -> false
|
GroupMemberStatus.MemGroupDeleted -> false
|
||||||
|
GroupMemberStatus.MemUnknown -> false
|
||||||
GroupMemberStatus.MemInvited -> false
|
GroupMemberStatus.MemInvited -> false
|
||||||
GroupMemberStatus.MemIntroduced -> false
|
GroupMemberStatus.MemIntroduced -> false
|
||||||
GroupMemberStatus.MemIntroInvited -> false
|
GroupMemberStatus.MemIntroInvited -> false
|
||||||
@ -1288,6 +1295,7 @@ data class GroupMember (
|
|||||||
GroupMemberStatus.MemRemoved -> false
|
GroupMemberStatus.MemRemoved -> false
|
||||||
GroupMemberStatus.MemLeft -> false
|
GroupMemberStatus.MemLeft -> false
|
||||||
GroupMemberStatus.MemGroupDeleted -> false
|
GroupMemberStatus.MemGroupDeleted -> false
|
||||||
|
GroupMemberStatus.MemUnknown -> false
|
||||||
GroupMemberStatus.MemInvited -> false
|
GroupMemberStatus.MemInvited -> false
|
||||||
GroupMemberStatus.MemIntroduced -> true
|
GroupMemberStatus.MemIntroduced -> true
|
||||||
GroupMemberStatus.MemIntroInvited -> true
|
GroupMemberStatus.MemIntroInvited -> true
|
||||||
@ -1377,6 +1385,7 @@ enum class GroupMemberStatus {
|
|||||||
@SerialName("removed") MemRemoved,
|
@SerialName("removed") MemRemoved,
|
||||||
@SerialName("left") MemLeft,
|
@SerialName("left") MemLeft,
|
||||||
@SerialName("deleted") MemGroupDeleted,
|
@SerialName("deleted") MemGroupDeleted,
|
||||||
|
@SerialName("unknown") MemUnknown,
|
||||||
@SerialName("invited") MemInvited,
|
@SerialName("invited") MemInvited,
|
||||||
@SerialName("introduced") MemIntroduced,
|
@SerialName("introduced") MemIntroduced,
|
||||||
@SerialName("intro-inv") MemIntroInvited,
|
@SerialName("intro-inv") MemIntroInvited,
|
||||||
@ -1390,6 +1399,7 @@ enum class GroupMemberStatus {
|
|||||||
MemRemoved -> generalGetString(MR.strings.group_member_status_removed)
|
MemRemoved -> generalGetString(MR.strings.group_member_status_removed)
|
||||||
MemLeft -> generalGetString(MR.strings.group_member_status_left)
|
MemLeft -> generalGetString(MR.strings.group_member_status_left)
|
||||||
MemGroupDeleted -> generalGetString(MR.strings.group_member_status_group_deleted)
|
MemGroupDeleted -> generalGetString(MR.strings.group_member_status_group_deleted)
|
||||||
|
MemUnknown -> generalGetString(MR.strings.group_member_status_unknown)
|
||||||
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
|
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
|
||||||
MemIntroduced -> generalGetString(MR.strings.group_member_status_introduced)
|
MemIntroduced -> generalGetString(MR.strings.group_member_status_introduced)
|
||||||
MemIntroInvited -> generalGetString(MR.strings.group_member_status_intro_invitation)
|
MemIntroInvited -> generalGetString(MR.strings.group_member_status_intro_invitation)
|
||||||
@ -1404,6 +1414,7 @@ enum class GroupMemberStatus {
|
|||||||
MemRemoved -> generalGetString(MR.strings.group_member_status_removed)
|
MemRemoved -> generalGetString(MR.strings.group_member_status_removed)
|
||||||
MemLeft -> generalGetString(MR.strings.group_member_status_left)
|
MemLeft -> generalGetString(MR.strings.group_member_status_left)
|
||||||
MemGroupDeleted -> generalGetString(MR.strings.group_member_status_group_deleted)
|
MemGroupDeleted -> generalGetString(MR.strings.group_member_status_group_deleted)
|
||||||
|
MemUnknown -> generalGetString(MR.strings.group_member_status_unknown_short)
|
||||||
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
|
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
|
||||||
MemIntroduced -> generalGetString(MR.strings.group_member_status_connecting)
|
MemIntroduced -> generalGetString(MR.strings.group_member_status_connecting)
|
||||||
MemIntroInvited -> generalGetString(MR.strings.group_member_status_connecting)
|
MemIntroInvited -> generalGetString(MR.strings.group_member_status_connecting)
|
||||||
|
@ -1217,6 +1217,7 @@
|
|||||||
<string name="group_member_status_removed">removed</string>
|
<string name="group_member_status_removed">removed</string>
|
||||||
<string name="group_member_status_left">left</string>
|
<string name="group_member_status_left">left</string>
|
||||||
<string name="group_member_status_group_deleted">group deleted</string>
|
<string name="group_member_status_group_deleted">group deleted</string>
|
||||||
|
<string name="group_member_status_unknown">unknown status</string>
|
||||||
<string name="group_member_status_invited">invited</string>
|
<string name="group_member_status_invited">invited</string>
|
||||||
<string name="group_member_status_introduced">connecting (introduced)</string>
|
<string name="group_member_status_introduced">connecting (introduced)</string>
|
||||||
<string name="group_member_status_intro_invitation">connecting (introduction invitation)</string>
|
<string name="group_member_status_intro_invitation">connecting (introduction invitation)</string>
|
||||||
@ -1227,6 +1228,9 @@
|
|||||||
<string name="group_member_status_creator">creator</string>
|
<string name="group_member_status_creator">creator</string>
|
||||||
|
|
||||||
<string name="group_member_status_connecting">connecting</string>
|
<string name="group_member_status_connecting">connecting</string>
|
||||||
|
<string name="group_member_status_unknown_short">unknown</string>
|
||||||
|
|
||||||
|
<string name="previous_member_vName"><![CDATA[<i>Previous member</i> %1$s]]></string>
|
||||||
|
|
||||||
<!-- AddGroupMembersView.kt -->
|
<!-- AddGroupMembersView.kt -->
|
||||||
<string name="no_contacts_to_add">No contacts to add</string>
|
<string name="no_contacts_to_add">No contacts to add</string>
|
||||||
|
@ -5100,16 +5100,24 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
|||||||
_ -> pure conn'
|
_ -> pure conn'
|
||||||
|
|
||||||
xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> m ()
|
xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> m ()
|
||||||
xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ memberProfile) msg brokerTs = do
|
xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do
|
||||||
checkHostRole m memRole
|
checkHostRole m memRole
|
||||||
unless (sameMemberId memId $ membership gInfo) $
|
unless (sameMemberId memId $ membership gInfo) $
|
||||||
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case
|
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case
|
||||||
|
Right unknownMember@GroupMember {memberStatus = GSMemUnknown} -> do
|
||||||
|
updatedMember <- withStore $ \db -> updateUnknownMemberAnnounced db user m unknownMember memInfo
|
||||||
|
toView $ CRUnknownMemberAnnounced user gInfo m unknownMember updatedMember
|
||||||
|
memberAnnouncedToView updatedMember
|
||||||
Right _ -> messageError "x.grp.mem.new error: member already exists"
|
Right _ -> messageError "x.grp.mem.new error: member already exists"
|
||||||
Left _ -> do
|
Left _ -> do
|
||||||
newMember@GroupMember {groupMemberId} <- withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember GSMemAnnounced
|
newMember <- withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember GSMemAnnounced
|
||||||
ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent $ RGEMemberAdded groupMemberId memberProfile)
|
memberAnnouncedToView newMember
|
||||||
groupMsgToView gInfo ci
|
where
|
||||||
toView $ CRJoinedGroupMemberConnecting user gInfo m newMember
|
memberAnnouncedToView announcedMember@GroupMember {groupMemberId, memberProfile} = do
|
||||||
|
let event = RGEMemberAdded groupMemberId (fromLocalProfile memberProfile)
|
||||||
|
ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent event)
|
||||||
|
groupMsgToView gInfo ci
|
||||||
|
toView $ CRJoinedGroupMemberConnecting user gInfo m announcedMember
|
||||||
|
|
||||||
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> m ()
|
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> m ()
|
||||||
xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) = do
|
xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) = do
|
||||||
@ -5355,8 +5363,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
|||||||
xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> m ()
|
xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> m ()
|
||||||
xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId msg msgTs = do
|
xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId msg msgTs = do
|
||||||
when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole localDisplayName)
|
when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole localDisplayName)
|
||||||
author <- withStore $ \db -> getGroupMemberByMemberId db user gInfo memberId
|
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memberId) >>= \case
|
||||||
processForwardedMsg author msg
|
Right author -> processForwardedMsg author msg
|
||||||
|
Left (SEGroupMemberNotFoundByMemberId _) -> do
|
||||||
|
let name = T.take 7 . safeDecodeUtf8 . B64.encode . unMemberId $ memberId
|
||||||
|
unknownAuthor <- withStore $ \db -> createNewUnknownGroupMember db vr user gInfo memberId name
|
||||||
|
toView $ CRUnknownMemberCreated user gInfo m unknownAuthor
|
||||||
|
processForwardedMsg unknownAuthor msg
|
||||||
|
Left e -> throwError $ ChatErrorStore e
|
||||||
where
|
where
|
||||||
-- Note: forwarded group events (see forwardedGroupMsg) should include msgId to be deduplicated
|
-- Note: forwarded group events (see forwardedGroupMsg) should include msgId to be deduplicated
|
||||||
processForwardedMsg :: GroupMember -> ChatMessage 'Json -> m ()
|
processForwardedMsg :: GroupMember -> ChatMessage 'Json -> m ()
|
||||||
|
@ -630,6 +630,8 @@ data ChatResponse
|
|||||||
| CRDeletedMember {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember}
|
| CRDeletedMember {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, deletedMember :: GroupMember}
|
||||||
| CRDeletedMemberUser {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
| CRDeletedMemberUser {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||||
| CRLeftMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
| CRLeftMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||||
|
| CRUnknownMemberCreated {user :: User, groupInfo :: GroupInfo, forwardedByMember :: GroupMember, member :: GroupMember}
|
||||||
|
| CRUnknownMemberAnnounced {user :: User, groupInfo :: GroupInfo, announcingMember :: GroupMember, unknownMember :: GroupMember, announcedMember :: GroupMember}
|
||||||
| CRGroupEmpty {user :: User, groupInfo :: GroupInfo}
|
| CRGroupEmpty {user :: User, groupInfo :: GroupInfo}
|
||||||
| CRGroupRemoved {user :: User, groupInfo :: GroupInfo}
|
| CRGroupRemoved {user :: User, groupInfo :: GroupInfo}
|
||||||
| CRGroupDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
| CRGroupDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
|
||||||
|
@ -110,6 +110,8 @@ module Simplex.Chat.Store.Groups
|
|||||||
updateMemberProfile,
|
updateMemberProfile,
|
||||||
getXGrpLinkMemReceived,
|
getXGrpLinkMemReceived,
|
||||||
setXGrpLinkMemReceived,
|
setXGrpLinkMemReceived,
|
||||||
|
createNewUnknownGroupMember,
|
||||||
|
updateUnknownMemberAnnounced,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -594,11 +596,9 @@ getGroupSummary db User {userId} groupId = do
|
|||||||
JOIN group_members m USING (group_id)
|
JOIN group_members m USING (group_id)
|
||||||
WHERE g.user_id = ?
|
WHERE g.user_id = ?
|
||||||
AND g.group_id = ?
|
AND g.group_id = ?
|
||||||
AND m.member_status != ?
|
AND m.member_status NOT IN (?,?,?,?)
|
||||||
AND m.member_status != ?
|
|
||||||
AND m.member_status != ?
|
|
||||||
|]
|
|]
|
||||||
(userId, groupId, GSMemRemoved, GSMemLeft, GSMemInvited)
|
(userId, groupId, GSMemRemoved, GSMemLeft, GSMemUnknown, GSMemInvited)
|
||||||
pure GroupSummary {currentMembers = fromMaybe 0 currentMembers_}
|
pure GroupSummary {currentMembers = fromMaybe 0 currentMembers_}
|
||||||
|
|
||||||
getContactGroupPreferences :: DB.Connection -> User -> Contact -> IO [FullGroupPreferences]
|
getContactGroupPreferences :: DB.Connection -> User -> Contact -> IO [FullGroupPreferences]
|
||||||
@ -682,13 +682,13 @@ getGroupMembersForExpiration db user@User {userId, userContactId} GroupInfo {gro
|
|||||||
( groupMemberQuery
|
( groupMemberQuery
|
||||||
<> [sql|
|
<> [sql|
|
||||||
WHERE m.group_id = ? AND m.user_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)
|
WHERE m.group_id = ? AND m.user_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)
|
||||||
AND m.member_status IN (?, ?, ?)
|
AND m.member_status IN (?, ?, ?, ?)
|
||||||
AND m.group_member_id NOT IN (
|
AND m.group_member_id NOT IN (
|
||||||
SELECT DISTINCT group_member_id FROM chat_items
|
SELECT DISTINCT group_member_id FROM chat_items
|
||||||
)
|
)
|
||||||
|]
|
|]
|
||||||
)
|
)
|
||||||
(userId, groupId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted)
|
(userId, groupId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown)
|
||||||
|
|
||||||
toContactMember :: User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember
|
toContactMember :: User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember
|
||||||
toContactMember User {userContactId} (memberRow :. connRow) =
|
toContactMember User {userContactId} (memberRow :. connRow) =
|
||||||
@ -1339,10 +1339,10 @@ getGroupInfoByGroupLinkHash db vr user@User {userId, userContactId} (groupLinkHa
|
|||||||
FROM groups g
|
FROM groups g
|
||||||
JOIN group_members mu ON mu.group_id = g.group_id
|
JOIN group_members mu ON mu.group_id = g.group_id
|
||||||
WHERE g.user_id = ? AND g.via_group_link_uri_hash IN (?,?)
|
WHERE g.user_id = ? AND g.via_group_link_uri_hash IN (?,?)
|
||||||
AND mu.contact_id = ? AND mu.member_status NOT IN (?,?,?)
|
AND mu.contact_id = ? AND mu.member_status NOT IN (?,?,?,?)
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
|]
|
|]
|
||||||
(userId, groupLinkHash1, groupLinkHash2, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted)
|
(userId, groupLinkHash1, groupLinkHash2, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown)
|
||||||
maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_
|
maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_
|
||||||
|
|
||||||
getGroupIdByName :: DB.Connection -> User -> GroupName -> ExceptT StoreError IO GroupId
|
getGroupIdByName :: DB.Connection -> User -> GroupName -> ExceptT StoreError IO GroupId
|
||||||
@ -1965,3 +1965,52 @@ setXGrpLinkMemReceived db mId xGrpLinkMemReceived = do
|
|||||||
db
|
db
|
||||||
"UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?"
|
"UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?"
|
||||||
(xGrpLinkMemReceived, currentTs, mId)
|
(xGrpLinkMemReceived, currentTs, mId)
|
||||||
|
|
||||||
|
createNewUnknownGroupMember :: DB.Connection -> VersionRange -> User -> GroupInfo -> MemberId -> Text -> ExceptT StoreError IO GroupMember
|
||||||
|
createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName = do
|
||||||
|
currentTs <- liftIO getCurrentTime
|
||||||
|
let memberProfile = profileFromName memberName
|
||||||
|
(localDisplayName, profileId) <- createNewMemberProfile_ db user memberProfile currentTs
|
||||||
|
groupMemberId <- liftIO $ do
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
INSERT INTO group_members
|
||||||
|
( group_id, member_id, member_role, member_category, member_status, invited_by,
|
||||||
|
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
|
||||||
|
peer_chat_min_version, peer_chat_max_version)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
|
|]
|
||||||
|
( (groupId, memberId, GRAuthor, GCPreMember, GSMemUnknown, fromInvitedBy userContactId IBUnknown)
|
||||||
|
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
|
||||||
|
:. (minV, maxV)
|
||||||
|
)
|
||||||
|
insertedRowId db
|
||||||
|
getGroupMemberById db user groupMemberId
|
||||||
|
where
|
||||||
|
VersionRange minV maxV = vr
|
||||||
|
|
||||||
|
updateUnknownMemberAnnounced :: DB.Connection -> User -> GroupMember -> GroupMember -> MemberInfo -> ExceptT StoreError IO GroupMember
|
||||||
|
updateUnknownMemberAnnounced db user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} = do
|
||||||
|
_ <- updateMemberProfile db user unknownMember profile
|
||||||
|
currentTs <- liftIO getCurrentTime
|
||||||
|
liftIO $
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
UPDATE group_members
|
||||||
|
SET member_role = ?,
|
||||||
|
member_category = ?,
|
||||||
|
member_status = ?,
|
||||||
|
invited_by_group_member_id = ?,
|
||||||
|
peer_chat_min_version = ?,
|
||||||
|
peer_chat_max_version = ?,
|
||||||
|
updated_at = ?
|
||||||
|
WHERE user_id = ? AND group_member_id = ?
|
||||||
|
|]
|
||||||
|
( (memberRole, GCPostMember, GSMemAnnounced, groupMemberId' invitingMember)
|
||||||
|
:. (minV, maxV, currentTs, userId, groupMemberId)
|
||||||
|
)
|
||||||
|
getGroupMemberById db user groupMemberId
|
||||||
|
where
|
||||||
|
VersionRange minV maxV = maybe (fromJVersionRange memberChatVRange) fromChatVRange v
|
||||||
|
@ -825,6 +825,7 @@ data GroupMemberStatus
|
|||||||
= GSMemRemoved -- member who was removed from the group
|
= GSMemRemoved -- member who was removed from the group
|
||||||
| GSMemLeft -- member who left the group
|
| GSMemLeft -- member who left the group
|
||||||
| GSMemGroupDeleted -- user member of the deleted group
|
| GSMemGroupDeleted -- user member of the deleted group
|
||||||
|
| GSMemUnknown -- unknown member, whose message was forwarded by an admin (likely member wasn't introduced due to not being a current member, but message was included in history)
|
||||||
| GSMemInvited -- member is sent to or received invitation to join the group
|
| GSMemInvited -- member is sent to or received invitation to join the group
|
||||||
| GSMemIntroduced -- user received x.grp.mem.intro for this member (only with GCPreMember)
|
| GSMemIntroduced -- user received x.grp.mem.intro for this member (only with GCPreMember)
|
||||||
| GSMemIntroInvited -- member is sent to or received from intro invitation
|
| GSMemIntroInvited -- member is sent to or received from intro invitation
|
||||||
@ -851,6 +852,7 @@ memberActive m = case memberStatus m of
|
|||||||
GSMemRemoved -> False
|
GSMemRemoved -> False
|
||||||
GSMemLeft -> False
|
GSMemLeft -> False
|
||||||
GSMemGroupDeleted -> False
|
GSMemGroupDeleted -> False
|
||||||
|
GSMemUnknown -> False
|
||||||
GSMemInvited -> False
|
GSMemInvited -> False
|
||||||
GSMemIntroduced -> False
|
GSMemIntroduced -> False
|
||||||
GSMemIntroInvited -> False
|
GSMemIntroInvited -> False
|
||||||
@ -869,6 +871,7 @@ memberCurrent' = \case
|
|||||||
GSMemRemoved -> False
|
GSMemRemoved -> False
|
||||||
GSMemLeft -> False
|
GSMemLeft -> False
|
||||||
GSMemGroupDeleted -> False
|
GSMemGroupDeleted -> False
|
||||||
|
GSMemUnknown -> False
|
||||||
GSMemInvited -> False
|
GSMemInvited -> False
|
||||||
GSMemIntroduced -> True
|
GSMemIntroduced -> True
|
||||||
GSMemIntroInvited -> True
|
GSMemIntroInvited -> True
|
||||||
@ -883,6 +886,7 @@ memberRemoved m = case memberStatus m of
|
|||||||
GSMemRemoved -> True
|
GSMemRemoved -> True
|
||||||
GSMemLeft -> True
|
GSMemLeft -> True
|
||||||
GSMemGroupDeleted -> True
|
GSMemGroupDeleted -> True
|
||||||
|
GSMemUnknown -> False
|
||||||
GSMemInvited -> False
|
GSMemInvited -> False
|
||||||
GSMemIntroduced -> False
|
GSMemIntroduced -> False
|
||||||
GSMemIntroInvited -> False
|
GSMemIntroInvited -> False
|
||||||
@ -897,6 +901,7 @@ instance TextEncoding GroupMemberStatus where
|
|||||||
"removed" -> Just GSMemRemoved
|
"removed" -> Just GSMemRemoved
|
||||||
"left" -> Just GSMemLeft
|
"left" -> Just GSMemLeft
|
||||||
"deleted" -> Just GSMemGroupDeleted
|
"deleted" -> Just GSMemGroupDeleted
|
||||||
|
"unknown" -> Just GSMemUnknown
|
||||||
"invited" -> Just GSMemInvited
|
"invited" -> Just GSMemInvited
|
||||||
"introduced" -> Just GSMemIntroduced
|
"introduced" -> Just GSMemIntroduced
|
||||||
"intro-inv" -> Just GSMemIntroInvited
|
"intro-inv" -> Just GSMemIntroInvited
|
||||||
@ -910,6 +915,7 @@ instance TextEncoding GroupMemberStatus where
|
|||||||
GSMemRemoved -> "removed"
|
GSMemRemoved -> "removed"
|
||||||
GSMemLeft -> "left"
|
GSMemLeft -> "left"
|
||||||
GSMemGroupDeleted -> "deleted"
|
GSMemGroupDeleted -> "deleted"
|
||||||
|
GSMemUnknown -> "unknown"
|
||||||
GSMemInvited -> "invited"
|
GSMemInvited -> "invited"
|
||||||
GSMemIntroduced -> "introduced"
|
GSMemIntroduced -> "introduced"
|
||||||
GSMemIntroInvited -> "intro-inv"
|
GSMemIntroInvited -> "intro-inv"
|
||||||
|
@ -178,6 +178,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
|
|||||||
CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
|
CRGroupLinkConnecting u g _ -> ttyUser u [ttyGroup' g <> ": joining the group..."]
|
||||||
CRUserDeletedMember u g m -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group"]
|
CRUserDeletedMember u g m -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group"]
|
||||||
CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g
|
CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g
|
||||||
|
CRUnknownMemberCreated u g fwdM um -> ttyUser u [ttyGroup' g <> ": " <> ttyMember fwdM <> " forwarded a message from an unknown member, creating unknown member record " <> ttyMember um]
|
||||||
|
CRUnknownMemberAnnounced u g _ um m -> ttyUser u [ttyGroup' g <> ": unknown member " <> ttyMember um <> " updated to " <> ttyMember m]
|
||||||
CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"]
|
CRGroupDeletedUser u g -> ttyUser u [ttyGroup' g <> ": you deleted the group"]
|
||||||
CRRcvFileDescrReady _ _ -> []
|
CRRcvFileDescrReady _ _ -> []
|
||||||
CRRcvFileDescrNotReady _ _ -> []
|
CRRcvFileDescrNotReady _ _ -> []
|
||||||
@ -963,6 +965,7 @@ viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filt
|
|||||||
status m = case memberStatus m of
|
status m = case memberStatus m of
|
||||||
GSMemRemoved -> ["removed"]
|
GSMemRemoved -> ["removed"]
|
||||||
GSMemLeft -> ["left"]
|
GSMemLeft -> ["left"]
|
||||||
|
GSMemUnknown -> ["status unknown"]
|
||||||
GSMemInvited -> ["not yet joined"]
|
GSMemInvited -> ["not yet joined"]
|
||||||
GSMemConnected -> ["connected"]
|
GSMemConnected -> ["connected"]
|
||||||
GSMemComplete -> ["connected"]
|
GSMemComplete -> ["connected"]
|
||||||
|
@ -132,6 +132,7 @@ chatGroupTests = do
|
|||||||
it "deleted message is not included" testGroupHistoryDeletedMessage
|
it "deleted message is not included" testGroupHistoryDeletedMessage
|
||||||
it "disappearing message is sent as disappearing" testGroupHistoryDisappearingMessage
|
it "disappearing message is sent as disappearing" testGroupHistoryDisappearingMessage
|
||||||
it "welcome message (group description) is sent after history" testGroupHistoryWelcomeMessage
|
it "welcome message (group description) is sent after history" testGroupHistoryWelcomeMessage
|
||||||
|
it "unknown member messages are processed" testGroupHistoryUnknownMember
|
||||||
where
|
where
|
||||||
_0 = supportedChatVRange -- don't create direct connections
|
_0 = supportedChatVRange -- don't create direct connections
|
||||||
_1 = groupCreateDirectVRange
|
_1 = groupCreateDirectVRange
|
||||||
@ -5179,3 +5180,71 @@ testGroupHistoryWelcomeMessage =
|
|||||||
[alice, cath] *<# "#team bob> 2"
|
[alice, cath] *<# "#team bob> 2"
|
||||||
cath #> "#team 3"
|
cath #> "#team 3"
|
||||||
[alice, bob] *<# "#team cath> 3"
|
[alice, bob] *<# "#team cath> 3"
|
||||||
|
|
||||||
|
testGroupHistoryUnknownMember :: HasCallStack => FilePath -> IO ()
|
||||||
|
testGroupHistoryUnknownMember =
|
||||||
|
testChat4 aliceProfile bobProfile cathProfile danProfile $
|
||||||
|
\alice bob cath dan -> do
|
||||||
|
createGroup3 "team" alice bob cath
|
||||||
|
|
||||||
|
threadDelay 1000000
|
||||||
|
|
||||||
|
alice #> "#team hi from alice"
|
||||||
|
[bob, cath] *<# "#team alice> hi from alice"
|
||||||
|
|
||||||
|
threadDelay 1000000
|
||||||
|
|
||||||
|
bob #> "#team hi from bob"
|
||||||
|
[alice, cath] *<# "#team bob> hi from bob"
|
||||||
|
|
||||||
|
threadDelay 1000000
|
||||||
|
|
||||||
|
cath #> "#team hi from cath"
|
||||||
|
[alice, bob] *<# "#team cath> hi from cath"
|
||||||
|
|
||||||
|
bob ##> "/l team"
|
||||||
|
concurrentlyN_
|
||||||
|
[ do
|
||||||
|
bob <## "#team: you left the group"
|
||||||
|
bob <## "use /d #team to delete the group",
|
||||||
|
alice <## "#team: bob left the group",
|
||||||
|
cath <## "#team: bob left the group"
|
||||||
|
]
|
||||||
|
|
||||||
|
connectUsers alice dan
|
||||||
|
addMember "team" alice dan GRAdmin
|
||||||
|
dan ##> "/j team"
|
||||||
|
concurrentlyN_
|
||||||
|
[ alice <## "#team: dan joined the group",
|
||||||
|
dan
|
||||||
|
<### [ "#team: you joined the group",
|
||||||
|
WithTime "#team alice> hi from alice [>>]",
|
||||||
|
StartsWith "#team: alice forwarded a message from an unknown member, creating unknown member record",
|
||||||
|
EndsWith "hi from bob [>>]",
|
||||||
|
WithTime "#team cath> hi from cath [>>]",
|
||||||
|
"#team: member cath (Catherine) is connected"
|
||||||
|
],
|
||||||
|
do
|
||||||
|
cath <## "#team: alice added dan (Daniel) to the group (connecting...)"
|
||||||
|
cath <## "#team: new member dan is connected"
|
||||||
|
]
|
||||||
|
|
||||||
|
dan ##> "/_get chat #1 count=100"
|
||||||
|
r <- chat <$> getTermLine dan
|
||||||
|
r `shouldContain` [(0, "hi from alice"), (0, "hi from bob"), (0, "hi from cath")]
|
||||||
|
|
||||||
|
dan ##> "/ms team"
|
||||||
|
dan
|
||||||
|
<### [ "dan (Daniel): admin, you, connected",
|
||||||
|
"alice (Alice): owner, host, connected",
|
||||||
|
"cath (Catherine): admin, connected",
|
||||||
|
EndsWith "author, status unknown"
|
||||||
|
]
|
||||||
|
|
||||||
|
-- message delivery works after sending history
|
||||||
|
alice #> "#team 1"
|
||||||
|
[cath, dan] *<# "#team alice> 1"
|
||||||
|
cath #> "#team 2"
|
||||||
|
[alice, dan] *<# "#team cath> 2"
|
||||||
|
dan #> "#team 3"
|
||||||
|
[alice, cath] *<# "#team dan> 3"
|
||||||
|
Loading…
Reference in New Issue
Block a user