core: include channel link and name when forwarding messages (#7409)

* core: include channel link and name when forwarding messages

* wip

* simplify

* add member ID

* refactor

* refactor

* refactor

* update api types

* store forward source group type

* rename

* api types

* simpler layout

* layout, translations

* refactor ios

* public

* simpler

* refactor kotlin

* padding

* padding

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
EvgenyandEvgeny @ SimpleX Chat authored and GitHub committed 2026-08-24 21:36:57 +01:00
1 parent 1196d362ee
commit 17cdef1692
65 files changed
+806 -162

No files matched your search

+10 -4
View File
@@ -711,7 +711,7 @@ processChatCommand cxt nm = \case
getForwardedFromItem user ChatItem {meta = CIMeta {itemForwarded}} = case itemForwarded of
Just (CIFFContact _ _ (Just ctId) (Just fwdItemId)) ->
Just <$> withFastStore (\db -> getAChatItem db cxt user (ChatRef CTDirect ctId Nothing) fwdItemId)
Just (CIFFGroup _ _ (Just gId) (Just fwdItemId)) ->
Just (CIFFGroup _ _ (Just gId) (Just fwdItemId) _ _ _) ->
-- TODO [knocking] getAChatItem doesn't differentiate how to read based on scope - it should, instead of using group filter
Just <$> withFastStore (\db -> getAChatItem db cxt user (ChatRef CTGroup gId Nothing) fwdItemId)
_ -> pure Nothing
@@ -1096,9 +1096,11 @@ processChatCommand cxt nm = \case
catMaybes <$> mapM (\ci -> ciComposeMsgReq gInfo ci <$$> prepareMsgReq ci) items
where
ciComposeMsgReq :: GroupInfo -> CChatItem 'CTGroup -> (MsgContent, Maybe CryptoFile) -> ComposedMessageReq
ciComposeMsgReq gInfo (CChatItem md ci@ChatItem {mentions, formattedText}) (mc, file) = do
ciComposeMsgReq gInfo (CChatItem md ci@ChatItem {mentions, formattedText, meta = CIMeta {itemSharedMsgId}}) (mc, file) = do
let itemId = chatItemId' ci
ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) (toMsgDirection md) (Just fromChatId) (Just itemId))
fwdMemberId = memberId' <$> chatItemMember gInfo ci
fwdGroupType = itemSharedMsgId *> sourceGroupType gInfo
ciff = forwardCIFF ci $ Just (CIFFGroup (forwardName gInfo) (toMsgDirection md) (Just fromChatId) (Just itemId) fwdMemberId itemSharedMsgId fwdGroupType)
-- updates text to reflect current mentioned member names
(mc', _, mentions') = updatedMentionNames mc formattedText mentions
-- only includes mentions when forwarding to the same group
@@ -1108,6 +1110,8 @@ processChatCommand cxt nm = \case
where
forwardName :: GroupInfo -> ContactName
forwardName GroupInfo {groupProfile = GroupProfile {displayName}} = displayName
sourceGroupType :: GroupInfo -> Maybe GroupType
sourceGroupType GroupInfo {groupProfile = GroupProfile {publicGroup}} = (\PublicGroupProfile {groupType} -> groupType) <$> publicGroup
CTLocal -> do
(_, items) <- getCommandLocalChatItems user fromChatId itemIds
catMaybes <$> mapM (\ci -> ciComposeMsgReq ci <$$> prepareMsgReq ci) items
@@ -4769,7 +4773,9 @@ processChatCommand cxt nm = \case
forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded, _, _), fInv_) -> do
(mc', quotedItem_) <- case (quotedItemId, itemForwarded) of
(Nothing, Nothing) -> pure (mcSimple mc, Nothing)
(Nothing, Just _) -> pure (mcForward mc, Nothing)
(Nothing, Just ciff) -> do
fl_ <- liftIO $ ciffForwardLink db ciff
pure (mcForward fl_ mc, Nothing)
(Just qiId, Nothing) -> do
CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, file} <-
getDirectChatItem db user contactId qiId
+55 -2
View File
@@ -208,7 +208,9 @@ prepareGroupMsg :: DB.Connection -> User -> GroupInfo -> Maybe MsgScope -> ShowG
prepareGroupMsg db user g@GroupInfo {membership} msgScope showGroupAsSender mc mentions quotedItemId_ itemForwarded fInv_ timed_ live = do
(mc', quotedItem_) <- case (quotedItemId_, itemForwarded) of
(Nothing, Nothing) -> pure (mcSimple mc, Nothing)
(Nothing, Just _) -> pure (mcForward mc, Nothing)
(Nothing, Just ciff) -> do
fl_ <- liftIO $ ciffForwardLink db ciff
pure (mcForward fl_ mc, Nothing)
(Just quotedItemId, Nothing) -> do
CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, mentions = quoteMentions, file} <-
getGroupCIWithReactions db user g quotedItemId
@@ -231,6 +233,56 @@ prepareGroupMsg db user g@GroupInfo {membership} msgScope showGroupAsSender mc m
quoteData ChatItem {chatDir = CIChannelRcv, content = CIRcvMsgContent qmc} _ = pure (qmc, CIQGroupRcv Nothing, False, Nothing)
quoteData _ _ = throwError SEInvalidQuote
-- re-forwarded message is attributed to the original source
ciffForwardLink :: DB.Connection -> CIForwardedFrom -> IO (Maybe ForwardLink)
ciffForwardLink db = \case
CIFFGroup {groupId = Just gId, memberId, sharedMsgId_ = Just msgId} ->
getGroupProfileById db gId >>= \case
Just GroupProfile {displayName, publicGroup = Just PublicGroupProfile {groupLink, publicGroupId}} ->
pure $ Just ForwardLink {displayName, groupLink, publicGroupId, memberId, msgId}
_ -> pure Nothing
CIFFGroupLink {chatName, groupLink, publicGroupId, memberId, sharedMsgId} ->
pure $ Just ForwardLink {displayName = chatName, groupLink, publicGroupId, memberId, msgId = sharedMsgId}
_ -> pure Nothing
rcvForwardedFrom :: DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> IO (Maybe CIForwardedFrom)
rcvForwardedFrom db user chatDirection RcvMessage {chatMsgEvent} = case chatMsgEvent of
ACME _ (XMsgNew MsgContainer {forward = Just True, forwardLink}) -> case forwardLink of
Nothing -> pure $ Just CIFFUnknown
Just fl@ForwardLink {displayName}
| linkAllowed -> Just <$> forwardLinkCIFF db user fl
| otherwise -> pure $ Just $ CIFFGroup displayName MDRcv Nothing Nothing Nothing Nothing Nothing
_ -> pure Nothing
where
linkAllowed = case chatDirection of
CDGroupRcv gInfo _ GroupMember {memberRole} -> allowed memberRole gInfo
CDChannelRcv gInfo _ -> allowed GROwner gInfo
_ -> True
where
allowed role = groupFeatureMemberAllowed' SGFSimplexLinks role . fullGroupPreferences
forwardLinkCIFF :: DB.Connection -> User -> ForwardLink -> IO CIForwardedFrom
forwardLinkCIFF db user ForwardLink {displayName, groupLink, publicGroupId, memberId, msgId} =
getGroupViaPublicGroupId db user publicGroupId >>= \case
Just (gId, Just storedLink)
| sameShortLinkContact groupLink storedLink -> do
ciId_ <- itemId_ gId
pure $ CIFFGroup displayName MDRcv (Just gId) ciId_ memberId (Just msgId) linkGroupType
_ -> pure $ CIFFGroupLink displayName MDRcv groupLink publicGroupId memberId msgId linkGroupType
where
linkGroupType = case groupLink of
CSLContact _ CCTChannel _ _ -> Just GTChannel
CSLContact _ CCTGroup _ _ -> Just GTGroup
_ -> Nothing
itemId_ gId = case memberId of
Nothing -> getGroupChatItemBySharedMsgId_ db user gId Nothing msgId
Just mId ->
getGroupMemberViaMemberId_ db user gId mId >>= \case
Just (gmId, category) ->
let scope = if category == GCUserMember then Nothing else Just gmId
in getGroupChatItemBySharedMsgId_ db user gId scope msgId
Nothing -> pure Nothing
updatedMentionNames :: MsgContent -> Maybe MarkdownList -> Map MemberName CIMention -> (MsgContent, Maybe MarkdownList, Map MemberName CIMention)
updatedMentionNames mc ft_ mentions = case ft_ of
Just ft
@@ -2767,7 +2819,8 @@ saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, msgSigned, forwardedByMem
else pure $ toChatInfo cd
let showAsGroup = case cd of CDChannelRcv {} -> True; _ -> False
hasLink_ = ciContentHasLink content ft_
(ciId, quotedItem, itemForwarded) <- createNewRcvChatItem db user cd msg sharedMsgId_ content itemTimed live userMention hasLink_ brokerTs createdAt
itemForwarded <- rcvForwardedFrom db user cd msg
(ciId, quotedItem) <- createNewRcvChatItem db user cd msg sharedMsgId_ content itemForwarded itemTimed live userMention hasLink_ brokerTs createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
let ci = mkChatItem_ cd showAsGroup ciId content (t, ft_) ciFile quotedItem sharedMsgId_ itemForwarded itemTimed live userMention hasLink_ brokerTs forwardedByMember (toMsgVerified (signMessagesRequired cd) msgSigned) createdAt
ci' <- case toChatInfo cd of
+5 -1
View File
@@ -1319,13 +1319,15 @@ itemDeletedTs = \case
data CIForwardedFrom
= CIFFUnknown
| CIFFContact {chatName :: Text, msgDir :: MsgDirection, contactId :: Maybe ContactId, chatItemId :: Maybe ChatItemId}
| CIFFGroup {chatName :: Text, msgDir :: MsgDirection, groupId :: Maybe GroupId, chatItemId :: Maybe ChatItemId}
| CIFFGroup {chatName :: Text, msgDir :: MsgDirection, groupId :: Maybe GroupId, chatItemId :: Maybe ChatItemId, memberId :: Maybe MemberId, sharedMsgId_ :: Maybe SharedMsgId, groupType :: Maybe GroupType}
| CIFFGroupLink {chatName :: Text, msgDir :: MsgDirection, groupLink :: ShortLinkContact, publicGroupId :: B64UrlByteString, memberId :: Maybe MemberId, sharedMsgId :: SharedMsgId, groupType :: Maybe GroupType}
deriving (Show)
data CIForwardedFromTag
= CIFFUnknown_
| CIFFContact_
| CIFFGroup_
| CIFFGroupLink_
instance FromField CIForwardedFromTag where fromField = fromTextField_ textDecode
@@ -1336,11 +1338,13 @@ instance TextEncoding CIForwardedFromTag where
"unknown" -> Just CIFFUnknown_
"contact" -> Just CIFFContact_
"group" -> Just CIFFGroup_
"groupLink" -> Just CIFFGroupLink_
_ -> Nothing
textEncode = \case
CIFFUnknown_ -> "unknown"
CIFFContact_ -> "contact"
CIFFGroup_ -> "group"
CIFFGroupLink_ -> "groupLink"
data ChatItemInfo = ChatItemInfo
{ itemVersions :: [ChatItemVersion],
+17 -4
View File
@@ -688,7 +688,17 @@ data MsgContainer = MsgContainer
asGroup :: Maybe Bool,
quote :: Maybe QuotedMsg,
parent :: Maybe MsgRef,
forward :: Maybe Bool
forward :: Maybe Bool,
forwardLink :: Maybe ForwardLink
}
deriving (Eq, Show)
data ForwardLink = ForwardLink
{ displayName :: Text,
groupLink :: ShortLinkContact,
publicGroupId :: B64UrlByteString,
memberId :: Maybe MemberId,
msgId :: SharedMsgId
}
deriving (Eq, Show)
@@ -704,7 +714,8 @@ mcSimple content =
asGroup = Nothing,
quote = Nothing,
parent = Nothing,
forward = Nothing
forward = Nothing,
forwardLink = Nothing
}
mcQuote :: QuotedMsg -> MsgContent -> MsgContainer
@@ -713,8 +724,8 @@ mcQuote q c = (mcSimple c) {quote = Just q}
mcComment :: MsgRef -> MsgContent -> MsgContainer
mcComment p c = (mcSimple c) {parent = Just p}
mcForward :: MsgContent -> MsgContainer
mcForward c = (mcSimple c) {forward = Just True}
mcForward :: Maybe ForwardLink -> MsgContent -> MsgContainer
mcForward fl c = (mcSimple c) {forward = Just True, forwardLink = fl}
data MsgContent
= MCText {text :: Text}
@@ -896,6 +907,8 @@ instance ToJSON MsgContent where
MCReport {text, reason} -> J.pairs $ "type" .= MCReport_ <> "text" .= text <> "reason" .= reason
MCChat {text, chatLink, ownerSig} -> J.pairs $ "type" .= MCChat_ <> "text" .= text <> "chatLink" .= chatLink <> maybe mempty ("ownerSig" .=) ownerSig
$(JQ.deriveJSON defaultJSON ''ForwardLink)
$(JQ.deriveJSON defaultJSON ''MsgContainer)
-- this limit reserves space for metadata in forwarded messages
+43 -19
View File
@@ -63,6 +63,7 @@ module Simplex.Chat.Store.Groups
getGroupMemberByMemberId,
getCreateUnknownGMByMemberId,
getGroupMemberIdViaMemberId,
getGroupMemberViaMemberId_,
getScopeMemberIdViaMemberId,
getGroupMembers,
getGroupMembersByIndexes,
@@ -128,6 +129,8 @@ module Simplex.Chat.Store.Groups
getRelayServedGroups,
getRelayPublishableGroups,
getRelayInactiveGroups,
getGroupViaPublicGroupId,
getGroupProfileById,
createJoiningMember,
getMemberJoinRequest,
createJoiningMemberConnection,
@@ -1212,11 +1215,16 @@ getScopeMemberIdViaMemberId db user g@GroupInfo {membership} sender scopeMemberI
| otherwise = getGroupMemberIdViaMemberId db user g scopeMemberId
getGroupMemberIdViaMemberId :: DB.Connection -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMemberId
getGroupMemberIdViaMemberId db User {userId} GroupInfo {groupId} memberId =
ExceptT . firstRow fromOnly (SEGroupMemberNotFoundByMemberId memberId) $
getGroupMemberIdViaMemberId db user GroupInfo {groupId} memberId = do
m_ <- liftIO $ getGroupMemberViaMemberId_ db user groupId memberId
maybe (throwError $ SEGroupMemberNotFoundByMemberId memberId) (pure . fst) m_
getGroupMemberViaMemberId_ :: DB.Connection -> User -> GroupId -> MemberId -> IO (Maybe (GroupMemberId, GroupMemberCategory))
getGroupMemberViaMemberId_ db User {userId} groupId memberId =
maybeFirstRow id $
DB.query
db
"SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND member_id = ?"
"SELECT group_member_id, member_category FROM group_members WHERE user_id = ? AND group_id = ? AND member_id = ?"
(userId, groupId, memberId)
getGroupMembers :: DB.Connection -> StoreCxt -> User -> GroupInfo -> IO [GroupMember]
@@ -2018,6 +2026,20 @@ getRelayPublishableGroups db User {userId, userContactId} =
where
toRow ((gId, pgId) :. accessRow) = (gId, pgId, toPublicGroupAccess accessRow)
getGroupViaPublicGroupId :: DB.Connection -> User -> B64UrlByteString -> IO (Maybe (GroupId, Maybe ShortLinkContact))
getGroupViaPublicGroupId db User {userId} publicGroupId =
maybeFirstRow id $
DB.query
db
[sql|
SELECT g.group_id, gp.group_link
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
WHERE g.user_id = ? AND gp.public_group_id = ?
LIMIT 1
|]
(userId, publicGroupId)
getRelayInactiveGroups :: DB.Connection -> StoreCxt -> User -> NominalDiffTime -> IO [GroupInfo]
getRelayInactiveGroups db cxt User {userId, userContactId} ttl = do
currentTs <- getCurrentTime
@@ -2759,26 +2781,28 @@ updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p}
updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo
updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, shortDescr = sd, description = descr, image = img} = do
p <- getGroupProfile -- to avoid any race conditions with UI
p_ <- liftIO $ getGroupProfileById db groupId -- to avoid any race conditions with UI
p <- maybe (throwError $ SEGroupNotFound groupId) pure p_
let g' = g {groupProfile = p} :: GroupInfo
p' = p {displayName = n, fullName = fn, shortDescr = sd, description = descr, image = img} :: GroupProfile
updateGroupProfile db user g' p'
getGroupProfileById :: DB.Connection -> GroupId -> IO (Maybe GroupProfile)
getGroupProfileById db groupId =
maybeFirstRow toGroupProfile $
DB.query
db
[sql|
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image,
gp.group_type, gp.group_link, gp.public_group_id,
gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof,
gp.preferences, gp.member_admission
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
|]
(Only groupId)
where
getGroupProfile =
ExceptT $
firstRow toGroupProfile (SEGroupNotFound groupId) $
DB.query
db
[sql|
SELECT gp.display_name, gp.full_name, gp.short_descr, gp.description, gp.image,
gp.group_type, gp.group_link, gp.public_group_id,
gp.group_web_page, gp.group_domain, gp.domain_web_page, gp.allow_embedding, gp.group_domain_proof,
gp.preferences, gp.member_admission
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
|]
(Only groupId)
toGroupProfile ((displayName, fullName, shortDescr, description, image, groupType_, groupLink_, publicGroupId_) :. accessRow :. (groupPreferences, memberAdmission)) =
let publicGroupAccess = toPublicGroupAccess accessRow
in GroupProfile {displayName, fullName, shortDescr, description, image, publicGroup = toPublicGroupProfile groupType_ groupLink_ publicGroupId_ publicGroupAccess, groupPreferences, memberAdmission}
+46 -31
View File
@@ -100,6 +100,7 @@ module Simplex.Chat.Store.Messages
getDirectChatItem,
getDirectCIWithReactions,
getDirectChatItemBySharedMsgId,
getGroupChatItemBySharedMsgId_,
getDirectChatItemsByAgentMsgId,
getGroupChatItem,
getGroupCIWithReactions,
@@ -560,16 +561,13 @@ createNewSndChatItem db user chatDirection showGroupAsSender SndMessage {msgId,
CIQGroupRcv (Just GroupMember {memberId}) -> (Just False, Just memberId)
CIQGroupRcv Nothing -> (Just False, Nothing)
createNewRcvChatItem :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> Bool -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c), Maybe CIForwardedFrom)
createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent, msgSigned, signedMsg_, signedByGMId_, forwardedByMember} sharedMsgId_ ciContent timed live userMention hasLink itemTs createdAt = do
createNewRcvChatItem :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c))
createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent, msgSigned, signedMsg_, signedByGMId_, forwardedByMember} sharedMsgId_ ciContent itemForwarded timed live userMention hasLink itemTs createdAt = do
let showAsGroup = case chatDirection of CDChannelRcv {} -> True; _ -> False
ciId <- createNewChatItem_ db user chatDirection showAsGroup (Just msgId) sharedMsgId_ ciContent quoteRow itemForwarded timed live userMention hasLink itemTs forwardedByMember (toMsgVerified (signMessagesRequired chatDirection) msgSigned) signedMsg_ signedByGMId_ createdAt
quotedItem <- mapM (getChatItemQuote_ db user chatDirection) quotedMsg
pure (ciId, quotedItem, itemForwarded)
pure (ciId, quotedItem)
where
itemForwarded = case chatMsgEvent of
ACME _ (XMsgNew MsgContainer {forward}) | forward == Just True -> Just CIFFUnknown
_ -> Nothing
quotedMsg = cmToQuotedMsg chatMsgEvent
quoteRow :: NewQuoteRow
quoteRow = case quotedMsg of
@@ -603,8 +601,9 @@ createNewChatItem_ db User {userId} chatDirection showGroupAsSender msgId_ share
-- quote
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id,
-- forwarded from
fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id,
fwd_from_group_type, fwd_from_group_link, fwd_from_public_group_id, fwd_from_member_id, fwd_from_shared_msg_id
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
((userId, msgId_) :. idsRow :. groupScopeRow :. itemRow :. quoteRow' :. forwardedFromRow)
ciId <- insertedRowId db
@@ -648,16 +647,20 @@ createNewChatItem_ db User {userId} chatDirection showGroupAsSender msgId_ share
SMDSnd -> isJust mcTag_
SMDRcv -> False
mcTag_ = msgContentTag <$> ciMsgContent ciContent
forwardedFromRow :: (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64)
forwardedFromRow :: ChatItemForwardedFromRow
forwardedFromRow = case itemForwarded of
Nothing ->
(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing)
(Nothing, Nothing, Nothing, Nothing, Nothing, Nothing) :. noLinkRow
Just CIFFUnknown ->
(Just CIFFUnknown_, Nothing, Nothing, Nothing, Nothing, Nothing)
(Just CIFFUnknown_, Nothing, Nothing, Nothing, Nothing, Nothing) :. noLinkRow
Just CIFFContact {chatName, msgDir, contactId, chatItemId} ->
(Just CIFFContact_, Just chatName, Just msgDir, contactId, Nothing, chatItemId)
Just CIFFGroup {chatName, msgDir, groupId, chatItemId} ->
(Just CIFFGroup_, Just chatName, Just msgDir, Nothing, groupId, chatItemId)
(Just CIFFContact_, Just chatName, Just msgDir, contactId, Nothing, chatItemId) :. noLinkRow
Just CIFFGroup {chatName, msgDir, groupId, chatItemId, memberId, sharedMsgId_, groupType} ->
(Just CIFFGroup_, Just chatName, Just msgDir, Nothing, groupId, chatItemId) :. (groupType, Nothing, Nothing, memberId, sharedMsgId_)
Just CIFFGroupLink {chatName, msgDir, groupLink, publicGroupId, memberId, sharedMsgId = fwdSharedMsgId, groupType} ->
(Just CIFFGroupLink_, Just chatName, Just msgDir, Nothing, Nothing, Nothing) :. (groupType, Just groupLink, Just publicGroupId, memberId, Just fwdSharedMsgId)
noLinkRow :: ChatItemForwardedLinkRow
noLinkRow = (Nothing, Nothing, Nothing, Nothing, Nothing)
ciTimedRow :: Maybe CITimed -> (Maybe Int, Maybe UTCTime)
ciTimedRow (Just CITimed {ttl, deleteAt}) = (Just ttl, deleteAt)
@@ -2277,7 +2280,9 @@ type MaybeCIFIleRow = (Maybe Int64, Maybe String, Maybe Integer, Maybe FilePath,
type ChatItemModeRow = (Maybe Int, Maybe UTCTime, Maybe BoolInt, BoolInt, BoolInt, Maybe MsgVerified)
type ChatItemForwardedFromRow = (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64)
type ChatItemForwardedFromRow = (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64) :. ChatItemForwardedLinkRow
type ChatItemForwardedLinkRow = (Maybe GroupType, Maybe ShortLinkContact, Maybe B64UrlByteString, Maybe MemberId, Maybe SharedMsgId)
type ChatItemRow =
(Int64, ChatItemTs, AMsgDirection, Text, Text, ACIStatus, Maybe BoolInt, Maybe SharedMsgId)
@@ -2337,11 +2342,14 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT
ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
toCIForwardedFrom :: ChatItemForwardedFromRow -> Maybe CIForwardedFrom
toCIForwardedFrom (fwdFromTag, fwdFromChatName, fwdFromMsgDir, fwdFromContactId, fwdFromGroupId, fwdFromChatItemId) =
case (fwdFromTag, fwdFromChatName, fwdFromMsgDir, fwdFromContactId, fwdFromGroupId, fwdFromChatItemId) of
toCIForwardedFrom (fwdFromRow :. (groupType_, groupLink_, publicGroupId_, memberId_, sharedMsgId_)) =
case fwdFromRow of
(Just CIFFUnknown_, Nothing, Nothing, Nothing, Nothing, Nothing) -> Just CIFFUnknown
(Just CIFFContact_, Just chatName, Just msgDir, contactId, Nothing, ciId) -> Just $ CIFFContact chatName msgDir contactId ciId
(Just CIFFGroup_, Just chatName, Just msgDir, Nothing, groupId, ciId) -> Just $ CIFFGroup chatName msgDir groupId ciId
(Just CIFFGroup_, Just chatName, Just msgDir, Nothing, groupId, ciId) -> Just $ CIFFGroup chatName msgDir groupId ciId memberId_ sharedMsgId_ groupType_
(Just CIFFGroupLink_, Just chatName, Just msgDir, Nothing, Nothing, Nothing)
| Just groupLink <- groupLink_, Just publicGroupId <- publicGroupId_, Just sharedMsgId <- sharedMsgId_ ->
Just $ CIFFGroupLink chatName msgDir groupLink publicGroupId memberId_ sharedMsgId groupType_
_ -> Nothing
type GroupQuoteRow = QuoteRow :. MaybeGroupMemberRow
@@ -2694,6 +2702,7 @@ getDirectChatItem db User {userId} contactId itemId = ExceptT $ do
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.via_proxy, i.shared_msg_id,
i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at,
i.fwd_from_tag, i.fwd_from_chat_name, i.fwd_from_msg_dir, i.fwd_from_contact_id, i.fwd_from_group_id, i.fwd_from_chat_item_id,
i.fwd_from_group_type, i.fwd_from_group_link, i.fwd_from_public_group_id, i.fwd_from_member_id, i.fwd_from_shared_msg_id,
i.timed_ttl, i.timed_delete_at, i.item_live, i.user_mention, i.has_link, i.msg_signed,
-- CIFile
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
@@ -3030,21 +3039,25 @@ markReceivedGroupReportsDeleted db User {userId} GroupInfo {groupId, membership}
(DBCIDeleted, deletedTs, groupMemberId' membership, currentTs, userId, groupId, MCReport_, DBCINotDeleted)
getGroupChatItemBySharedMsgId :: DB.Connection -> User -> GroupInfo -> Maybe GroupMemberId -> SharedMsgId -> ExceptT StoreError IO (CChatItem 'CTGroup)
getGroupChatItemBySharedMsgId db user@User {userId} g@GroupInfo {groupId} groupMemberId_ sharedMsgId = do
itemId <-
ExceptT . firstRow fromOnly (SEChatItemSharedMsgIdNotFound sharedMsgId) $
DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND group_member_id IS NOT DISTINCT FROM ? AND shared_msg_id = ?
ORDER BY chat_item_id DESC
LIMIT 1
|]
(userId, groupId, groupMemberId_, sharedMsgId)
getGroupChatItemBySharedMsgId db user g@GroupInfo {groupId} groupMemberId_ sharedMsgId = do
itemId_ <- liftIO $ getGroupChatItemBySharedMsgId_ db user groupId groupMemberId_ sharedMsgId
itemId <- maybe (throwError $ SEChatItemSharedMsgIdNotFound sharedMsgId) pure itemId_
getGroupCIWithReactions db user g itemId
getGroupChatItemBySharedMsgId_ :: DB.Connection -> User -> GroupId -> Maybe GroupMemberId -> SharedMsgId -> IO (Maybe ChatItemId)
getGroupChatItemBySharedMsgId_ db User {userId} groupId groupMemberId_ sharedMsgId =
maybeFirstRow fromOnly $
DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ? AND group_member_id IS NOT DISTINCT FROM ? AND shared_msg_id = ?
ORDER BY chat_item_id DESC
LIMIT 1
|]
(userId, groupId, groupMemberId_, sharedMsgId)
getGroupMemberCIBySharedMsgId :: DB.Connection -> User -> GroupInfo -> MemberId -> SharedMsgId -> ExceptT StoreError IO (CChatItem 'CTGroup)
getGroupMemberCIBySharedMsgId db user@User {userId} g@GroupInfo {groupId} memberId sharedMsgId = do
itemId <-
@@ -3083,6 +3096,7 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.via_proxy, i.shared_msg_id,
i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at,
i.fwd_from_tag, i.fwd_from_chat_name, i.fwd_from_msg_dir, i.fwd_from_contact_id, i.fwd_from_group_id, i.fwd_from_chat_item_id,
i.fwd_from_group_type, i.fwd_from_group_link, i.fwd_from_public_group_id, i.fwd_from_member_id, i.fwd_from_shared_msg_id,
i.timed_ttl, i.timed_delete_at, i.item_live, i.user_mention, i.has_link, i.msg_signed,
-- CIFile
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol,
@@ -3195,6 +3209,7 @@ getLocalChatItem db User {userId} folderId itemId = ExceptT $ do
i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.via_proxy, i.shared_msg_id,
i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at,
i.fwd_from_tag, i.fwd_from_chat_name, i.fwd_from_msg_dir, i.fwd_from_contact_id, i.fwd_from_group_id, i.fwd_from_chat_item_id,
i.fwd_from_group_type, i.fwd_from_group_link, i.fwd_from_public_group_id, i.fwd_from_member_id, i.fwd_from_shared_msg_id,
i.timed_ttl, i.timed_delete_at, i.item_live, i.user_mention, i.has_link, i.msg_signed,
-- CIFile
f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol
@@ -47,6 +47,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260716_signed_history
import Simplex.Chat.Store.Postgres.Migrations.M20260720_server_roles
import Simplex.Chat.Store.Postgres.Migrations.M20260723_contact_request_rejection
import Simplex.Chat.Store.Postgres.Migrations.M20260813_auto_accept_group_invitations
import Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Text, Maybe Text)]
@@ -93,7 +94,8 @@ schemaMigrations =
("20260716_signed_history", m20260716_signed_history, Just down_m20260716_signed_history),
("20260720_server_roles", m20260720_server_roles, Just down_m20260720_server_roles),
("20260723_contact_request_rejection", m20260723_contact_request_rejection, Just down_m20260723_contact_request_rejection),
("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations)
("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations),
("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,27 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link where
import Data.Text (Text)
import Text.RawString.QQ (r)
m20260822_forward_link :: Text
m20260822_forward_link =
[r|
ALTER TABLE chat_items ADD COLUMN fwd_from_group_type TEXT;
ALTER TABLE chat_items ADD COLUMN fwd_from_group_link BYTEA;
ALTER TABLE chat_items ADD COLUMN fwd_from_public_group_id BYTEA;
ALTER TABLE chat_items ADD COLUMN fwd_from_member_id BYTEA;
ALTER TABLE chat_items ADD COLUMN fwd_from_shared_msg_id BYTEA;
|]
down_m20260822_forward_link :: Text
down_m20260822_forward_link =
[r|
ALTER TABLE chat_items DROP COLUMN fwd_from_group_type;
ALTER TABLE chat_items DROP COLUMN fwd_from_group_link;
ALTER TABLE chat_items DROP COLUMN fwd_from_public_group_id;
ALTER TABLE chat_items DROP COLUMN fwd_from_member_id;
ALTER TABLE chat_items DROP COLUMN fwd_from_shared_msg_id;
|]
@@ -349,7 +349,12 @@ CREATE TABLE test_chat_schema.chat_items (
item_msg_body bytea,
item_chat_binding text,
item_signatures bytea,
item_signed_by_group_member_id bigint
item_signed_by_group_member_id bigint,
fwd_from_group_type text,
fwd_from_group_link bytea,
fwd_from_public_group_id bytea,
fwd_from_member_id bytea,
fwd_from_shared_msg_id bytea
);
+3 -1
View File
@@ -170,6 +170,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260716_signed_history
import Simplex.Chat.Store.SQLite.Migrations.M20260720_server_roles
import Simplex.Chat.Store.SQLite.Migrations.M20260723_contact_request_rejection
import Simplex.Chat.Store.SQLite.Migrations.M20260813_auto_accept_group_invitations
import Simplex.Chat.Store.SQLite.Migrations.M20260822_forward_link
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -339,7 +340,8 @@ schemaMigrations =
("20260716_signed_history", m20260716_signed_history, Just down_m20260716_signed_history),
("20260720_server_roles", m20260720_server_roles, Just down_m20260720_server_roles),
("20260723_contact_request_rejection", m20260723_contact_request_rejection, Just down_m20260723_contact_request_rejection),
("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations)
("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations),
("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link)
]
-- | The list of migrations in ascending order by date
@@ -0,0 +1,26 @@
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20260822_forward_link where
import Database.SQLite.Simple (Query)
import Database.SQLite.Simple.QQ (sql)
m20260822_forward_link :: Query
m20260822_forward_link =
[sql|
ALTER TABLE chat_items ADD COLUMN fwd_from_group_type TEXT;
ALTER TABLE chat_items ADD COLUMN fwd_from_group_link BLOB;
ALTER TABLE chat_items ADD COLUMN fwd_from_public_group_id BLOB;
ALTER TABLE chat_items ADD COLUMN fwd_from_member_id BLOB;
ALTER TABLE chat_items ADD COLUMN fwd_from_shared_msg_id BLOB;
|]
down_m20260822_forward_link :: Query
down_m20260822_forward_link =
[sql|
ALTER TABLE chat_items DROP COLUMN fwd_from_group_type;
ALTER TABLE chat_items DROP COLUMN fwd_from_group_link;
ALTER TABLE chat_items DROP COLUMN fwd_from_public_group_id;
ALTER TABLE chat_items DROP COLUMN fwd_from_member_id;
ALTER TABLE chat_items DROP COLUMN fwd_from_shared_msg_id;
|]
@@ -514,7 +514,12 @@ CREATE TABLE chat_items(
item_msg_body BLOB,
item_chat_binding TEXT,
item_signatures BLOB,
item_signed_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL
item_signed_by_group_member_id INTEGER REFERENCES group_members ON DELETE SET NULL,
fwd_from_group_type TEXT,
fwd_from_group_link BLOB,
fwd_from_public_group_id BLOB,
fwd_from_member_id BLOB,
fwd_from_shared_msg_id BLOB
) STRICT;
CREATE TABLE sqlite_sequence(name,seq);
CREATE TABLE chat_item_messages(
+8 -4
View File
@@ -790,7 +790,7 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwa
prohibited = styled (colored Red) ("[unexpected chat item created, please report to developers]" :: String)
viewChatItemInfo :: AChatItem -> ChatItemInfo -> TimeZone -> [StyledString]
viewChatItemInfo (AChatItem _ msgDir _ ChatItem {meta = CIMeta {itemTs, itemTimed, createdAt}}) ChatItemInfo {itemVersions, forwardedFromChatItem} tz =
viewChatItemInfo (AChatItem _ msgDir _ ChatItem {meta = CIMeta {itemTs, itemTimed, createdAt, itemForwarded}}) ChatItemInfo {itemVersions, forwardedFromChatItem} tz =
["sent at: " <> ts itemTs]
<> receivedAt
<> toBeDeletedAt
@@ -822,7 +822,10 @@ viewChatItemInfo (AChatItem _ msgDir _ ChatItem {meta = CIMeta {itemTs, itemTime
(SMDRcv, GroupChat gInfo _scopeInfo) -> Just $ "#" <> viewGroupName gInfo
_ -> Nothing
fwdItemId = "chat item id: " <> (T.pack . show $ aChatItemId fwdACI)
_ -> []
_ -> case itemForwarded of
Just (CIFFGroup g _ _ _ _ _ _) -> ["forwarded from: #" <> (plain . viewName) g]
Just (CIFFGroupLink g _ _ _ _ _ _) -> ["forwarded from: #" <> (plain . viewName) g]
_ -> []
localTs :: TimeZone -> UTCTime -> String
localTs tz ts = do
@@ -1010,8 +1013,9 @@ forwardedFrom = \case
CIFFUnknown -> ["-> forwarded"]
CIFFContact c MDSnd _ _ -> ["<- you @" <> (plain . viewName) c]
CIFFContact c MDRcv _ _ -> ["<- @" <> (plain . viewName) c]
CIFFGroup g MDSnd _ _ -> ["<- you #" <> (plain . viewName) g]
CIFFGroup g MDRcv _ _ -> ["<- #" <> (plain . viewName) g]
CIFFGroup g MDSnd _ _ _ _ _ -> ["<- you #" <> (plain . viewName) g]
CIFFGroup g MDRcv _ _ _ _ _ -> ["<- #" <> (plain . viewName) g]
CIFFGroupLink g _ _ _ _ _ _ -> ["<- #" <> (plain . viewName) g]
sentByMember :: GroupInfo -> CIQDirection 'CTGroup -> Maybe GroupMember
sentByMember GroupInfo {membership} = \case