mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-31 13:15:42 +00:00
Merge branch 'master' into f/channel-comments
This commit is contained in:
@@ -33,6 +33,7 @@ data AppSettings = AppSettings
|
||||
privacyAskToApproveRelays :: Maybe Bool,
|
||||
privacyAcceptImages :: Maybe Bool,
|
||||
privacyLinkPreviews :: Maybe Bool,
|
||||
privacySanitizeLinks :: Maybe Bool,
|
||||
privacyShowChatPreviews :: Maybe Bool,
|
||||
privacySaveLastDraft :: Maybe Bool,
|
||||
privacyProtectScreen :: Maybe Bool,
|
||||
@@ -83,6 +84,7 @@ defaultAppSettings =
|
||||
privacyAskToApproveRelays = Just True,
|
||||
privacyAcceptImages = Just True,
|
||||
privacyLinkPreviews = Just True,
|
||||
privacySanitizeLinks = Just False,
|
||||
privacyShowChatPreviews = Just True,
|
||||
privacySaveLastDraft = Just True,
|
||||
privacyProtectScreen = Just False,
|
||||
@@ -120,6 +122,7 @@ defaultParseAppSettings =
|
||||
privacyAskToApproveRelays = Nothing,
|
||||
privacyAcceptImages = Nothing,
|
||||
privacyLinkPreviews = Nothing,
|
||||
privacySanitizeLinks = Nothing,
|
||||
privacyShowChatPreviews = Nothing,
|
||||
privacySaveLastDraft = Nothing,
|
||||
privacyProtectScreen = Nothing,
|
||||
@@ -157,6 +160,7 @@ combineAppSettings platformDefaults storedSettings =
|
||||
privacyAskToApproveRelays = p privacyAskToApproveRelays,
|
||||
privacyAcceptImages = p privacyAcceptImages,
|
||||
privacyLinkPreviews = p privacyLinkPreviews,
|
||||
privacySanitizeLinks = p privacySanitizeLinks,
|
||||
privacyShowChatPreviews = p privacyShowChatPreviews,
|
||||
privacySaveLastDraft = p privacySaveLastDraft,
|
||||
privacyProtectScreen = p privacyProtectScreen,
|
||||
@@ -210,6 +214,7 @@ instance FromJSON AppSettings where
|
||||
privacyAskToApproveRelays <- p "privacyAskToApproveRelays"
|
||||
privacyAcceptImages <- p "privacyAcceptImages"
|
||||
privacyLinkPreviews <- p "privacyLinkPreviews"
|
||||
privacySanitizeLinks <- p "privacySanitizeLinks"
|
||||
privacyShowChatPreviews <- p "privacyShowChatPreviews"
|
||||
privacySaveLastDraft <- p "privacySaveLastDraft"
|
||||
privacyProtectScreen <- p "privacyProtectScreen"
|
||||
@@ -244,6 +249,7 @@ instance FromJSON AppSettings where
|
||||
privacyAskToApproveRelays,
|
||||
privacyAcceptImages,
|
||||
privacyLinkPreviews,
|
||||
privacySanitizeLinks,
|
||||
privacyShowChatPreviews,
|
||||
privacySaveLastDraft,
|
||||
privacyProtectScreen,
|
||||
|
||||
@@ -409,6 +409,7 @@ data ChatCommand
|
||||
| SetUserChatRelays [CLINewRelay]
|
||||
| APITestChatRelay UserId ShortLinkContact
|
||||
| TestChatRelay ShortLinkContact
|
||||
| APIAllowRelayGroup {groupId :: GroupId}
|
||||
| APIGetServerOperators
|
||||
| APISetServerOperators (NonEmpty ServerOperator)
|
||||
| SetServerOperators (NonEmpty ServerOperatorRoles)
|
||||
@@ -534,6 +535,7 @@ data ChatCommand
|
||||
| BlockForAll GroupName ContactName Bool
|
||||
| RemoveMembers {groupName :: GroupName, members :: NonEmpty ContactName, withMessages :: Bool}
|
||||
| LeaveGroup GroupName
|
||||
| AllowRelayGroup GroupName
|
||||
| DeleteGroup GroupName
|
||||
| ClearGroup GroupName
|
||||
| ListMembers GroupName
|
||||
@@ -737,6 +739,7 @@ data ChatResponse
|
||||
| CRPublicGroupCreated {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink, groupRelays :: [GroupRelay]}
|
||||
| CRPublicGroupCreationFailed {user :: User, addRelayResults :: [AddRelayResult]}
|
||||
| CRGroupRelays {user :: User, groupInfo :: GroupInfo, groupRelays :: [GroupRelay]}
|
||||
| CRRelayGroupAllowed {user :: User, groupInfo :: GroupInfo}
|
||||
| CRGroupRelaysAdded {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink, groupRelays :: [GroupRelay]}
|
||||
| CRGroupRelaysAddFailed {user :: User, addRelayResults :: [AddRelayResult]}
|
||||
| CRGroupMembers {user :: User, group :: Group}
|
||||
@@ -947,6 +950,7 @@ data ChatEvent
|
||||
|
||||
data TerminalEvent
|
||||
= TEGroupLinkRejected {user :: User, groupInfo :: GroupInfo, groupRejectionReason :: GroupRejectionReason}
|
||||
| TERelayRejected {user :: User, groupInfo :: GroupInfo, relayRejectionReason :: RelayRejectionReason}
|
||||
| TERejectingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, groupRejectionReason :: GroupRejectionReason}
|
||||
| TENewMemberContact {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| TEContactVerificationReset {user :: User, contact :: Contact}
|
||||
|
||||
@@ -781,11 +781,12 @@ processChatCommand vr nm = \case
|
||||
deletions <- case mode of
|
||||
CIDMInternal -> deleteDirectCIs user ct items
|
||||
CIDMInternalMark -> markDirectCIsDeleted user ct items =<< liftIO getCurrentTime
|
||||
CIDMHistory -> throwChatError CEInvalidChatItemDelete
|
||||
CIDMBroadcast -> do
|
||||
assertDeletable items
|
||||
assertDirectAllowed user MDSnd ct XMsgDel_
|
||||
let msgIds = itemsMsgIds items
|
||||
events = map (\msgId -> XMsgDel msgId Nothing Nothing) msgIds
|
||||
events = map (\msgId -> XMsgDel msgId Nothing Nothing False) msgIds
|
||||
forM_ (L.nonEmpty events) $ \events' ->
|
||||
sendDirectContactMessages user ct events'
|
||||
if featureAllowed SCFFullDelete forUser ct
|
||||
@@ -797,8 +798,9 @@ processChatCommand vr nm = \case
|
||||
-- TODO [knocking] check scope for all items?
|
||||
chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
|
||||
deletions <- case mode of
|
||||
CIDMInternal -> do
|
||||
deleteGroupCIs user gInfo chatScopeInfo items Nothing =<< liftIO getCurrentTime
|
||||
CIDMInternal
|
||||
| publicGroupEditor gInfo (membership gInfo) -> throwChatError CEInvalidChatItemDelete
|
||||
| otherwise -> deleteGroupCIs user gInfo chatScopeInfo items Nothing =<< liftIO getCurrentTime
|
||||
CIDMInternalMark -> do
|
||||
markGroupCIsDeleted user gInfo chatScopeInfo items Nothing =<< liftIO getCurrentTime
|
||||
CIDMBroadcast -> do
|
||||
@@ -806,9 +808,15 @@ processChatCommand vr nm = \case
|
||||
assertDeletable items
|
||||
assertUserGroupRole gInfo GRObserver -- can still delete messages sent earlier
|
||||
let msgIds = itemsMsgIds items
|
||||
events = L.nonEmpty $ map (\msgId -> XMsgDel msgId Nothing $ toMsgScope gInfo <$> chatScopeInfo) msgIds
|
||||
events = L.nonEmpty $ map (\msgId -> XMsgDel msgId Nothing (toMsgScope gInfo <$> chatScopeInfo) False) msgIds
|
||||
mapM_ (sendGroupMessages user gInfo Nothing False recipients) events
|
||||
delGroupChatItems user gInfo chatScopeInfo items False
|
||||
CIDMHistory -> do
|
||||
unless (publicGroupEditor gInfo (membership gInfo)) $ throwChatError CEInvalidChatItemDelete
|
||||
recipients <- getGroupRecipients vr user gInfo chatScopeInfo groupKnockingVersion
|
||||
let msgIds = itemsMsgIds items
|
||||
events = L.nonEmpty $ map (\msgId -> XMsgDel msgId Nothing (toMsgScope gInfo <$> chatScopeInfo) True) msgIds
|
||||
mapM_ (sendGroupMessages user gInfo Nothing False recipients) events
|
||||
-- TODO delGroupChatItems sends deletion events too. Are they needed?
|
||||
delGroupChatItems user gInfo chatScopeInfo items False
|
||||
pure $ CRChatItemsDeleted user deletions True False
|
||||
CTLocal -> do
|
||||
@@ -850,6 +858,7 @@ processChatCommand vr nm = \case
|
||||
deletions <- case mode of
|
||||
CIDMInternal -> deleteGroupCIs user gInfo Nothing items Nothing =<< liftIO getCurrentTime
|
||||
CIDMInternalMark -> markGroupCIsDeleted user gInfo Nothing items Nothing =<< liftIO getCurrentTime
|
||||
CIDMHistory -> throwChatError CEInvalidChatItemDelete
|
||||
CIDMBroadcast -> do
|
||||
ms <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
|
||||
let recipients = filter memberCurrent ms
|
||||
@@ -1611,6 +1620,9 @@ processChatCommand vr nm = \case
|
||||
Right (Just (Just failure)) -> pure $ CRChatRelayTestResult user (Just relayProfile) (Just failure)
|
||||
TestChatRelay address -> withUser $ \User {userId} ->
|
||||
processChatCommand vr nm $ APITestChatRelay userId address
|
||||
APIAllowRelayGroup groupId -> withUser $ \user -> do
|
||||
gInfo' <- withStore $ \db -> allowRelayGroup db vr user groupId
|
||||
pure $ CRRelayGroupAllowed user gInfo'
|
||||
GetUserChatRelays -> withUser $ \user -> do
|
||||
srvs <- withFastStore (`getUserServers` user)
|
||||
liftIO $ CRUserServers user <$> groupByOperator (onlyRelays srvs)
|
||||
@@ -2972,9 +2984,13 @@ processChatCommand vr nm = \case
|
||||
toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo' scopeInfo) ci]
|
||||
-- TODO delete direct connections that were unused
|
||||
deleteGroupLinkIfExists user gInfo'
|
||||
let relayRejected = useRelays' gInfo && isRelay membership
|
||||
-- member records are not deleted to keep history
|
||||
withFastStore' $ \db -> updateGroupMemberStatus db userId membership GSMemLeft
|
||||
pure $ CRLeftMemberUser user gInfo' {membership = membership {memberStatus = GSMemLeft}}
|
||||
withFastStore' $ \db -> do
|
||||
updateGroupMemberStatus db userId membership GSMemLeft
|
||||
when relayRejected $ updateRelayOwnStatus_ db gInfo RSRejected
|
||||
let relayOwnStatus' = if relayRejected then Just RSRejected else relayOwnStatus gInfo
|
||||
pure $ CRLeftMemberUser user gInfo' {membership = membership {memberStatus = GSMemLeft}, relayOwnStatus = relayOwnStatus'}
|
||||
where
|
||||
-- Relay leaving channel: create delivery job for cursor-based sending and async connection cleanup.
|
||||
leaveChannelRelay gInfo = do
|
||||
@@ -3026,6 +3042,9 @@ processChatCommand vr nm = \case
|
||||
LeaveGroup gName -> withUser $ \user -> do
|
||||
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
|
||||
processChatCommand vr nm $ APILeaveGroup groupId
|
||||
AllowRelayGroup gName -> withUser $ \user -> do
|
||||
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
|
||||
processChatCommand vr nm $ APIAllowRelayGroup groupId
|
||||
DeleteGroup gName -> withUser $ \user -> do
|
||||
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
|
||||
processChatCommand vr nm $ APIDeleteChat (ChatRef CTGroup groupId Nothing) (CDMFull True)
|
||||
@@ -3856,7 +3875,7 @@ processChatCommand vr nm = \case
|
||||
assertDeletable gInfo items
|
||||
assertUserGroupRole gInfo GRModerator
|
||||
let msgMemIds = itemsMsgMemIds gInfo items
|
||||
events = L.nonEmpty $ map (\(msgId, memId) -> XMsgDel msgId memId $ toMsgScope gInfo <$> chatScopeInfo) msgMemIds
|
||||
events = L.nonEmpty $ map (\(msgId, memId) -> XMsgDel msgId memId (toMsgScope gInfo <$> chatScopeInfo) False) msgMemIds
|
||||
mapM_ (sendGroupMessages_ user gInfo ms) events
|
||||
delGroupChatItems user gInfo chatScopeInfo items True
|
||||
where
|
||||
@@ -5090,6 +5109,8 @@ chatCommandP =
|
||||
"/xftp" $> GetUserProtoServers (AProtocolType SPXFTP),
|
||||
"/_relay test " *> (APITestChatRelay <$> A.decimal <* A.space <*> strP),
|
||||
"/relay test " *> (TestChatRelay <$> strP),
|
||||
"/_relay allow #" *> (APIAllowRelayGroup <$> A.decimal),
|
||||
"/group allow #" *> (AllowRelayGroup <$> displayNameP),
|
||||
"/relays " *> (SetUserChatRelays <$> chatRelaysP),
|
||||
"/relays" $> GetUserChatRelays,
|
||||
"/_operators" $> APIGetServerOperators,
|
||||
|
||||
@@ -1079,6 +1079,28 @@ acceptRelayJoinRequestAsync
|
||||
ownerMember' <- getGroupMemberById db vr user groupMemberId
|
||||
pure (gInfo', ownerMember')
|
||||
|
||||
rejectRelayInvitationAsync
|
||||
:: User
|
||||
-> Int64
|
||||
-> VersionRangeChat
|
||||
-> GroupRelayInvitation
|
||||
-> InvitationId
|
||||
-> VersionRangeChat
|
||||
-> Int64
|
||||
-> RelayRejectionReason
|
||||
-> CM ()
|
||||
rejectRelayInvitationAsync user uclId vr groupRelayInv invId reqChatVRange initialDelay reason = do
|
||||
(_gInfo, ownerMember) <- withStore $ \db ->
|
||||
createRelayRequestGroup db vr user groupRelayInv invId reqChatVRange initialDelay GSMemInvited RSRejected
|
||||
let GroupMember {groupMemberId} = ownerMember
|
||||
msg = XGrpRelayReject reason
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
chatVR <- chatVersionRange
|
||||
let chatV = chatVR `peerConnChatVersion` reqChatVRange
|
||||
connIds <- agentAcceptContactAsync user False invId msg subMode PQSupportOff chatV
|
||||
withStore' $ \db ->
|
||||
createJoiningMemberConnection db user uclId connIds chatV reqChatVRange groupMemberId subMode
|
||||
|
||||
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
|
||||
businessGroupProfile Profile {displayName, fullName, shortDescr, image} groupPreferences =
|
||||
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, publicGroup = Nothing, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
|
||||
|
||||
@@ -511,7 +511,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
XMsgNew mc -> newContentMessage ct'' mc msg msgMeta
|
||||
XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct'' sharedMsgId fileDescr
|
||||
XMsgUpdate sharedMsgId mContent _ ttl live _msgScope _ _ -> messageUpdate ct'' sharedMsgId mContent msg msgMeta ttl live
|
||||
XMsgDel sharedMsgId _ _ -> messageDelete ct'' sharedMsgId msg msgMeta
|
||||
XMsgDel sharedMsgId _ _ _ -> messageDelete ct'' sharedMsgId msg msgMeta
|
||||
XMsgReact sharedMsgId _ _ reaction add -> directMsgReaction ct'' sharedMsgId reaction add msg msgMeta
|
||||
-- TODO discontinue XFile
|
||||
XFile fInv -> processFileInvitation' ct'' fInv msg msgMeta
|
||||
@@ -770,6 +770,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
withStore' $ \db -> setRelayLinkConfId db m confId relayLink
|
||||
void $ getAgentConnShortLinkAsync user CFGetRelayDataAccept (Just conn') relayLink
|
||||
| otherwise -> messageError "x.grp.relay.acpt: only owner can add relay"
|
||||
XGrpRelayReject reason
|
||||
| memberRole' membership == GROwner && isRelay m -> do
|
||||
-- GSMemLeft (not GSMemRejected): owner UI treats this identically to an explicit /leave from the relay; GSMemRejected has knocking-admission semantics.
|
||||
(relay', m') <- withStore $ \db -> do
|
||||
relay <- getGroupRelayByGMId db (groupMemberId' m)
|
||||
relay' <- if relayStatus relay == RSInvited
|
||||
then liftIO $ updateRelayStatusFromTo db relay RSInvited RSRejected
|
||||
else pure relay
|
||||
liftIO $ updateGroupMemberStatus db userId m GSMemLeft
|
||||
pure (relay', m {memberStatus = GSMemLeft})
|
||||
-- complete the contact handshake so the relay receives INFO and cleans up its transient bookkeeping
|
||||
allowAgentConnectionAsync user conn' confId XOk
|
||||
toView $ CEvtGroupRelayUpdated user gInfo m' relay'
|
||||
toViewTE $ TERelayRejected user gInfo reason
|
||||
| otherwise -> messageError "x.grp.relay.reject: only owner should receive relay rejection"
|
||||
_ -> messageError "CONF from invited member must have x.grp.acpt"
|
||||
GCHostMember ->
|
||||
case chatMsgEvent of
|
||||
@@ -817,10 +832,16 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
when (memberStatus m == GSMemRejected) $ do
|
||||
deleteMemberConnection' m True
|
||||
withStore' $ \db -> deleteGroupMember db user m
|
||||
XOk -> pure ()
|
||||
XOk ->
|
||||
-- transient relay-reject row cleanup after the rejection handshake completes
|
||||
when (memberCategory m == GCHostMember && not (relayServesGroup gInfo)) $ do
|
||||
deleteMemberConnection' m True
|
||||
withStore' $ \db -> do
|
||||
deleteGroupMember db user m
|
||||
deleteGroup db user gInfo
|
||||
_ -> messageError "INFO from member must have x.grp.mem.info, x.info or x.ok"
|
||||
pure ()
|
||||
CON _pqEnc -> unless (memberStatus m == GSMemRejected || memberStatus membership == GSMemRejected) $ do
|
||||
CON _pqEnc -> unless rejected $ do
|
||||
-- TODO [knocking] send pending messages after accepting?
|
||||
-- possible improvement: check for each pending message, requires keeping track of connection state
|
||||
unless (connDisabled conn) $ sendPendingGroupMessages user gInfo m conn
|
||||
@@ -922,6 +943,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
forM_ (memberConn im) $ \imConn ->
|
||||
void $ sendDirectMemberMessage imConn (XGrpMemCon memberId) groupId
|
||||
_ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected"
|
||||
where
|
||||
rejected =
|
||||
memberStatus m `elem` ([GSMemRejected, GSMemLeft, GSMemRemoved, GSMemGroupDeleted] :: [GroupMemberStatus])
|
||||
|| memberStatus membership == GSMemRejected
|
||||
|| not (relayServesGroup gInfo)
|
||||
MSG msgMeta _msgFlags msgBody -> do
|
||||
tags <- newTVarIO []
|
||||
withAckMessage "group msg" agentConnId msgMeta True (Just tags) $ \eInfo -> do
|
||||
@@ -933,7 +959,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
if isUserGrpFwdRelay gInfo' && not (blockedByAdmin m)
|
||||
then
|
||||
let tasks
|
||||
| relayOwnStatus gInfo' == Just RSInactive = filter relayRemovedNewTask newDeliveryTasks
|
||||
| not (relayServesGroup gInfo') = filter relayRemovedNewTask newDeliveryTasks
|
||||
| otherwise = newDeliveryTasks
|
||||
in createDeliveryTasks gInfo' m' tasks
|
||||
else pure False
|
||||
@@ -1005,7 +1031,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
gate = if isComment then memberCanComment (Just m'') else memberCanSend (Just m'') msgScope
|
||||
check $ gate $
|
||||
groupMessageUpdate gInfo' (Just m'') sharedMsgId mContent mentions msgScope msg brokerTs ttl live asGroup_ prefs_
|
||||
XMsgDel sharedMsgId memberId_ scope_ -> groupMessageDelete gInfo' (Just m'') sharedMsgId memberId_ scope_ msg brokerTs
|
||||
XMsgDel sharedMsgId memberId_ scope_ onlyHistory ->
|
||||
groupMessageDelete gInfo' (Just m'') sharedMsgId memberId_ scope_ onlyHistory msg brokerTs
|
||||
XMsgReact sharedMsgId memberId scope_ reaction add -> groupMsgReaction gInfo' m'' sharedMsgId memberId scope_ reaction add msg brokerTs
|
||||
-- TODO discontinue XFile
|
||||
XFile fInv -> Nothing <$ processGroupFileInvitation' gInfo' m'' fInv msg brokerTs
|
||||
@@ -1528,10 +1555,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
mem <- acceptGroupJoinSendRejectAsync user uclId gInfo invId chatVRange p xContactId_ rjctReason
|
||||
toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason
|
||||
xGrpRelayInv :: InvitationId -> VersionRangeChat -> GroupRelayInvitation -> CM ()
|
||||
xGrpRelayInv invId chatVRange groupRelayInv = do
|
||||
xGrpRelayInv invId chatVRange groupRelayInv@GroupRelayInvitation {groupLink} = do
|
||||
rejected <- withStore' $ \db -> isRelayGroupRejected db user groupLink
|
||||
initialDelay <- asks $ initialInterval . relayRequestRetryInterval . config
|
||||
(_gInfo, _ownerMember) <- withStore $ \db -> createRelayRequestGroup db vr user groupRelayInv invId chatVRange initialDelay
|
||||
lift $ void $ getRelayRequestWorker True
|
||||
if rejected
|
||||
then rejectRelayInvitationAsync user uclId vr groupRelayInv invId chatVRange initialDelay RRRRejoinRejected
|
||||
else do
|
||||
(_gInfo, _ownerMember) <- withStore $ \db ->
|
||||
createRelayRequestGroup db vr user groupRelayInv invId chatVRange initialDelay GSMemAccepted RSInvited
|
||||
lift $ void $ getRelayRequestWorker True
|
||||
xGrpRelayTest :: InvitationId -> VersionRangeChat -> ByteString -> CM ()
|
||||
xGrpRelayTest invId chatVRange challenge = do
|
||||
privKey_ <- withAgent $ \a -> getConnLinkPrivKey a (aConnId conn)
|
||||
@@ -2242,26 +2274,30 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
pure True
|
||||
Nothing -> pure False
|
||||
|
||||
groupMessageDelete :: GroupInfo -> Maybe GroupMember -> SharedMsgId -> Maybe MemberId -> Maybe MsgScope -> RcvMessage -> UTCTime -> CM (Maybe DeliveryTaskContext)
|
||||
groupMessageDelete gInfo@GroupInfo {membership} m_ sharedMsgId sndMemberId_ scope_ rcvMsg brokerTs =
|
||||
groupMessageDelete :: GroupInfo -> Maybe GroupMember -> SharedMsgId -> Maybe MemberId -> Maybe MsgScope -> Bool -> RcvMessage -> UTCTime -> CM (Maybe DeliveryTaskContext)
|
||||
groupMessageDelete gInfo@GroupInfo {membership} m_ sharedMsgId sndMemberId_ scope_ onlyHistory rcvMsg brokerTs =
|
||||
findItem >>= \case
|
||||
Right cci@(CChatItem _ ci@ChatItem {chatDir}) -> case (chatDir, m_) of
|
||||
(CIGroupRcv mem, Just m@GroupMember {memberId}) ->
|
||||
let msgMemberId = fromMaybe memberId sndMemberId_
|
||||
isAuthor = sameMemberId memberId mem
|
||||
in case sndMemberId_ of
|
||||
-- regular deletion
|
||||
Nothing
|
||||
| sameMemberId memberId mem && (publicGroupItemDeletable mem || rcvItemDeletable ci brokerTs) ->
|
||||
| isAuthor && onlyHistory && publicGroupEditor gInfo m ->
|
||||
delete cci False Nothing $> Nothing
|
||||
| isAuthor && not onlyHistory && rcvItemDeletable ci brokerTs ->
|
||||
delete cci False Nothing
|
||||
| otherwise ->
|
||||
messageError "x.msg.del: member attempted invalid message delete" $> Nothing
|
||||
-- moderation (not limited by time)
|
||||
Just _
|
||||
| sameMemberId memberId mem && msgMemberId == memberId ->
|
||||
| isAuthor && msgMemberId == memberId ->
|
||||
delete cci False (Just m)
|
||||
| otherwise -> moderate m mem cci
|
||||
(CIChannelRcv, _)
|
||||
| isNothing sndMemberId_ && isOwner -> delete cci True Nothing
|
||||
| isNothing sndMemberId_ && isOwner ->
|
||||
(if onlyHistory then ($> Nothing) else id) $ delete cci True Nothing
|
||||
| otherwise -> messageError "x.msg.del: invalid channel message delete" $> Nothing
|
||||
(CIGroupSnd, Just m) -> moderate m membership cci
|
||||
_ -> messageError "x.msg.del: invalid message deletion" $> Nothing
|
||||
@@ -2287,7 +2323,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
messageError ("x.msg.del: channel message not found, " <> tshow e) $> Nothing
|
||||
where
|
||||
isOwner = maybe True (\m -> memberRole' m == GROwner) m_
|
||||
publicGroupItemDeletable mem = useRelays' gInfo && memberRole' mem >= GRModerator
|
||||
RcvMessage {msgId} = rcvMsg
|
||||
findItem = do
|
||||
let tryMemberLookup mId =
|
||||
@@ -3199,7 +3234,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
unless (isUserGrpFwdRelay gInfo) $ deleteGroupConnections user gInfo False
|
||||
withStore' $ \db -> do
|
||||
updateGroupMemberStatus db userId membership GSMemRemoved
|
||||
when (isJust $ relayOwnStatus gInfo) $ updateRelayOwnStatus_ db gInfo RSInactive
|
||||
when (maybe False (/= RSRejected) (relayOwnStatus gInfo)) $ updateRelayOwnStatus_ db gInfo RSInactive
|
||||
let membership' = membership {memberStatus = GSMemRemoved}
|
||||
when withMessages $ deleteMessages gInfo membership' SMDSnd
|
||||
deleteMemberItem msg gInfo RGEUserDeleted
|
||||
@@ -3457,7 +3492,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
isComment <- isCommentEdit gInfo author_ asGroup_ sharedMsgId
|
||||
let gate = if isComment then memberCanComment author_ else memberCanSend author_ msgScope
|
||||
void $ gate $ groupMessageUpdate gInfo author_ sharedMsgId mContent mentions msgScope rcvMsg msgTs ttl live asGroup_ prefs_
|
||||
XMsgDel sharedMsgId memId scope_ -> void $ groupMessageDelete gInfo author_ sharedMsgId memId scope_ rcvMsg msgTs
|
||||
XMsgDel sharedMsgId memId scope_ _ -> void $ groupMessageDelete gInfo author_ sharedMsgId memId scope_ False rcvMsg msgTs
|
||||
XMsgReact sharedMsgId memId scope_ reaction add -> withAuthor XMsgReact_ $ \author -> void $ groupMsgReaction gInfo author sharedMsgId memId scope_ reaction add rcvMsg msgTs
|
||||
XFileCancel sharedMsgId -> void $ xFileCancelGroup gInfo author_ sharedMsgId
|
||||
XInfo p -> withAuthor XInfo_ $ \author -> void $ xInfoMember gInfo author p rcvMsg msgTs
|
||||
@@ -3638,7 +3673,7 @@ runDeliveryTaskWorker a deliveryKey Worker {doWork} = do
|
||||
processDeliveryTask task@MessageDeliveryTask {jobScope} =
|
||||
case jobScopeImpliedSpec jobScope of
|
||||
DJDeliveryJob _includePending
|
||||
| relayOwnStatus gInfo == Just RSInactive -> do
|
||||
| not (relayServesGroup gInfo) -> do
|
||||
logWarn "delivery task worker: relay inactive"
|
||||
withStore' $ \db -> setDeliveryTaskErrStatus db (deliveryTaskId task) "relay inactive"
|
||||
| otherwise ->
|
||||
@@ -3708,7 +3743,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
|
||||
processDeliveryJob job =
|
||||
case jobScopeImpliedSpec jobScope of
|
||||
DJDeliveryJob _includePending
|
||||
| relayOwnStatus gInfo == Just RSInactive -> do
|
||||
| not (relayServesGroup gInfo) -> do
|
||||
logWarn "delivery job worker: relay inactive"
|
||||
withStore' $ \db -> setDeliveryJobErrStatus db (deliveryJobId job) "relay inactive"
|
||||
| otherwise -> do
|
||||
|
||||
@@ -302,6 +302,8 @@ markdownP = mconcat <$> A.many' fragmentP
|
||||
isPunctuation' = \case
|
||||
'/' -> False
|
||||
')' -> False
|
||||
'_' -> False
|
||||
'!' -> False
|
||||
c -> isPunctuation c
|
||||
isUri s = T.length s >= 10 && any (`T.isPrefixOf` s) ["http://", "https://", "simplex:/"]
|
||||
-- matches what is likely to be a domain, not all valid domain names
|
||||
|
||||
@@ -105,7 +105,7 @@ msgDirectionIntP = \case
|
||||
1 -> Just MDSnd
|
||||
_ -> Nothing
|
||||
|
||||
data CIDeleteMode = CIDMBroadcast | CIDMInternal | CIDMInternalMark
|
||||
data CIDeleteMode = CIDMBroadcast | CIDMInternal | CIDMInternalMark | CIDMHistory
|
||||
deriving (Show)
|
||||
|
||||
instance StrEncoding CIDeleteMode where
|
||||
@@ -113,11 +113,13 @@ instance StrEncoding CIDeleteMode where
|
||||
CIDMBroadcast -> "broadcast"
|
||||
CIDMInternal -> "internal"
|
||||
CIDMInternalMark -> "internalMark"
|
||||
CIDMHistory -> "history"
|
||||
strP =
|
||||
A.takeTill (== ' ') >>= \case
|
||||
"broadcast" -> pure CIDMBroadcast
|
||||
"internal" -> pure CIDMInternal
|
||||
"internalMark" -> pure CIDMInternalMark
|
||||
"history" -> pure CIDMHistory
|
||||
_ -> fail "bad CIDeleteMode"
|
||||
|
||||
instance ToJSON CIDeleteMode where
|
||||
@@ -132,6 +134,7 @@ ciDeleteModeToText = \case
|
||||
CIDMBroadcast -> "this item is deleted (broadcast)"
|
||||
CIDMInternal -> "this item is deleted (locally)"
|
||||
CIDMInternalMark -> "this item is deleted (locally)"
|
||||
CIDMHistory -> "this item is deleted (from history)"
|
||||
|
||||
-- This type is used both in API and in DB, so we use different JSON encodings for the database and for the API
|
||||
-- ! Nested sum types also have to use different encodings for database and API
|
||||
|
||||
@@ -437,7 +437,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
|
||||
XMsgNew :: MsgContainer -> ChatMsgEvent 'Json
|
||||
XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr} -> ChatMsgEvent 'Json
|
||||
XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, mentions :: Map MemberName MsgMention, ttl :: Maybe Int, live :: Maybe Bool, scope :: Maybe MsgScope, asGroup :: Maybe Bool, prefs :: Maybe MsgPrefs} -> ChatMsgEvent 'Json
|
||||
XMsgDel :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, scope :: Maybe MsgScope} -> ChatMsgEvent 'Json
|
||||
XMsgDel :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, scope :: Maybe MsgScope, onlyHistory :: Bool} -> ChatMsgEvent 'Json
|
||||
XMsgDeleted :: ChatMsgEvent 'Json
|
||||
XMsgReact :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, scope :: Maybe MsgScope, reaction :: MsgReaction, add :: Bool} -> ChatMsgEvent 'Json
|
||||
XFile :: FileInvitation -> ChatMsgEvent 'Json -- TODO discontinue
|
||||
@@ -458,6 +458,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
|
||||
XGrpRelayAcpt :: ShortLinkContact -> ChatMsgEvent 'Json
|
||||
XGrpRelayTest :: ByteString -> Maybe ByteString -> ChatMsgEvent 'Json
|
||||
XGrpRelayNew :: ShortLinkContact -> ChatMsgEvent 'Json
|
||||
XGrpRelayReject :: RelayRejectionReason -> ChatMsgEvent 'Json
|
||||
XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json
|
||||
XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json
|
||||
XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json
|
||||
@@ -1042,6 +1043,7 @@ data CMEventTag (e :: MsgEncoding) where
|
||||
XGrpRelayAcpt_ :: CMEventTag 'Json
|
||||
XGrpRelayTest_ :: CMEventTag 'Json
|
||||
XGrpRelayNew_ :: CMEventTag 'Json
|
||||
XGrpRelayReject_ :: CMEventTag 'Json
|
||||
XGrpMemNew_ :: CMEventTag 'Json
|
||||
XGrpMemIntro_ :: CMEventTag 'Json
|
||||
XGrpMemInv_ :: CMEventTag 'Json
|
||||
@@ -1100,6 +1102,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
|
||||
XGrpRelayAcpt_ -> "x.grp.relay.acpt"
|
||||
XGrpRelayTest_ -> "x.grp.relay.test"
|
||||
XGrpRelayNew_ -> "x.grp.relay.new"
|
||||
XGrpRelayReject_ -> "x.grp.relay.reject"
|
||||
XGrpMemNew_ -> "x.grp.mem.new"
|
||||
XGrpMemIntro_ -> "x.grp.mem.intro"
|
||||
XGrpMemInv_ -> "x.grp.mem.inv"
|
||||
@@ -1159,6 +1162,7 @@ instance StrEncoding ACMEventTag where
|
||||
"x.grp.relay.acpt" -> XGrpRelayAcpt_
|
||||
"x.grp.relay.test" -> XGrpRelayTest_
|
||||
"x.grp.relay.new" -> XGrpRelayNew_
|
||||
"x.grp.relay.reject" -> XGrpRelayReject_
|
||||
"x.grp.mem.new" -> XGrpMemNew_
|
||||
"x.grp.mem.intro" -> XGrpMemIntro_
|
||||
"x.grp.mem.inv" -> XGrpMemInv_
|
||||
@@ -1214,6 +1218,7 @@ toCMEventTag msg = case msg of
|
||||
XGrpRelayAcpt _ -> XGrpRelayAcpt_
|
||||
XGrpRelayTest {} -> XGrpRelayTest_
|
||||
XGrpRelayNew _ -> XGrpRelayNew_
|
||||
XGrpRelayReject _ -> XGrpRelayReject_
|
||||
XGrpMemNew {} -> XGrpMemNew_
|
||||
XGrpMemIntro _ _ -> XGrpMemIntro_
|
||||
XGrpMemInv _ _ -> XGrpMemInv_
|
||||
@@ -1342,7 +1347,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
|
||||
asGroup <- opt "asGroup"
|
||||
prefs <- opt "prefs"
|
||||
pure XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope, asGroup, prefs}
|
||||
XMsgDel_ -> XMsgDel <$> p "msgId" <*> opt "memberId" <*> opt "scope"
|
||||
XMsgDel_ -> XMsgDel <$> p "msgId" <*> opt "memberId" <*> opt "scope" <*> (fromMaybe False <$> opt "onlyHistory")
|
||||
XMsgDeleted_ -> pure XMsgDeleted
|
||||
XMsgReact_ -> XMsgReact <$> p "msgId" <*> opt "memberId" <*> opt "scope" <*> p "reaction" <*> p "add"
|
||||
XFile_ -> XFile <$> p "file"
|
||||
@@ -1373,6 +1378,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
|
||||
sig_ <- fmap (\(B64UrlByteString s) -> s) <$> opt "signature"
|
||||
pure $ XGrpRelayTest challenge sig_
|
||||
XGrpRelayNew_ -> XGrpRelayNew <$> p "relayLink"
|
||||
XGrpRelayReject_ -> XGrpRelayReject <$> p "reason"
|
||||
XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo" <*> opt "scope"
|
||||
XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo" <*> opt "memberRestrictions"
|
||||
XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro"
|
||||
@@ -1418,7 +1424,7 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en
|
||||
XMsgNew container -> msgContainerJSON container
|
||||
XMsgFileDescr msgId' fileDescr -> o ["msgId" .= msgId', "fileDescr" .= fileDescr]
|
||||
XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope, asGroup, prefs} -> o $ ("prefs" .=? prefs) $ ("asGroup" .=? asGroup) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("scope" .=? scope) $ ("mentions" .=? nonEmptyMap mentions) ["msgId" .= msgId', "content" .= content]
|
||||
XMsgDel msgId' memberId scope -> o $ ("memberId" .=? memberId) $ ("scope" .=? scope) ["msgId" .= msgId']
|
||||
XMsgDel msgId' memberId scope onlyHistory -> o $ ("memberId" .=? memberId) $ ("scope" .=? scope) $ ("onlyHistory" .=? justTrue onlyHistory) ["msgId" .= msgId']
|
||||
XMsgDeleted -> JM.empty
|
||||
XMsgReact msgId' memberId scope reaction add -> o $ ("memberId" .=? memberId) $ ("scope" .=? scope) ["msgId" .= msgId', "reaction" .= reaction, "add" .= add]
|
||||
XFile fileInv -> o ["file" .= fileInv]
|
||||
@@ -1441,6 +1447,7 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en
|
||||
("signature" .=? (B64UrlByteString <$> sig_))
|
||||
["challenge" .= B64UrlByteString challenge]
|
||||
XGrpRelayNew relayLink -> o ["relayLink" .= relayLink]
|
||||
XGrpRelayReject reason -> o ["reason" .= reason]
|
||||
XGrpMemNew memInfo scope -> o $ ("scope" .=? scope) ["memberInfo" .= memInfo]
|
||||
XGrpMemIntro memInfo memRestrictions -> o $ ("memberRestrictions" .=? memRestrictions) ["memberInfo" .= memInfo]
|
||||
XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro]
|
||||
|
||||
@@ -95,6 +95,8 @@ module Simplex.Chat.Store.Groups
|
||||
createRelayRequestGroup,
|
||||
updateRelayOwnStatusFromTo,
|
||||
updateRelayOwnStatus_,
|
||||
isRelayGroupRejected,
|
||||
allowRelayGroup,
|
||||
getRelayServedGroups,
|
||||
getRelayInactiveGroups,
|
||||
createNewContactMemberAsync,
|
||||
@@ -1523,8 +1525,8 @@ setGroupInProgressDone db GroupInfo {groupId} = do
|
||||
"UPDATE groups SET creating_in_progress = 0, updated_at = ? WHERE group_id = ?"
|
||||
(currentTs, groupId)
|
||||
|
||||
createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay = do
|
||||
createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> GroupMemberStatus -> RelayStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay memberStatus relayStatus = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
-- Create group with placeholder profile
|
||||
let Profile {displayName = fromMemberLDN} = fromMemberProfile
|
||||
@@ -1538,13 +1540,13 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
|
||||
groupPreferences = Nothing,
|
||||
memberAdmission = Nothing
|
||||
}
|
||||
(groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just RSInvited) Nothing currentTs
|
||||
(groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just relayStatus) Nothing currentTs
|
||||
-- Store relay request data for recovery
|
||||
liftIO $ setRelayRequestData_ groupId currentTs
|
||||
ownerMemberId <- insertOwner_ currentTs groupId
|
||||
let relayMember = MemberIdRole relayMemberId GRRelay
|
||||
-- TODO [member keys] should relays use member keys?
|
||||
_membership <- createContactMemberInv_ db user groupId (Just ownerMemberId) user relayMember GCUserMember GSMemAccepted IBUnknown Nothing Nothing currentTs vr
|
||||
_membership <- createContactMemberInv_ db user groupId (Just ownerMemberId) user relayMember GCUserMember memberStatus IBUnknown Nothing Nothing currentTs vr
|
||||
ownerMember <- getGroupMember db vr user groupId ownerMemberId
|
||||
g <- getGroupInfo db vr user groupId
|
||||
pure (g, ownerMember)
|
||||
@@ -1578,7 +1580,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
|
||||
peer_chat_min_version, peer_chat_max_version)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
( (groupId, indexInGroup, memberId, memberRole, GCHostMember, GSMemAccepted)
|
||||
( (groupId, indexInGroup, memberId, memberRole, GCHostMember, memberStatus)
|
||||
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
|
||||
:. (minV, maxV)
|
||||
)
|
||||
@@ -1596,6 +1598,41 @@ updateRelayOwnStatus_ db GroupInfo {groupId} relayStatus = do
|
||||
let inactiveAt_ = if relayStatus == RSInactive then Just currentTs else Nothing
|
||||
DB.execute db "UPDATE groups SET relay_own_status = ?, relay_inactive_at = ?, updated_at = ? WHERE group_id = ?" (relayStatus, inactiveAt_, currentTs, groupId)
|
||||
|
||||
-- Flip every RSRejected row sharing the targeted group's relay_request_group_link
|
||||
-- to RSInactive in one statement; returns the refreshed GroupInfo for the targeted groupId.
|
||||
allowRelayGroup :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO GroupInfo
|
||||
allowRelayGroup db vr user@User {userId} groupId = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
liftIO $
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET relay_own_status = ?, relay_inactive_at = ?, updated_at = ?
|
||||
WHERE user_id = ?
|
||||
AND relay_request_group_link = (SELECT relay_request_group_link FROM groups WHERE group_id = ?)
|
||||
AND relay_own_status = ?
|
||||
|]
|
||||
(RSInactive, currentTs, currentTs, userId, groupId, RSRejected)
|
||||
getGroupInfo db vr user groupId
|
||||
|
||||
isRelayGroupRejected :: DB.Connection -> User -> ShortLinkContact -> IO Bool
|
||||
isRelayGroupRejected db User {userId} groupLink =
|
||||
fromMaybe False <$> maybeFirstRow fromOnly (
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM groups
|
||||
WHERE user_id = ?
|
||||
AND relay_request_group_link = ?
|
||||
AND relay_own_status = ?
|
||||
LIMIT 1
|
||||
)
|
||||
|]
|
||||
(userId, groupLink, RSRejected)
|
||||
)
|
||||
|
||||
getRelayServedGroups :: DB.Connection -> VersionRangeChat -> User -> IO [GroupInfo]
|
||||
getRelayServedGroups db vr User {userId, userContactId} = do
|
||||
map (toGroupInfo vr userContactId [])
|
||||
|
||||
@@ -31,6 +31,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260403_item_viewed
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260407_channel_comments
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260429_relay_request_retries
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260507_relay_inactive_at
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260514_relay_request_group_link_index
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Text, Maybe Text)]
|
||||
@@ -61,7 +62,8 @@ schemaMigrations =
|
||||
("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed),
|
||||
("20260407_channel_comments", m20260407_channel_comments, Just down_m20260407_channel_comments),
|
||||
("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries),
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at)
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at),
|
||||
("20260514_relay_request_group_link_index", m20260514_relay_request_group_link_index, Just down_m20260514_relay_request_group_link_index)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.Postgres.Migrations.M20260514_relay_request_group_link_index where
|
||||
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
m20260514_relay_request_group_link_index :: Text
|
||||
m20260514_relay_request_group_link_index =
|
||||
[r|
|
||||
CREATE INDEX idx_groups_relay_request_group_link
|
||||
ON groups(user_id, relay_request_group_link)
|
||||
WHERE relay_request_group_link IS NOT NULL;
|
||||
|]
|
||||
|
||||
down_m20260514_relay_request_group_link_index :: Text
|
||||
down_m20260514_relay_request_group_link_index =
|
||||
[r|
|
||||
DROP INDEX idx_groups_relay_request_group_link;
|
||||
|]
|
||||
@@ -2359,6 +2359,10 @@ CREATE INDEX idx_groups_inv_queue_info ON test_chat_schema.groups USING btree (i
|
||||
|
||||
|
||||
|
||||
CREATE INDEX idx_groups_relay_request_group_link ON test_chat_schema.groups USING btree (user_id, relay_request_group_link) WHERE (relay_request_group_link IS NOT NULL);
|
||||
|
||||
|
||||
|
||||
CREATE INDEX idx_groups_summary_current_members_count ON test_chat_schema.groups USING btree (summary_current_members_count);
|
||||
|
||||
|
||||
|
||||
@@ -154,6 +154,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260403_item_viewed
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260407_channel_comments
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260429_relay_request_retries
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260507_relay_inactive_at
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260514_relay_request_group_link_index
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -307,7 +308,8 @@ schemaMigrations =
|
||||
("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed),
|
||||
("20260407_channel_comments", m20260407_channel_comments, Just down_m20260407_channel_comments),
|
||||
("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries),
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at)
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at),
|
||||
("20260514_relay_request_group_link_index", m20260514_relay_request_group_link_index, Just down_m20260514_relay_request_group_link_index)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.SQLite.Migrations.M20260514_relay_request_group_link_index where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20260514_relay_request_group_link_index :: Query
|
||||
m20260514_relay_request_group_link_index =
|
||||
[sql|
|
||||
CREATE INDEX idx_groups_relay_request_group_link
|
||||
ON groups(user_id, relay_request_group_link)
|
||||
WHERE relay_request_group_link IS NOT NULL;
|
||||
|]
|
||||
|
||||
down_m20260514_relay_request_group_link_index :: Query
|
||||
down_m20260514_relay_request_group_link_index =
|
||||
[sql|
|
||||
DROP INDEX idx_groups_relay_request_group_link;
|
||||
|]
|
||||
@@ -1197,9 +1197,9 @@ Query: UPDATE connections SET smp_agent_version = ? WHERE conn_id = ?
|
||||
Plan:
|
||||
SEARCH connections USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query: UPDATE deleted_snd_chunk_replicas SET delay = ?, retries = retries + 1, updated_at = ? WHERE deleted_snd_chunk_replica_id = ?
|
||||
Query: UPDATE connections SET smp_agent_version = ?, pq_support = ?, enable_ntfs = ? WHERE conn_id = ?
|
||||
Plan:
|
||||
SEARCH deleted_snd_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH connections USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query: UPDATE messages SET msg_body = x'' WHERE conn_id = ? AND internal_id = ?
|
||||
Plan:
|
||||
@@ -1209,6 +1209,10 @@ Query: UPDATE ratchets SET ratchet_state = ? WHERE conn_id = ?
|
||||
Plan:
|
||||
SEARCH ratchets USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
Query: UPDATE rcv_file_chunk_replicas SET delay = ?, retries = retries + 1, updated_at = ? WHERE rcv_file_chunk_replica_id = ?
|
||||
Plan:
|
||||
SEARCH rcv_file_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE rcv_file_chunk_replicas SET received = 1, updated_at = ? WHERE rcv_file_chunk_replica_id = ?
|
||||
Plan:
|
||||
SEARCH rcv_file_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -1405,14 +1405,6 @@ SEARCH users USING INTEGER PRIMARY KEY (rowid=?)
|
||||
INDEX 2
|
||||
SEARCH users USING INDEX sqlite_autoindex_users_1 (contact_id=?)
|
||||
|
||||
Query:
|
||||
SELECT COUNT(1) FROM group_members
|
||||
WHERE group_id = ? AND member_role = ?
|
||||
AND member_status IN (?,?,?,?,?,?,?)
|
||||
|
||||
Plan:
|
||||
SEARCH group_members USING INDEX idx_group_members_group_id_index_in_group (group_id=?)
|
||||
|
||||
Query:
|
||||
SELECT agent_conn_id FROM (
|
||||
SELECT
|
||||
@@ -3346,6 +3338,20 @@ SCAN CONSTANT ROW
|
||||
SCALAR SUBQUERY 1
|
||||
SCAN groups
|
||||
|
||||
Query:
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM groups
|
||||
WHERE user_id = ?
|
||||
AND relay_request_group_link = ?
|
||||
AND relay_own_status = ?
|
||||
LIMIT 1
|
||||
)
|
||||
|
||||
Plan:
|
||||
SCAN CONSTANT ROW
|
||||
SCALAR SUBQUERY 1
|
||||
SEARCH groups USING INDEX idx_groups_relay_request_group_link (user_id=? AND relay_request_group_link=?)
|
||||
|
||||
Query:
|
||||
SELECT agent_conn_id
|
||||
FROM connections
|
||||
@@ -3963,15 +3969,6 @@ Query:
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_shared_msg_id (user_id=? AND group_id=? AND group_member_id=?)
|
||||
|
||||
Query:
|
||||
UPDATE chat_items
|
||||
SET item_deleted = ?, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0
|
||||
RETURNING chat_item_id
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id=? AND msg_content_tag=? AND item_deleted=? AND item_sent=?)
|
||||
|
||||
Query:
|
||||
UPDATE chat_items
|
||||
SET item_deleted = ?, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, updated_at = ?
|
||||
@@ -4052,6 +4049,18 @@ Query:
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET relay_own_status = ?, relay_inactive_at = ?, updated_at = ?
|
||||
WHERE user_id = ?
|
||||
AND relay_request_group_link = (SELECT relay_request_group_link FROM groups WHERE group_id = ?)
|
||||
AND relay_own_status = ?
|
||||
|
||||
Plan:
|
||||
SEARCH groups USING INDEX idx_groups_relay_request_group_link (user_id=? AND relay_request_group_link=?)
|
||||
SCALAR SUBQUERY 1
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE groups
|
||||
SET via_group_link_uri = ?, via_group_link_uri_hash = ?
|
||||
@@ -4642,6 +4651,15 @@ Query:
|
||||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
INSERT INTO connections (
|
||||
user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated,
|
||||
group_member_id, via_short_link_contact, custom_user_profile_id, via_group_link,
|
||||
created_at, updated_at, to_subscribe
|
||||
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|
||||
Plan:
|
||||
|
||||
Query:
|
||||
INSERT INTO connections (
|
||||
user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated,
|
||||
@@ -4927,6 +4945,16 @@ Query:
|
||||
Plan:
|
||||
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE connections
|
||||
SET via_contact_uri = ?, via_contact_uri_hash = ?, group_link_id = ?,
|
||||
conn_chat_version = ?, pq_support = ?, pq_encryption = ?,
|
||||
updated_at = ?
|
||||
WHERE user_id = ? AND connection_id = ?
|
||||
|
||||
Plan:
|
||||
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE connections_sync
|
||||
SET should_sync = 0, last_sync_ts = ?
|
||||
@@ -5405,6 +5433,26 @@ SCAN chat_items USING COVERING INDEX idx_chat_items_group_member_id
|
||||
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
|
||||
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
|
||||
m.created_at, m.updated_at,
|
||||
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, m.member_pub_key, m.relay_link,
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
|
||||
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
|
||||
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
|
||||
FROM group_members m
|
||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
|
||||
JOIN group_member_status_predicates sp ON m.member_status = sp.member_status WHERE m.group_id = ? AND m.relay_link = ? AND sp.current_member = 1
|
||||
Plan:
|
||||
SEARCH m USING INDEX idx_group_members_group_id_index_in_group (group_id=?)
|
||||
SEARCH sp USING INDEX sqlite_autoindex_group_member_status_predicates_1 (member_status=?)
|
||||
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
|
||||
@@ -5500,25 +5548,6 @@ SEARCH m USING INDEX sqlite_autoindex_group_members_1 (group_id=? AND member_id=
|
||||
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
|
||||
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.short_descr, p.image, p.contact_link, p.chat_peer_type, p.local_alias, p.preferences,
|
||||
m.created_at, m.updated_at,
|
||||
m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts, m.member_pub_key, m.relay_link,
|
||||
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.xcontact_id, c.custom_user_profile_id,
|
||||
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.user_contact_link_id,
|
||||
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
|
||||
c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version
|
||||
FROM group_members m
|
||||
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
|
||||
LEFT JOIN connections c ON c.group_member_id = m.group_member_id
|
||||
WHERE m.group_id = ? AND m.relay_link = ?
|
||||
Plan:
|
||||
SEARCH m USING INDEX idx_group_members_group_id_index_in_group (group_id=?)
|
||||
SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
|
||||
SEARCH c USING INDEX idx_connections_group_member_id (group_member_id=?) LEFT-JOIN
|
||||
|
||||
Query:
|
||||
SELECT
|
||||
m.group_member_id, m.group_id, m.index_in_group, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
|
||||
@@ -6574,6 +6603,10 @@ Query: SELECT 1 FROM settings WHERE user_id = ? LIMIT 1
|
||||
Plan:
|
||||
SEARCH settings USING COVERING INDEX idx_settings_user_id (user_id=?)
|
||||
|
||||
Query: SELECT COUNT(*) FROM groups WHERE relay_own_status IS NOT NULL
|
||||
Plan:
|
||||
SCAN groups
|
||||
|
||||
Query: SELECT COUNT(1) FROM chat_item_versions WHERE chat_item_id = ?
|
||||
Plan:
|
||||
SEARCH chat_item_versions USING COVERING INDEX idx_chat_item_versions_chat_item_id (chat_item_id=?)
|
||||
@@ -6590,6 +6623,11 @@ Query: SELECT COUNT(1) FROM group_members WHERE member_role = 'owner' AND member
|
||||
Plan:
|
||||
SCAN group_members
|
||||
|
||||
Query: SELECT COUNT(1) FROM group_members m JOIN group_member_status_predicates sp ON m.member_status = sp.member_status WHERE m.group_id = ? AND m.member_role = ? AND sp.current_member = 1
|
||||
Plan:
|
||||
SEARCH m USING INDEX idx_group_members_group_id_index_in_group (group_id=?)
|
||||
SEARCH sp USING INDEX sqlite_autoindex_group_member_status_predicates_1 (member_status=?)
|
||||
|
||||
Query: SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0
|
||||
Plan:
|
||||
SEARCH groups USING INDEX sqlite_autoindex_groups_2 (user_id=?)
|
||||
@@ -6784,6 +6822,10 @@ Query: SELECT group_id, conn_full_link_to_connect FROM groups WHERE user_id = ?
|
||||
Plan:
|
||||
SEARCH groups USING INDEX sqlite_autoindex_groups_2 (user_id=?)
|
||||
|
||||
Query: SELECT group_id, relay_own_status FROM groups WHERE relay_own_status IS NOT NULL ORDER BY group_id
|
||||
Plan:
|
||||
SCAN groups
|
||||
|
||||
Query: SELECT group_member_id FROM group_members WHERE user_id = ? AND contact_profile_id = ? AND group_member_id != ? LIMIT 1
|
||||
Plan:
|
||||
SEARCH group_members USING INDEX idx_group_members_user_id (user_id=?)
|
||||
@@ -6848,6 +6890,10 @@ Query: SELECT relay_own_status FROM groups WHERE group_id = ?
|
||||
Plan:
|
||||
SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: SELECT relay_status FROM group_relays
|
||||
Plan:
|
||||
SCAN group_relays
|
||||
|
||||
Query: SELECT relay_status FROM group_relays WHERE group_relay_id = ?
|
||||
Plan:
|
||||
SEARCH group_relays USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -1295,6 +1295,12 @@ CREATE INDEX idx_chat_items_groups_item_viewed ON chat_items(
|
||||
item_viewed,
|
||||
item_ts
|
||||
);
|
||||
CREATE INDEX idx_groups_relay_request_group_link
|
||||
ON groups(
|
||||
user_id,
|
||||
relay_request_group_link
|
||||
)
|
||||
WHERE relay_request_group_link IS NOT NULL;
|
||||
CREATE TRIGGER on_group_members_insert_update_summary
|
||||
AFTER INSERT ON group_members
|
||||
FOR EACH ROW
|
||||
|
||||
@@ -494,6 +494,15 @@ data GroupInfo = GroupInfo
|
||||
useRelays' :: GroupInfo -> Bool
|
||||
useRelays' GroupInfo {useRelays} = isTrue useRelays
|
||||
|
||||
relayServesGroup :: GroupInfo -> Bool
|
||||
relayServesGroup GroupInfo {relayOwnStatus} = case relayOwnStatus of
|
||||
Just RSInactive -> False
|
||||
Just RSRejected -> False
|
||||
_ -> True
|
||||
|
||||
publicGroupEditor :: GroupInfo -> GroupMember -> Bool
|
||||
publicGroupEditor gInfo mem = useRelays' gInfo && memberRole' mem >= GRModerator
|
||||
|
||||
groupId' :: GroupInfo -> GroupId
|
||||
groupId' GroupInfo {groupId} = groupId
|
||||
|
||||
@@ -916,6 +925,26 @@ instance ToJSON GroupRejectionReason where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
|
||||
data RelayRejectionReason
|
||||
= RRRRejoinRejected
|
||||
| RRRUnknown {text :: Text}
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance StrEncoding RelayRejectionReason where
|
||||
strEncode = \case
|
||||
RRRRejoinRejected -> "rejoin_rejected"
|
||||
RRRUnknown text -> encodeUtf8 text
|
||||
strP =
|
||||
"rejoin_rejected" $> RRRRejoinRejected
|
||||
<|> RRRUnknown . safeDecodeUtf8 <$> A.takeByteString
|
||||
|
||||
instance FromJSON RelayRejectionReason where
|
||||
parseJSON = strParseJSON "RelayRejectionReason"
|
||||
|
||||
instance ToJSON RelayRejectionReason where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
|
||||
data MemberIdRole = MemberIdRole
|
||||
{ memberId :: MemberId,
|
||||
memberRole :: GroupMemberRole
|
||||
|
||||
@@ -87,6 +87,7 @@ data RelayStatus
|
||||
| RSAccepted
|
||||
| RSActive
|
||||
| RSInactive
|
||||
| RSRejected
|
||||
deriving (Eq, Show)
|
||||
|
||||
relayStatusText :: RelayStatus -> Text
|
||||
@@ -96,6 +97,7 @@ relayStatusText = \case
|
||||
RSAccepted -> "accepted"
|
||||
RSActive -> "active"
|
||||
RSInactive -> "inactive"
|
||||
RSRejected -> "rejected"
|
||||
|
||||
instance TextEncoding RelayStatus where
|
||||
textEncode = \case
|
||||
@@ -104,12 +106,14 @@ instance TextEncoding RelayStatus where
|
||||
RSAccepted -> "accepted"
|
||||
RSActive -> "active"
|
||||
RSInactive -> "inactive"
|
||||
RSRejected -> "rejected"
|
||||
textDecode = \case
|
||||
"new" -> Just RSNew
|
||||
"invited" -> Just RSInvited
|
||||
"accepted" -> Just RSAccepted
|
||||
"active" -> Just RSActive
|
||||
"inactive" -> Just RSInactive
|
||||
"rejected" -> Just RSRejected
|
||||
_ -> Nothing
|
||||
|
||||
instance FromField RelayStatus where fromField = fromTextField_ textDecode
|
||||
@@ -118,6 +122,7 @@ instance ToField RelayStatus where toField = toField . textEncode
|
||||
|
||||
$(JQ.deriveJSON (enumJSON $ dropPrefix "RS") ''RelayStatus)
|
||||
|
||||
|
||||
data MsgSigStatus = MSSVerified | MSSSignedNoKey
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRGroupRelays u g relays -> ttyUser u $ viewGroupRelays g relays
|
||||
CRGroupRelaysAdded u g _groupLink relays -> ttyUser u $ viewGroupRelays g relays
|
||||
CRGroupRelaysAddFailed u results -> ttyUser u $ viewGroupRelaysAddFailed results
|
||||
CRRelayGroupAllowed u g -> ttyUser u [ttyFullGroup g <> ": relay rejection cleared"]
|
||||
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
|
||||
CRMemberSupportChats u g ms -> ttyUser u $ viewMemberSupportChats g ms
|
||||
-- CRGroupConversationsArchived u _g _conversations -> ttyUser u []
|
||||
@@ -222,7 +223,14 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRUserDeletedMembers u g members wm signed -> case members of
|
||||
[m] -> ttyUser u [ttyGroup' g <> ": you removed " <> ttyMember m <> " from the group" <> withMessages wm <> signedStr signed]
|
||||
mems' -> ttyUser u [ttyGroup' g <> ": you removed " <> sShow (length mems') <> " members from the group" <> withMessages wm <> signedStr signed]
|
||||
CRLeftMemberUser u g -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g
|
||||
CRLeftMemberUser u g
|
||||
| relayOwnStatus g == Just RSRejected ->
|
||||
ttyUser u
|
||||
[ ttyGroup' g <> ": you left the group (future invitations will be rejected)",
|
||||
"use " <> highlight ("/group allow #" <> viewGroupName g) <> " to allow future invitations",
|
||||
"use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the group (also clears the rejection)"
|
||||
]
|
||||
| otherwise -> ttyUser u $ [ttyGroup' g <> ": you left the group"] <> groupPreserved g
|
||||
CRGroupDeletedUser u g signed -> ttyUser u [ttyGroup' g <> ": you deleted the group" <> signedStr signed]
|
||||
CRForwardPlan u count itemIds fc -> ttyUser u $ viewForwardPlan count itemIds fc
|
||||
CRChatMsgContent u mc -> ttyUser u $ ttyMsgContent mc <> viewMsgTestInfo testView mc
|
||||
@@ -541,6 +549,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
|
||||
CEvtTerminalEvent te -> case te of
|
||||
TERejectingGroupJoinRequestMember _ g m reason -> [ttyFullMember m <> ": rejecting request to join group " <> ttyGroup' g <> ", reason: " <> sShow reason]
|
||||
TEGroupLinkRejected u g reason -> ttyUser u [ttyGroup' g <> ": join rejected, reason: " <> sShow reason]
|
||||
TERelayRejected u g reason -> ttyUser u [ttyGroup' g <> ": relay rejected, reason: " <> sShow reason]
|
||||
TENewMemberContact u _ g m -> ttyUser u ["contact for member " <> ttyGroup' g <> " " <> ttyMember m <> " is created"]
|
||||
TEContactVerificationReset u ct -> ttyUser u $ viewContactVerificationReset ct
|
||||
TEGroupMemberVerificationReset u g m -> ttyUser u $ viewGroupMemberVerificationReset g m
|
||||
@@ -1435,11 +1444,14 @@ viewGroupsList gs = map groupSS $ sortOn ldn_ gs
|
||||
where
|
||||
ldn_ :: GroupInfo -> Text
|
||||
ldn_ GroupInfo {localDisplayName} = T.toLower localDisplayName
|
||||
groupSS g@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}, groupSummary = GroupSummary {currentMembers}} =
|
||||
groupSS g@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}, groupSummary = GroupSummary {currentMembers}, relayOwnStatus} =
|
||||
case memberStatus membership of
|
||||
GSMemInvited -> groupInvitation' g
|
||||
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s <> alias g
|
||||
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s <> rejectionSuffix <> alias g
|
||||
where
|
||||
rejectionSuffix = case relayOwnStatus of
|
||||
Just RSRejected -> " [rejected]"
|
||||
_ -> ""
|
||||
viewMemberStatus = \case
|
||||
GSMemRejected -> delete "you are rejected"
|
||||
GSMemRemoved -> delete "you are removed"
|
||||
|
||||
Reference in New Issue
Block a user