From 3726040d84a407ed6317d74995fa8a664fc9a99f Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Sat, 25 Jul 2026 09:42:34 +0000 Subject: [PATCH] core: ignore mentions and replies of blocked members (#7303) Messages of blocked members are hidden, but they still marked chat item as user mention, showing mention badge and counting group as unread in "mentions only" notification mode: - createNonLive passed mentions unfiltered, allowing members blocked by admin to mention user - it is the default path, as it is only used when full delete is not allowed; - userReply was set for replies to user messages from blocked members. --- src/Simplex/Chat/Library/Internal.hs | 6 ++- src/Simplex/Chat/Library/Subscriber.hs | 3 +- tests/ChatTests/Groups.hs | 69 ++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index 271348b5d6..6fb002eaee 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -2811,7 +2811,11 @@ saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, msgSigned, forwardedByMem where groupMentions db g membership = do mentions' <- getRcvCIMentions db user g ft_ mentions - let userReply = case cmToQuotedMsg chatMsgEvent of + -- messages of blocked members are hidden, they should not mention user + let senderBlocked = case cd of + CDGroupRcv _g _scope m -> memberBlocked m + _ -> False + userReply = not senderBlocked && case cmToQuotedMsg chatMsgEvent of Just QuotedMsg {msgRef = MsgRef {memberId = Just mId}} -> sameMemberId mId membership _ -> False userMention' = userReply || any (\CIMention {memberId} -> sameMemberId memberId membership) mentions' diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 949a9c8dfe..a65bed75a0 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -2221,7 +2221,8 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = toView $ CEvtChatItemsDeleted user deletions False False -- m' is Maybe GroupMember createNonLive gInfo' m' scopeInfo file_ = do - saveRcvCI gInfo' m' scopeInfo (CIRcvMsgContent content, ts) (snd <$> file_) (timed_ gInfo') False mentions + let mentions' = if maybe False memberBlocked m' then M.empty else mentions + saveRcvCI gInfo' m' scopeInfo (CIRcvMsgContent content, ts) (snd <$> file_) (timed_ gInfo') False mentions' createContentItem gInfo' m' scopeInfo = do file_ <- processFileInv gInfo' m' newChatItem gInfo' m' scopeInfo (CIRcvMsgContent content, ts) (snd <$> file_) (timed_ gInfo') live' diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index c74b6367b4..37827ab2ad 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -213,6 +213,8 @@ chatGroupTests = do it "repeat block, unblock" testBlockForAllRepeat it "block multiple members" testBlockForAllMultipleMembers it "block left/removed members" testBlockForAllLeftRemoved + it "mentions of blocked member are ignored" testBlockForAllMentionsIgnored + it "replies of blocked member are not mentions" testBlockedMemberReplyNotMention describe "group member inactivity" $ do it "mark member inactive on reaching quota" testGroupMemberInactive describe "group member reports" $ do @@ -6921,6 +6923,73 @@ testBlockForAllMarkedBlocked = ) bob #$> ("/_get chat #1 count=4", chat, [(1, "1"), (1, "2"), (1, "3"), (1, "4")]) +testBlockForAllMentionsIgnored :: HasCallStack => TestParams -> IO () +testBlockForAllMentionsIgnored = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath + + threadDelay 1000000 + + -- mention of user is shown as mention ("!" after member name) + bob #> "#team hello @alice" + alice <# "#team bob!> hello @alice" + cath <# "#team bob> hello @alice" + + threadDelay 1000000 + + alice ##> "/block for all #team bob" + alice <## "#team: you blocked bob" + cath <## "#team: alice blocked bob" + bob "#team hello again @alice" + alice <# "#team bob> hello again @alice [blocked by admin] " + cath <# "#team bob> hello again @alice [blocked by admin] " + +testBlockedMemberReplyNotMention :: HasCallStack => TestParams -> IO () +testBlockedMemberReplyNotMention = + testChat3 aliceProfile bobProfile cathProfile $ + \alice bob cath -> do + createGroup3 "team" alice bob cath + + threadDelay 1000000 + + bob #> "#team hi" + alice <# "#team bob> hi" + cath <# "#team bob> hi" + + threadDelay 1000000 + + -- reply to user message is shown as mention ("!" after member name) + alice `send` "> #team @bob (hi) hey bob!" + alice <# "#team > bob hi" + alice <## " hey bob!" + bob <# "#team alice!> > bob hi" + bob <## " hey bob!" + cath <# "#team alice> > bob hi" + cath <## " hey bob!" + + threadDelay 1000000 + + -- admins can only block for all, blocking for self via api + bob ##> "/_member settings #1 1 {\"showMessages\": false}" + bob <## "ok" + + threadDelay 1000000 + + -- reply of blocked member is ignored (no "!" after member name) + alice `send` "> #team @bob (hi) hey again!" + alice <# "#team > bob hi" + alice <## " hey again!" + bob <#. "#team alice> > bob hi" + bob <##. " hey again!" + cath <# "#team alice> > bob hi" + cath <## " hey again!" + testBlockForAllFullDelete :: HasCallStack => TestParams -> IO () testBlockForAllFullDelete = testChat3 aliceProfile bobProfile cathProfile $