mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-28 02:38:44 +00:00
Merge branch 'master' into ep/address-dr
This commit is contained in:
@@ -156,7 +156,7 @@ createActiveUser cc CoreChatOpts {chatRelay, headless} createBot_ userDisplayNam
|
||||
\It is only stored on your device and you can change it later."
|
||||
where
|
||||
loop = do
|
||||
displayName <- T.pack <$> withPrompt "display name" getLine
|
||||
displayName <- T.pack <$> withPrompt "display name: " getLine
|
||||
createUser loop False $ mkProfile displayName
|
||||
mkProfile displayName = Profile {displayName, fullName = "", shortDescr = Nothing, description = Nothing, image = Nothing, contactLink = Nothing, peerType = Nothing, preferences = Nothing, badge = Nothing, contactDomain = Nothing}
|
||||
createUser onError clientService p =
|
||||
|
||||
@@ -441,7 +441,6 @@ processChatCommand cxt nm = \case
|
||||
where
|
||||
createPresetContactCards :: DB.Connection -> User -> ExceptT StoreError IO ()
|
||||
createPresetContactCards db user = do
|
||||
createContact db cxt user simplexStatusContactProfile
|
||||
createContact db cxt user simplexTeamContactProfile
|
||||
chooseServers :: Maybe User -> CM ([UpdatedUserOperatorServers], (NonEmpty (ServerCfg 'PSMP), NonEmpty (ServerCfg 'PXFTP)))
|
||||
chooseServers user_ = do
|
||||
@@ -693,7 +692,8 @@ processChatCommand cxt nm = \case
|
||||
(SCTGroup, SMDSnd) -> L.nonEmpty <$> withFastStore' (`getGroupSndStatuses` itemId)
|
||||
_ -> pure Nothing
|
||||
forwardedFromChatItem <- getForwardedFromItem user ci
|
||||
pure $ CRChatItemInfo user aci ChatItemInfo {itemVersions, memberDeliveryStatuses, forwardedFromChatItem}
|
||||
fileXftpServers <- getChatItemFileServers user dir ci
|
||||
pure $ CRChatItemInfo user aci ChatItemInfo {itemVersions, memberDeliveryStatuses, forwardedFromChatItem, fileXftpServers}
|
||||
where
|
||||
getForwardedFromItem :: User -> ChatItem c d -> CM (Maybe AChatItem)
|
||||
getForwardedFromItem user ChatItem {meta = CIMeta {itemForwarded}} = case itemForwarded of
|
||||
@@ -2889,14 +2889,14 @@ processChatCommand cxt nm = \case
|
||||
-- TODO [relays] possible optimization is to read only required members + relays
|
||||
g@(Group gInfo members) <- withFastStore $ \db -> getGroup db cxt user groupId
|
||||
when (selfSelected gInfo) $ throwCmdError "can't change role for self"
|
||||
let (invitedMems, currentMems, unchangedMems, maxRole, anyAdmin, anyPending, anyPrivilegedTarget, anyRosterChange, finalPrivilegedCount) = selectMembers members
|
||||
let (invitedMems, currentMems, unchangedMems, maxRole, anyAdmin, anyPending, anyPrivilegedTarget, anyRelay, anyRosterChange, finalPrivilegedCount) = selectMembers members
|
||||
when (length invitedMems + length currentMems + length unchangedMems /= length memberIds) $ throwChatError CEGroupMemberNotFound
|
||||
when (length memberIds > 1 && (anyAdmin || newRole >= GRAdmin)) $
|
||||
throwCmdError "can't change role of multiple members when admins selected, or new role is admin"
|
||||
when anyPending $ throwCmdError "can't change role of members pending approval"
|
||||
-- TODO allow moderators (recipients already accept it; needs UI too): the observer..member limit is an
|
||||
-- TODO `all` over targets - maxRole can't express it, a max hides targets below GRObserver (relay,
|
||||
-- TODO unknown) that receivers reject. Fold roleRequiredToChange per target, or add allModeratable.
|
||||
when (anyRelay || newRole == GRRelay) $ throwCmdError "relay role can't be changed"
|
||||
-- TODO allow moderators (needs UI) - relay is rejected above (anyRelay), so drop the GRAdmin floor:
|
||||
-- TODO assertUserGroupRole gInfo (roleRequiredToChange maxRole newRole)
|
||||
assertUserGroupRole gInfo $ maximum ([GRAdmin, maxRole, newRole] :: [GroupMemberRole])
|
||||
-- in relay groups the roster has a single signer, so only the owner may change member/moderator/admin roles
|
||||
when (useRelays' gInfo && (isRosterRole newRole || anyPrivilegedTarget) && memberRole' (membership gInfo) /= GROwner) $
|
||||
@@ -2917,22 +2917,23 @@ processChatCommand cxt nm = \case
|
||||
-- anyPrivilegedTarget: a target currently member/moderator/admin (gates the owner-only check); anyRosterChange:
|
||||
-- a current member's role change that alters the roster blob - the only case that bumps the version, since a
|
||||
-- bump with no delta reads as a gap to subscribers; finalPrivilegedCount: moderators + admins after the change.
|
||||
selectMembers :: [GroupMember] -> ([GroupMember], [GroupMember], [GroupMember], GroupMemberRole, Bool, Bool, Bool, Bool, Int)
|
||||
selectMembers = foldr' addMember ([], [], [], GRObserver, False, False, False, False, 0)
|
||||
selectMembers :: [GroupMember] -> ([GroupMember], [GroupMember], [GroupMember], GroupMemberRole, Bool, Bool, Bool, Bool, Bool, Int)
|
||||
selectMembers = foldr' addMember ([], [], [], GRObserver, False, False, False, False, False, 0)
|
||||
where
|
||||
addMember m@GroupMember {groupMemberId, memberStatus, memberRole} (invited, current, unchanged, maxRole, anyAdmin, anyPending, anyPrivTarget, anyRosterChange, privCount)
|
||||
addMember m@GroupMember {groupMemberId, memberStatus, memberRole} (invited, current, unchanged, maxRole, anyAdmin, anyPending, anyPrivTarget, anyRelay, anyRosterChange, privCount)
|
||||
| groupMemberId `elem` memberIds =
|
||||
let maxRole' = max maxRole memberRole
|
||||
anyAdmin' = anyAdmin || memberRole >= GRAdmin
|
||||
anyPending' = anyPending || memberPending m
|
||||
anyPrivTarget' = anyPrivTarget || isRosterRole memberRole
|
||||
anyRelay' = anyRelay || memberRole == GRRelay
|
||||
privCount' = if isRosterRole newRole then privCount + 1 else privCount
|
||||
in if
|
||||
| memberRole == newRole -> (invited, current, m : unchanged, maxRole', anyAdmin', anyPending', anyPrivTarget', anyRosterChange, privCount')
|
||||
| memberStatus == GSMemInvited -> (m : invited, current, unchanged, maxRole', anyAdmin', anyPending', anyPrivTarget', anyRosterChange, privCount')
|
||||
| memberRole == newRole -> (invited, current, m : unchanged, maxRole', anyAdmin', anyPending', anyPrivTarget', anyRelay', anyRosterChange, privCount')
|
||||
| memberStatus == GSMemInvited -> (m : invited, current, unchanged, maxRole', anyAdmin', anyPending', anyPrivTarget', anyRelay', anyRosterChange, privCount')
|
||||
-- a current member's role actually changes here; it alters the roster iff the old or new role is on it
|
||||
| otherwise -> (invited, m : current, unchanged, maxRole', anyAdmin', anyPending', anyPrivTarget', anyRosterChange || isRosterRole newRole || isRosterRole memberRole, privCount')
|
||||
| otherwise = (invited, current, unchanged, maxRole, anyAdmin, anyPending, anyPrivTarget, anyRosterChange, if isRosterRole memberRole then privCount + 1 else privCount)
|
||||
| otherwise -> (invited, m : current, unchanged, maxRole', anyAdmin', anyPending', anyPrivTarget', anyRelay', anyRosterChange || isRosterRole newRole || isRosterRole memberRole, privCount')
|
||||
| otherwise = (invited, current, unchanged, maxRole, anyAdmin, anyPending, anyPrivTarget, anyRelay, anyRosterChange, if isRosterRole memberRole then privCount + 1 else privCount)
|
||||
changeRoleInvitedMems :: User -> GroupInfo -> [GroupMember] -> CM ([ChatError], [GroupMember])
|
||||
changeRoleInvitedMems user gInfo memsToChange = do
|
||||
-- not batched, as we need to send different invitations to different connections anyway
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3337,6 +3337,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
|
||||
xGrpMemRole :: GroupInfo -> Maybe GroupMember -> GroupMember -> MemberId -> GroupMemberRole -> Maybe MemberKey -> Maybe VersionRoster -> RcvMessage -> UTCTime -> CM (Maybe DeliveryJobScope)
|
||||
xGrpMemRole gInfo@GroupInfo {membership} fwdRelay_ m@GroupMember {memberRole = senderRole} memId memRole memberKey_ rosterVer_ msg@RcvMessage {msgSigned} brokerTs
|
||||
| memRole == GRRelay = messageError "x.grp.mem.role: relay role can't be assigned" $> Nothing
|
||||
| membershipMemId == memId =
|
||||
applyAtRosterVersion gInfo fwdRelay_ m rosterVer_ $
|
||||
let gInfo' = gInfo {membership = membership {memberRole = memRole}}
|
||||
@@ -3363,6 +3364,8 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
-- applyMember writes the change (role, or role + pinned key for a freshly TOFU-created member);
|
||||
-- the delivery scope (relay forwarding) is computed on the pre-change role
|
||||
changeMemberRole gInfo' member@GroupMember {memberRole = fromRole} created applyMember gEvent createItem
|
||||
| fromRole == GRRelay =
|
||||
messageError "x.grp.mem.role: relay role can't be changed" $> Nothing
|
||||
| senderRole < roleRequiredToChange fromRole memRole =
|
||||
messageError "x.grp.mem.role with insufficient member permissions" $> Nothing
|
||||
| useRelays' gInfo && (isRosterRole memRole || isRosterRole fromRole) && senderRole /= GROwner =
|
||||
@@ -3873,6 +3876,8 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
Just (author, unknown)
|
||||
| memberRemoved author ->
|
||||
logInfo $ "x.grp.msg.forward: ignoring content from removed member, group " <> tshow (groupId' gInfo) <> ", member " <> safeDecodeUtf8 (strEncode memberId) <> ", event " <> tshow (toCMEventTag chatMsgEvent)
|
||||
| not (useRelays' gInfo) && not (expectedForwarder author) ->
|
||||
logInfo $ "x.grp.msg.forward: ignoring content from unexpected forwarder, group " <> tshow (groupId' gInfo) <> ", forwarder " <> tshow (groupMemberId' m) <> ", member " <> safeDecodeUtf8 (strEncode memberId) <> ", event " <> tshow (toCMEventTag chatMsgEvent)
|
||||
| otherwise -> do
|
||||
when unknown $ toView $ CEvtUnknownMemberCreated user gInfo m author
|
||||
void $ withVerifiedMsg gInfo scopeInfo author parsedMsg msgTs $
|
||||
@@ -3880,6 +3885,13 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
Nothing -> pure ()
|
||||
FwdChannel -> processForwardedMsg (VMUnsigned chatMsg) Nothing
|
||||
where
|
||||
-- Forwards are only expected from the member that introduced us to the author: our host, or
|
||||
-- the author's inviter. Unknown members have no such record, so any admin may forward theirs.
|
||||
expectedForwarder :: GroupMember -> Bool
|
||||
expectedForwarder author =
|
||||
memberCategory m == GCHostMember
|
||||
|| invitedByGroupMemberId author == Just (groupMemberId' m)
|
||||
|| memberStatus author == GSMemUnknown
|
||||
-- ! see isForwardedGroupMsg: forwarded group events should include msgId to be deduplicated
|
||||
processForwardedMsg :: VerifiedMsg 'Json -> Maybe GroupMember -> CM ()
|
||||
processForwardedMsg verifiedMsg author_ = do
|
||||
|
||||
@@ -55,7 +55,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (BlockingInfo, MsgBody)
|
||||
import Simplex.Messaging.Protocol (BlockingInfo, MsgBody, XFTPServer)
|
||||
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
|
||||
|
||||
data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection
|
||||
@@ -1345,7 +1345,8 @@ instance TextEncoding CIForwardedFromTag where
|
||||
data ChatItemInfo = ChatItemInfo
|
||||
{ itemVersions :: [ChatItemVersion],
|
||||
memberDeliveryStatuses :: Maybe (NonEmpty MemberDeliveryStatus),
|
||||
forwardedFromChatItem :: Maybe AChatItem
|
||||
forwardedFromChatItem :: Maybe AChatItem,
|
||||
fileXftpServers :: [XFTPServer]
|
||||
}
|
||||
deriving (Show)
|
||||
|
||||
|
||||
@@ -815,6 +815,13 @@ isVoice = \case
|
||||
MCVoice {} -> True
|
||||
_ -> False
|
||||
|
||||
isMedia :: MsgContent -> Bool
|
||||
isMedia = \case
|
||||
MCImage {} -> True
|
||||
MCVideo {} -> True
|
||||
MCFile {} -> True
|
||||
_ -> False
|
||||
|
||||
isReport :: MsgContent -> Bool
|
||||
isReport = \case
|
||||
MCReport {} -> True
|
||||
|
||||
@@ -20,6 +20,7 @@ module Simplex.Chat.Store.Files
|
||||
createSndFileTransferXFTP,
|
||||
createSndFTDescrXFTP,
|
||||
setSndFTPrivateSndDescr,
|
||||
getSndFTPrivateSndDescr,
|
||||
updateSndFTDescrXFTP,
|
||||
createExtraSndFTDescrs,
|
||||
updateSndFTDeliveryXFTP,
|
||||
@@ -210,6 +211,15 @@ setSndFTPrivateSndDescr db User {userId} fileId sfdText = do
|
||||
"UPDATE files SET private_snd_file_descr = ?, updated_at = ? WHERE user_id = ? AND file_id = ?"
|
||||
(sfdText, currentTs, userId, fileId)
|
||||
|
||||
getSndFTPrivateSndDescr :: DB.Connection -> User -> FileTransferId -> IO (Maybe Text)
|
||||
getSndFTPrivateSndDescr db User {userId} fileId =
|
||||
fmap (maybe Nothing fromOnly) $
|
||||
maybeFirstRow id $
|
||||
DB.query
|
||||
db
|
||||
"SELECT private_snd_file_descr FROM files WHERE user_id = ? AND file_id = ?"
|
||||
(userId, fileId)
|
||||
|
||||
updateSndFTDescrXFTP :: DB.Connection -> User -> SndFileTransfer -> Text -> IO ()
|
||||
updateSndFTDescrXFTP db user@User {userId} sft@SndFileTransfer {fileId, fileDescrId} rfdText = do
|
||||
currentTs <- getCurrentTime
|
||||
|
||||
@@ -2093,6 +2093,7 @@ updateGroupChatItemsRead db User {userId} GroupInfo {groupId} = do
|
||||
[sql|
|
||||
UPDATE chat_items SET item_status = ?, item_viewed = 1, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL
|
||||
AND item_status = ?
|
||||
|]
|
||||
(CISRcvRead, currentTs, userId, groupId, CISRcvNew)
|
||||
@@ -2149,6 +2150,7 @@ getGroupUnreadTimedItems db User {userId} groupId scope =
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL
|
||||
AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|]
|
||||
(userId, groupId, CISRcvNew)
|
||||
|
||||
@@ -1586,10 +1586,11 @@ Query:
|
||||
SELECT chat_item_id, timed_ttl
|
||||
FROM chat_items
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL
|
||||
AND item_status = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?)
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_scope_stats_all (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
SELECT chat_item_moderation_id, moderator_member_id, created_by_msg_id, moderated_at
|
||||
@@ -5042,10 +5043,11 @@ SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
|
||||
Query:
|
||||
UPDATE chat_items SET item_status = ?, item_viewed = 1, updated_at = ?
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL
|
||||
AND item_status = ?
|
||||
|
||||
Plan:
|
||||
SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?)
|
||||
SEARCH chat_items USING INDEX idx_chat_items_group_scope_stats_all (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
|
||||
|
||||
Query:
|
||||
UPDATE chat_items SET item_status = ?, item_viewed = 1, updated_at = ?
|
||||
|
||||
Reference in New Issue
Block a user