Merge branch 'master' into chat-relays

This commit is contained in:
spaced4ndy
2026-02-19 15:29:18 +04:00
149 changed files with 9834 additions and 94 deletions
+9 -1
View File
@@ -12,7 +12,7 @@ import Control.Concurrent.Async
import Control.Concurrent.STM
import Control.Monad
import qualified Data.ByteString.Char8 as B
import Data.List.NonEmpty (NonEmpty)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as L
import qualified Data.Map.Strict as M
import Data.Maybe (isJust)
@@ -26,6 +26,7 @@ import Simplex.Chat.Protocol (MsgContent (..))
import Simplex.Chat.Store
import Simplex.Chat.Types (Contact (..), ContactId, IsContact (..), User (..))
import Simplex.Messaging.Agent.Protocol (CreatedConnLink (..))
import Simplex.Messaging.Crypto.File (CryptoFile)
import Simplex.Messaging.Encoding.String (strEncode)
import System.Exit (exitFailure)
@@ -89,6 +90,13 @@ sendComposedMessages_ cc sendRef qmcs = do
Right (CRNewChatItems {}) -> printLog cc CLLInfo $ "sent " <> show (length cms) <> " messages to " <> show sendRef
r -> putStrLn $ "unexpected send message response: " <> show r
sendComposedMessageFile :: ChatController -> SendRef -> Maybe ChatItemId -> MsgContent -> CryptoFile -> IO ()
sendComposedMessageFile cc sendRef qiId mc file = do
let cm = ComposedMessage {fileSource = Just file, quotedItemId = qiId, msgContent = mc, mentions = M.empty}
sendChatCmd cc (APISendMessages sendRef False Nothing (cm :| [])) >>= \case
Right (CRNewChatItems {}) -> printLog cc CLLInfo $ "sent file message to " <> show sendRef
r -> putStrLn $ "unexpected send message response: " <> show r
deleteMessage :: ChatController -> Contact -> ChatItemId -> IO ()
deleteMessage cc ct chatItemId = do
let cmd = APIDeleteChatItem (contactRef ct) [chatItemId] CIDMInternal
+2 -2
View File
@@ -3656,8 +3656,8 @@ processChatCommand vr nm = \case
pure gInfo
sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM ()
sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership, businessChat} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership
let currentMemCount = fromIntegral $ currentMembers $ groupSummary gInfo
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
groupInv =
GroupInvitation
{ fromMember = MemberIdRole userMemberId userRole,
+14 -7
View File
@@ -338,13 +338,22 @@ quoteContent mc qmc ciFile_
qTextOrFile = if T.null qText then qFileName else qText
prohibitedGroupContent :: GroupInfo -> GroupMember -> Maybe GroupChatScopeInfo -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature
prohibitedGroupContent gInfo@GroupInfo {membership = GroupMember {memberRole = userRole}} m scopeInfo mc ft file_ sent
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice
prohibitedGroupContent gInfo@GroupInfo {membership = mem@GroupMember {memberRole = userRole}} m scopeInfo mc ft file_ sent
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) && not hostApprovalVoice = Just GFVoice
| isNothing scopeInfo && not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles
| isNothing scopeInfo && isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports
| isNothing scopeInfo && prohibitedSimplexLinks gInfo m ft = Just GFSimplexLinks
| otherwise = Nothing
where
hostApprovalVoice
| sent = userRole >= GRAdmin && sendApprovalPhase
| otherwise = memberCategory m == GCHostMember && hostApprovalPhase
hostApprovalPhase = case scopeInfo of
Just (GCSIMemberSupport Nothing) -> memberStatus mem == GSMemPendingApproval
_ -> False
sendApprovalPhase = case scopeInfo of
Just (GCSIMemberSupport (Just scopeMem)) -> memberStatus scopeMem == GSMemPendingApproval
_ -> False
-- admins cannot send reports, non-admins cannot receive reports
badReportUser
| sent = userRole >= GRModerator
@@ -929,11 +938,9 @@ acceptGroupJoinRequestAsync
incognitoProfile = do
gVar <- asks random
let initialStatus = acceptanceToStatus (memberAdmission groupProfile) gAccepted
((groupMemberId, memberId), currentMemCount) <- withStore $ \db ->
liftM2
(,)
(createJoiningMember db gVar user gInfo cReqChatVRange cReqProfile cReqXContactId_ cReqMemberId_ welcomeMsgId_ gLinkMemRole initialStatus)
(liftIO $ getGroupCurrentMembersCount db user gInfo)
(groupMemberId, memberId) <- withStore $ \db ->
createJoiningMember db gVar user gInfo cReqChatVRange cReqProfile cReqXContactId_ cReqMemberId_ welcomeMsgId_ gLinkMemRole initialStatus
let currentMemCount = fromIntegral $ currentMembers $ groupSummary gInfo
let Profile {displayName} = userProfileInGroup user gInfo (fromIncognitoProfile <$> incognitoProfile)
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
msg =
+4 -4
View File
@@ -703,8 +703,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
where
sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> CM ()
sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
let GroupMember {memberRole = userRole, memberId = userMemberId} = membership
let currentMemCount = fromIntegral $ currentMembers $ groupSummary gInfo
GroupMember {memberRole = userRole, memberId = userMemberId} = membership
groupInv =
GroupInvitation
{ fromMember = MemberIdRole userMemberId userRole,
@@ -986,8 +986,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
pure $ NewMessageDeliveryTask {messageId = msgId, taskContext}
checkSendRcpt :: [AChatMessage] -> CM Bool
checkSendRcpt aMsgs = do
currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo
let GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo
let currentMemCount = fromIntegral $ currentMembers $ groupSummary gInfo
GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo
pure $
fromMaybe (sendRcptsSmallGroups user) sendRcpts
&& any aChatMsgHasReceipt aMsgs
+6 -1
View File
@@ -77,12 +77,13 @@ import Simplex.Messaging.Version hiding (version)
-- 14 - support sending and receiving group join rejection (2025-02-24)
-- 15 - support specifying message scopes for group messages (2025-03-12)
-- 16 - support short link data (2025-06-10)
-- 17 - allow host voice messages during member approval regardless of group voice setting (2026-02-10)
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
-- This indirection is needed for backward/forward compatibility testing.
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
currentChatVersion :: VersionChat
currentChatVersion = VersionChat 16
currentChatVersion = VersionChat 17
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
supportedChatVRange :: VersionRangeChat
@@ -145,6 +146,10 @@ groupKnockingVersion = VersionChat 15
shortLinkDataVersion :: VersionChat
shortLinkDataVersion = VersionChat 16
-- support host voice messages during member approval regardless of group voice setting
memberSupportVoiceVersion :: VersionChat
memberSupportVoiceVersion = VersionChat 17
agentToChatVersion :: VersionSMPA -> VersionChat
agentToChatVersion v
| v < pqdrSMPAgentVersion = initialChatVersion
-15
View File
@@ -68,7 +68,6 @@ module Simplex.Chat.Store.Groups
getGroupModerators,
getGroupRelayMembers,
getGroupMembersForExpiration,
getGroupCurrentMembersCount,
deleteGroupChatItems,
deleteGroupMembers,
cleanupHostGroupLinkConn,
@@ -1164,20 +1163,6 @@ getGroupMembersForExpiration db vr user@User {userId, userContactId} GroupInfo {
)
(groupId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown)
getGroupCurrentMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int
getGroupCurrentMembersCount db User {userId} GroupInfo {groupId} = do
statuses :: [GroupMemberStatus] <-
map fromOnly
<$> DB.query
db
[sql|
SELECT member_status
FROM group_members
WHERE group_id = ? AND user_id = ?
|]
(groupId, userId)
pure $ length $ filter memberCurrent' statuses
getGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO ReceivedGroupInvitation
getGroupInvitation db vr user groupId =
getConnRec_ user >>= \case
@@ -1197,6 +1197,10 @@ 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 = ?
Plan:
SEARCH deleted_snd_chunk_replicas USING INTEGER PRIMARY KEY (rowid=?)
Query: UPDATE messages SET msg_body = x'' WHERE conn_id = ? AND internal_id = ?
Plan:
SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?)
@@ -1205,10 +1209,6 @@ 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=?)
@@ -1588,14 +1588,6 @@ Plan:
SEARCH g USING INTEGER PRIMARY KEY (rowid=?)
SEARCH i USING INTEGER PRIMARY KEY (rowid=?)
Query:
SELECT member_status
FROM group_members
WHERE group_id = ? AND user_id = ?
Plan:
SEARCH group_members USING INDEX idx_group_members_group_id (user_id=? AND group_id=?)
Query:
SELECT r.file_id
FROM rcv_files r
@@ -5000,14 +4992,6 @@ Query:
Plan:
SEARCH protocol_servers USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE rcv_files
SET to_receive = 1, user_approved_relays = ?, updated_at = ?
WHERE file_id = ?
Plan:
SEARCH rcv_files USING INTEGER PRIMARY KEY (rowid=?)
Query:
UPDATE remote_controllers
SET ctrl_device_name = ?, dh_priv_key = ?, prev_dh_priv_key = dh_priv_key