From 205ced1c1d7b6983d0e31687384d535c5dbdc600 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Mon, 10 Feb 2025 09:06:16 +0000 Subject: [PATCH] core, ui: report preference (#5620) * core: report preference * fix tests * ios: disable reports toggle until 6.4 * android, desktop: reports preference * ui: section * boolean --- apps/ios/Shared/Views/Chat/ChatView.swift | 6 ++- .../Chat/Group/GroupPreferencesView.swift | 2 + apps/ios/SimpleXChat/ChatTypes.swift | 24 +++++++++ apps/ios/SimpleXChat/ChatUtils.swift | 1 + .../chat/simplex/common/model/ChatModel.kt | 28 +++++----- .../chat/simplex/common/model/SimpleXAPI.kt | 20 ++++++- .../views/chat/group/GroupPreferences.kt | 6 +++ .../common/views/chat/item/ChatItemView.kt | 2 +- .../commonMain/resources/MR/base/strings.xml | 4 ++ src/Simplex/Chat/Library/Commands.hs | 3 +- src/Simplex/Chat/Library/Internal.hs | 10 +++- src/Simplex/Chat/Library/Subscriber.hs | 4 +- src/Simplex/Chat/Protocol.hs | 9 +++- src/Simplex/Chat/Store/Messages.hs | 4 +- .../SQLite/Migrations/chat_query_plans.txt | 9 ++++ src/Simplex/Chat/Types/Preferences.hs | 41 ++++++++++++-- tests/ChatTests/Groups.hs | 53 +++++++++++-------- tests/ChatTests/Utils.hs | 3 +- tests/ProtocolTests.hs | 2 +- 19 files changed, 177 insertions(+), 54 deletions(-) diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 9e48bd897a..8c8e98c2f1 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -1354,7 +1354,11 @@ struct ChatView: View { if ci.chatDir != .groupSnd { if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) { moderateButton(ci, groupInfo) - } else if ci.meta.itemDeleted == nil, case let .group(gInfo) = chat.chatInfo, gInfo.membership.memberRole == .member, !live, composeState.voiceMessageRecordingState == .noRecording { + } else if ci.meta.itemDeleted == nil && chat.groupFeatureEnabled(.reports), + case let .group(gInfo) = chat.chatInfo, + gInfo.membership.memberRole == .member + && !live + && composeState.voiceMessageRecordingState == .noRecording { reportButton(ci) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index 9ef53258aa..ed39c401ce 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -37,6 +37,7 @@ struct GroupPreferencesView: View { featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) featureSection(.files, $preferences.files.enable, $preferences.files.role) featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) + featureSection(.reports, $preferences.reports.enable) featureSection(.history, $preferences.history.enable) if groupInfo.isOwner { @@ -89,6 +90,7 @@ struct GroupPreferencesView: View { settingsRow(icon, color: color) { Toggle(feature.text, isOn: enable) } + .disabled(feature == .reports) // remove in 6.4 if timedOn { DropdownCustomTimePicker( selection: $preferences.timedMessages.ttl, diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index ebba98db4c..e974c2ca86 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -711,6 +711,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case voice case files case simplexLinks + case reports case history public var id: Self { self } @@ -731,6 +732,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: true case .files: true case .simplexLinks: true + case .reports: false case .history: false } } @@ -744,6 +746,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return NSLocalizedString("Voice messages", comment: "chat feature") case .files: return NSLocalizedString("Files and media", comment: "chat feature") case .simplexLinks: return NSLocalizedString("SimpleX links", comment: "chat feature") + case .reports: return NSLocalizedString("Member reports", comment: "chat feature") case .history: return NSLocalizedString("Visible history", comment: "chat feature") } } @@ -757,6 +760,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return "mic" case .files: return "doc" case .simplexLinks: return "link.circle" + case .reports: return "flag" case .history: return "clock" } } @@ -770,6 +774,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .voice: return "mic.fill" case .files: return "doc.fill" case .simplexLinks: return "link.circle.fill" + case .reports: return "flag.fill" case .history: return "clock.fill" } } @@ -819,6 +824,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .on: return "Allow to send SimpleX links." case .off: return "Prohibit sending SimpleX links." } + case .reports: + switch enabled { + case .on: return "Allow to report messsages to moderators." + case .off: return "Prohibit reporting messages to moderators." + } case .history: switch enabled { case .on: return "Send up to 100 last messages to new members." @@ -862,6 +872,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable { case .on: return "Members can send SimpleX links." case .off: return "SimpleX links are prohibited." } + case .reports: + switch enabled { + case .on: return "Members can report messsages to moderators." + case .off: return "Reporting messages to moderators is prohibited." + } case .history: switch enabled { case .on: return "Up to 100 last messages are sent to new members." @@ -1007,6 +1022,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { public var voice: RoleGroupPreference public var files: RoleGroupPreference public var simplexLinks: RoleGroupPreference + public var reports: GroupPreference public var history: GroupPreference public init( @@ -1017,6 +1033,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { voice: RoleGroupPreference, files: RoleGroupPreference, simplexLinks: RoleGroupPreference, + reports: GroupPreference, history: GroupPreference ) { self.timedMessages = timedMessages @@ -1026,6 +1043,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { self.voice = voice self.files = files self.simplexLinks = simplexLinks + self.reports = reports self.history = history } @@ -1037,6 +1055,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable { voice: RoleGroupPreference(enable: .on, role: nil), files: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil), + reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on) ) } @@ -1049,6 +1068,7 @@ public struct GroupPreferences: Codable, Hashable { public var voice: RoleGroupPreference? public var files: RoleGroupPreference? public var simplexLinks: RoleGroupPreference? + public var reports: GroupPreference? public var history: GroupPreference? public init( @@ -1059,6 +1079,7 @@ public struct GroupPreferences: Codable, Hashable { voice: RoleGroupPreference? = nil, files: RoleGroupPreference? = nil, simplexLinks: RoleGroupPreference? = nil, + reports: GroupPreference? = nil, history: GroupPreference? = nil ) { self.timedMessages = timedMessages @@ -1068,6 +1089,7 @@ public struct GroupPreferences: Codable, Hashable { self.voice = voice self.files = files self.simplexLinks = simplexLinks + self.reports = reports self.history = history } @@ -1079,6 +1101,7 @@ public struct GroupPreferences: Codable, Hashable { voice: RoleGroupPreference(enable: .on, role: nil), files: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil), + reports: GroupPreference(enable: .on), history: GroupPreference(enable: .on) ) } @@ -1092,6 +1115,7 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group voice: fullPreferences.voice, files: fullPreferences.files, simplexLinks: fullPreferences.simplexLinks, + reports: fullPreferences.reports, history: fullPreferences.history ) } diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift index 2bf861f437..6cbc76ec98 100644 --- a/apps/ios/SimpleXChat/ChatUtils.swift +++ b/apps/ios/SimpleXChat/ChatUtils.swift @@ -27,6 +27,7 @@ extension ChatLike { case .files: p.files.on(for: groupInfo.membership) case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership) case .history: p.history.on + case .reports: p.reports.on } } else { return true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index a148392572..3d64fda251 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -1225,18 +1225,7 @@ data class Chat( fun groupFeatureEnabled(feature: GroupFeature): Boolean = if (chatInfo is ChatInfo.Group) { - val groupInfo = chatInfo.groupInfo - val p = groupInfo.fullGroupPreferences - when (feature) { - GroupFeature.TimedMessages -> p.timedMessages.on - GroupFeature.DirectMessages -> p.directMessages.on(groupInfo.membership) - GroupFeature.FullDelete -> p.fullDelete.on - GroupFeature.Reactions -> p.reactions.on - GroupFeature.Voice -> p.voice.on(groupInfo.membership) - GroupFeature.Files -> p.files.on(groupInfo.membership) - GroupFeature.SimplexLinks -> p.simplexLinks.on(groupInfo.membership) - GroupFeature.History -> p.history.on - } + chatInfo.groupInfo.groupFeatureEnabled(feature) } else { true } @@ -1780,6 +1769,21 @@ data class GroupInfo ( val canModerate: Boolean get() = membership.memberRole >= GroupMemberRole.Moderator && membership.memberActive + fun groupFeatureEnabled(feature: GroupFeature): Boolean { + val p = fullGroupPreferences + return when (feature) { + GroupFeature.TimedMessages -> p.timedMessages.on + GroupFeature.DirectMessages -> p.directMessages.on(membership) + GroupFeature.FullDelete -> p.fullDelete.on + GroupFeature.Reactions -> p.reactions.on + GroupFeature.Voice -> p.voice.on(membership) + GroupFeature.Files -> p.files.on(membership) + GroupFeature.SimplexLinks -> p.simplexLinks.on(membership) + GroupFeature.Reports -> p.reports.on + GroupFeature.History -> p.history.on + } + } + companion object { val sampleData = GroupInfo( groupId = 1, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 6021d48b0a..abb449f0cc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -5160,6 +5160,7 @@ enum class GroupFeature: Feature { @SerialName("voice") Voice, @SerialName("files") Files, @SerialName("simplexLinks") SimplexLinks, + @SerialName("reports") Reports, @SerialName("history") History; override val hasParam: Boolean get() = when(this) { @@ -5176,6 +5177,7 @@ enum class GroupFeature: Feature { Voice -> true Files -> true SimplexLinks -> true + Reports -> false History -> false } @@ -5188,6 +5190,7 @@ enum class GroupFeature: Feature { Voice -> generalGetString(MR.strings.voice_messages) Files -> generalGetString(MR.strings.files_and_media) SimplexLinks -> generalGetString(MR.strings.simplex_links) + Reports -> generalGetString(MR.strings.group_reports_member_reports) History -> generalGetString(MR.strings.recent_history) } @@ -5200,6 +5203,7 @@ enum class GroupFeature: Feature { Voice -> painterResource(MR.images.ic_keyboard_voice) Files -> painterResource(MR.images.ic_draft) SimplexLinks -> painterResource(MR.images.ic_link) + Reports -> painterResource(MR.images.ic_flag) History -> painterResource(MR.images.ic_schedule) } @@ -5212,6 +5216,7 @@ enum class GroupFeature: Feature { Voice -> painterResource(MR.images.ic_keyboard_voice_filled) Files -> painterResource(MR.images.ic_draft_filled) SimplexLinks -> painterResource(MR.images.ic_link) + Reports -> painterResource(MR.images.ic_flag_filled) History -> painterResource(MR.images.ic_schedule_filled) } @@ -5246,6 +5251,10 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_send_simplex_links) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_simplex_links) } + Reports -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_member_reports) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_member_reports) + } History -> when(enabled) { GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history) @@ -5281,6 +5290,10 @@ enum class GroupFeature: Feature { GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_simplex_links) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.simplex_links_are_prohibited_in_group) } + Reports -> when(enabled) { + GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_reports) + GroupFeatureEnabled.OFF -> generalGetString(MR.strings.member_reports_are_prohibited) + } History -> when(enabled) { GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members) @@ -5400,6 +5413,7 @@ data class FullGroupPreferences( val voice: RoleGroupPreference, val files: RoleGroupPreference, val simplexLinks: RoleGroupPreference, + val reports: GroupPreference, val history: GroupPreference, ) { fun toGroupPreferences(): GroupPreferences = @@ -5411,7 +5425,8 @@ data class FullGroupPreferences( voice = voice, files = files, simplexLinks = simplexLinks, - history = history + reports = reports, + history = history, ) companion object { @@ -5423,6 +5438,7 @@ data class FullGroupPreferences( voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), + reports = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON), ) } @@ -5437,6 +5453,7 @@ data class GroupPreferences( val voice: RoleGroupPreference? = null, val files: RoleGroupPreference? = null, val simplexLinks: RoleGroupPreference? = null, + val reports: GroupPreference? = null, val history: GroupPreference? = null, ) { companion object { @@ -5448,6 +5465,7 @@ data class GroupPreferences( voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), + reports = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON), ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 3d9f42f929..64195322c2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -132,6 +132,11 @@ private fun GroupPreferencesLayout( applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role))) } + SectionDividerSpaced(true, maxBottomPadding = false) + val enableReports = remember(preferences) { mutableStateOf(preferences.reports.enable) } + FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> + applyPrefs(preferences.copy(reports = GroupPreference(enable = enable))) + } SectionDividerSpaced(true, maxBottomPadding = false) val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) } FeatureSection(GroupFeature.History, enableHistory, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> @@ -169,6 +174,7 @@ private fun FeatureSection( feature.text, icon, iconTint, + disabled = feature == GroupFeature.Reports, // remove in 6.4 checked = enableFeature.value == GroupFeatureEnabled.ON, ) { checked -> onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index e0650e1d80..10e902f24b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -400,7 +400,7 @@ fun ChatItemView( val groupInfo = cItem.memberToModerate(cInfo)?.first if (groupInfo != null) { ModerateItemAction(cItem, questionText = moderateMessageQuestionText(cInfo.featureEnabled(ChatFeature.FullDelete), 1), showMenu, deleteMessage) - } else if (cItem.meta.itemDeleted == null && cInfo is ChatInfo.Group && cInfo.groupInfo.membership.memberRole == GroupMemberRole.Member && !live) { + } else if (cItem.meta.itemDeleted == null && cInfo is ChatInfo.Group && cInfo.groupInfo.groupFeatureEnabled(GroupFeature.Reports) && cInfo.groupInfo.membership.memberRole == GroupMemberRole.Member && !live) { ReportItemAction(cItem, composeState, showMenu) } } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 5441f0816a..4798c9df56 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -2064,6 +2064,8 @@ Prohibit sending SimpleX links Send up to 100 last messages to new members. Do not send history to new members. + Allow to report messsages to moderators. + Prohibit reporting messages to moderators. Members can send disappearing messages. Disappearing messages are prohibited. Members can send direct messages. @@ -2082,6 +2084,8 @@ SimpleX links are prohibited. Up to 100 last messages are sent to new members. History is not sent to new members. + Members can report messsages to moderators. + Reporting messages is prohibited in this group. Delete after %d sec %ds diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index e54eae4472..4aed1b053d 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -3066,7 +3066,7 @@ processChatCommand' vr = \case findProhibited :: [ComposedMessageReq] -> Maybe GroupFeature findProhibited = foldr' - (\(ComposedMessage {fileSource, msgContent = mc}, _, (_, ft), _) acc -> prohibitedGroupContent gInfo membership mc ft fileSource <|> acc) + (\(ComposedMessage {fileSource, msgContent = mc}, _, (_, ft), _) acc -> prohibitedGroupContent gInfo membership mc ft fileSource True <|> acc) Nothing processComposedMessages :: CM ChatResponse processComposedMessages = do @@ -3974,6 +3974,7 @@ chatCommandP = "/set disappear #" *> (SetGroupTimedMessages <$> displayNameP <*> (A.space *> timedTTLOnOffP)), "/set disappear @" *> (SetContactTimedMessages <$> displayNameP <*> optional (A.space *> timedMessagesEnabledP)), "/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))), + "/set reports #" *> (SetGroupFeature (AGFNR SGFReports) <$> displayNameP <*> _strP), "/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole), ("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito, "/set device name " *> (SetLocalDeviceName <$> textP), diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs index faf503eb83..ea838206fe 100644 --- a/src/Simplex/Chat/Library/Internal.hs +++ b/src/Simplex/Chat/Library/Internal.hs @@ -320,12 +320,18 @@ quoteContent mc qmc ciFile_ qFileName = maybe qText (T.pack . getFileName) ciFile_ qTextOrFile = if T.null qText then qFileName else qText -prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe MarkdownList -> Maybe f -> Maybe GroupFeature -prohibitedGroupContent gInfo m mc ft file_ +prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature +prohibitedGroupContent gInfo@GroupInfo {membership = GroupMember {memberRole = userRole}} m mc ft file_ sent | isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice | not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles + | isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports | prohibitedSimplexLinks gInfo m ft = Just GFSimplexLinks | otherwise = Nothing + where + -- admins cannot send reports, non-admins cannot receive reports + badReportUser + | sent = userRole >= GRModerator + | otherwise = userRole < GRModerator prohibitedSimplexLinks :: GroupInfo -> GroupMember -> Maybe MarkdownList -> Bool prohibitedSimplexLinks gInfo m ft = diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index 0ea262ec4c..902c19ed63 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -1720,7 +1720,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> Bool -> CM () newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs forwarded | blockedByAdmin m = createBlockedByAdmin - | otherwise = case prohibitedGroupContent gInfo m content ft_ fInv_ of + | otherwise = case prohibitedGroupContent gInfo m content ft_ fInv_ False of Just f -> rejected f Nothing -> withStore' (\db -> getCIModeration db vr user gInfo memberId sharedMsgId_) >>= \case @@ -1729,7 +1729,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> deleteCIModeration db gInfo memberId sharedMsgId_ Nothing -> createContentItem where - rejected f = void $ newChatItem (ciContentNoParse $ CIRcvGroupFeatureRejected f) Nothing Nothing False + rejected f = newChatItem (ciContentNoParse $ CIRcvGroupFeatureRejected f) Nothing Nothing False timed' = if forwarded then rcvCITimed_ (Just Nothing) itemTTL else rcvGroupCITimed gInfo itemTTL live' = fromMaybe False live_ ExtMsgContent content mentions fInv_ itemTTL live_ = mcExtMsgContent mc diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index b366cc3979..1e5815bab3 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -410,8 +410,8 @@ forwardedToGroupMembers ms forwardedMsgs = XGrpMemRestrict mId _ -> Just mId _ -> Nothing _ -> Nothing - hasReport = any isReport forwardedMsgs - isReport ChatMessage {chatMsgEvent} = case encoding @e of + hasReport = any isReportEvent forwardedMsgs + isReportEvent ChatMessage {chatMsgEvent} = case encoding @e of SJson -> case chatMsgEvent of XMsgNew mc -> case mcExtMsgContent mc of ExtMsgContent {content = MCReport {}} -> True @@ -600,6 +600,11 @@ isVoice = \case MCVoice {} -> True _ -> False +isReport :: MsgContent -> Bool +isReport = \case + MCReport {} -> True + _ -> False + msgContentTag :: MsgContent -> MsgContentTag msgContentTag = \case MCText _ -> MCText_ diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index f849223440..f37201c487 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -2453,10 +2453,10 @@ markReceivedGroupReportsDeleted db User {userId} GroupInfo {groupId, membership} [sql| 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 = ? + WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0 RETURNING chat_item_id |] - (DBCIDeleted, deletedTs, groupMemberId' membership, currentTs, userId, groupId, MCReport_, DBCINotDeleted, False) + (DBCIDeleted, deletedTs, groupMemberId' membership, currentTs, userId, groupId, MCReport_, DBCINotDeleted) getGroupChatItemBySharedMsgId :: DB.Connection -> User -> GroupInfo -> GroupMemberId -> SharedMsgId -> ExceptT StoreError IO (CChatItem 'CTGroup) getGroupChatItemBySharedMsgId db user@User {userId} g@GroupInfo {groupId} groupMemberId sharedMsgId = do diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt index 2df28067ad..2ee87ace8f 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt @@ -3322,6 +3322,15 @@ Query: Plan: SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?) +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 = ? + 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 = ? diff --git a/src/Simplex/Chat/Types/Preferences.hs b/src/Simplex/Chat/Types/Preferences.hs index 001dd27172..2e704c5cf5 100644 --- a/src/Simplex/Chat/Types/Preferences.hs +++ b/src/Simplex/Chat/Types/Preferences.hs @@ -149,6 +149,7 @@ data GroupFeature | GFVoice | GFFiles | GFSimplexLinks + | GFReports | GFHistory deriving (Show) @@ -160,6 +161,7 @@ data SGroupFeature (f :: GroupFeature) where SGFVoice :: SGroupFeature 'GFVoice SGFFiles :: SGroupFeature 'GFFiles SGFSimplexLinks :: SGroupFeature 'GFSimplexLinks + SGFReports :: SGroupFeature 'GFReports SGFHistory :: SGroupFeature 'GFHistory deriving instance Show (SGroupFeature f) @@ -185,6 +187,7 @@ groupFeatureNameText = \case GFVoice -> "Voice messages" GFFiles -> "Files and media" GFSimplexLinks -> "SimpleX links" + GFReports -> "Member reports" GFHistory -> "Recent history" groupFeatureNameText' :: SGroupFeature f -> Text @@ -208,11 +211,12 @@ allGroupFeatures = AGF SGFVoice, AGF SGFFiles, AGF SGFSimplexLinks, + AGF SGFReports, AGF SGFHistory ] groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) -groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, history} = case f of +groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history} = case f of SGFTimedMessages -> timedMessages SGFDirectMessages -> directMessages SGFFullDelete -> fullDelete @@ -220,6 +224,7 @@ groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reac SGFVoice -> voice SGFFiles -> files SGFSimplexLinks -> simplexLinks + SGFReports -> reports SGFHistory -> history toGroupFeature :: SGroupFeature f -> GroupFeature @@ -231,6 +236,7 @@ toGroupFeature = \case SGFVoice -> GFVoice SGFFiles -> GFFiles SGFSimplexLinks -> GFSimplexLinks + SGFReports -> GFReports SGFHistory -> GFHistory class GroupPreferenceI p where @@ -243,7 +249,7 @@ instance GroupPreferenceI (Maybe GroupPreferences) where getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) instance GroupPreferenceI FullGroupPreferences where - getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, history} = case f of + getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history} = case f of SGFTimedMessages -> timedMessages SGFDirectMessages -> directMessages SGFFullDelete -> fullDelete @@ -251,6 +257,7 @@ instance GroupPreferenceI FullGroupPreferences where SGFVoice -> voice SGFFiles -> files SGFSimplexLinks -> simplexLinks + SGFReports -> reports SGFHistory -> history {-# INLINE getGroupPreference #-} @@ -263,6 +270,7 @@ data GroupPreferences = GroupPreferences voice :: Maybe VoiceGroupPreference, files :: Maybe FilesGroupPreference, simplexLinks :: Maybe SimplexLinksGroupPreference, + reports :: Maybe ReportsGroupPreference, history :: Maybe HistoryGroupPreference } deriving (Eq, Show) @@ -296,6 +304,7 @@ setGroupPreference_ f pref prefs = SGFVoice -> prefs {voice = pref} SGFFiles -> prefs {files = pref} SGFSimplexLinks -> prefs {simplexLinks = pref} + SGFReports -> prefs {reports = pref} SGFHistory -> prefs {history = pref} setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences @@ -325,6 +334,7 @@ data FullGroupPreferences = FullGroupPreferences voice :: VoiceGroupPreference, files :: FilesGroupPreference, simplexLinks :: SimplexLinksGroupPreference, + reports :: ReportsGroupPreference, history :: HistoryGroupPreference } deriving (Eq, Show) @@ -377,22 +387,23 @@ defaultGroupPrefs = FullGroupPreferences { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, directMessages = DirectMessagesGroupPreference {enable = FEOff, role = Nothing}, - fullDelete = FullDeleteGroupPreference {enable = FEOn, role = Just GRModerator}, + fullDelete = FullDeleteGroupPreference {enable = FEOff, role = Nothing}, reactions = ReactionsGroupPreference {enable = FEOn}, voice = VoiceGroupPreference {enable = FEOn, role = Nothing}, files = FilesGroupPreference {enable = FEOn, role = Nothing}, simplexLinks = SimplexLinksGroupPreference {enable = FEOn, role = Nothing}, + reports = ReportsGroupPreference {enable = FEOn}, history = HistoryGroupPreference {enable = FEOff} } emptyGroupPrefs :: GroupPreferences -emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing businessGroupPrefs :: Preferences -> GroupPreferences businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice} = defaultBusinessGroupPrefs { timedMessages = Just TimedMessagesGroupPreference {enable = maybe FEOff enableFeature timedMessages, ttl = maybe Nothing prefParam timedMessages}, - fullDelete = Just FullDeleteGroupPreference {enable = maybe FEOff enableFeature fullDelete, role = Just GRModerator}, + fullDelete = Just FullDeleteGroupPreference {enable = maybe FEOff enableFeature fullDelete, role = Nothing}, reactions = Just ReactionsGroupPreference {enable = maybe FEOn enableFeature reactions}, voice = Just VoiceGroupPreference {enable = maybe FEOff enableFeature voice, role = Nothing} } @@ -412,6 +423,7 @@ defaultBusinessGroupPrefs = voice = Just $ VoiceGroupPreference FEOff Nothing, files = Just $ FilesGroupPreference FEOn Nothing, simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing, + reports = Just $ ReportsGroupPreference FEOff, history = Just $ HistoryGroupPreference FEOn } @@ -512,6 +524,10 @@ data SimplexLinksGroupPreference = SimplexLinksGroupPreference {enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole} deriving (Eq, Show) +data ReportsGroupPreference = ReportsGroupPreference + {enable :: GroupFeatureEnabled} + deriving (Eq, Show) + data HistoryGroupPreference = HistoryGroupPreference {enable :: GroupFeatureEnabled} deriving (Eq, Show) @@ -550,6 +566,9 @@ instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where instance HasField "enable" SimplexLinksGroupPreference GroupFeatureEnabled where hasField p@SimplexLinksGroupPreference {enable} = (\e -> p {enable = e}, enable) +instance HasField "enable" ReportsGroupPreference GroupFeatureEnabled where + hasField p@ReportsGroupPreference {enable} = (\e -> p {enable = e}, enable) + instance HasField "enable" HistoryGroupPreference GroupFeatureEnabled where hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable) @@ -595,6 +614,12 @@ instance GroupFeatureI 'GFSimplexLinks where groupPrefParam _ = Nothing groupPrefRole SimplexLinksGroupPreference {role} = role +instance GroupFeatureI 'GFReports where + type GroupFeaturePreference 'GFReports = ReportsGroupPreference + sGroupFeature = SGFReports + groupPrefParam _ = Nothing + groupPrefRole _ = Nothing + instance GroupFeatureI 'GFHistory where type GroupFeaturePreference 'GFHistory = HistoryGroupPreference sGroupFeature = SGFHistory @@ -607,6 +632,8 @@ instance GroupFeatureNoRoleI 'GFFullDelete instance GroupFeatureNoRoleI 'GFReactions +instance GroupFeatureNoRoleI 'GFReports + instance GroupFeatureNoRoleI 'GFHistory instance HasField "role" DirectMessagesGroupPreference (Maybe GroupMemberRole) where @@ -761,6 +788,7 @@ mergeGroupPreferences groupPreferences = voice = pref SGFVoice, files = pref SGFFiles, simplexLinks = pref SGFSimplexLinks, + reports = pref SGFReports, history = pref SGFHistory } where @@ -777,6 +805,7 @@ toGroupPreferences groupPreferences = voice = pref SGFVoice, files = pref SGFFiles, simplexLinks = pref SGFSimplexLinks, + reports = pref SGFReports, history = pref SGFHistory } where @@ -885,6 +914,8 @@ $(J.deriveJSON defaultJSON ''FilesGroupPreference) $(J.deriveJSON defaultJSON ''SimplexLinksGroupPreference) +$(J.deriveJSON defaultJSON ''ReportsGroupPreference) + $(J.deriveJSON defaultJSON ''HistoryGroupPreference) $(J.deriveJSON defaultJSON ''GroupPreferences) diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 254fc6e813..04976ad5cd 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -559,7 +559,7 @@ testGroup2 = ] dan <##> alice -- show last messages - alice ##> "/t #club 17" + alice ##> "/t #club 18" alice -- these strings are expected in any order because of sorting by time and rounding of time for sent <##? ( map (ConsoleString . ("#club " <> )) groupFeatureStrs @@ -1226,7 +1226,7 @@ testGroupMessageDelete = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath threadDelay 1000000 -- alice, bob: msg id 5, cath: msg id 4 (after group invitations & group events) alice #> "#team hello!" @@ -1238,7 +1238,7 @@ testGroupMessageDelete = msgItemId1 <- lastItemId alice alice #$> ("/_delete item #1 " <> msgItemId1 <> " internal", id, "message deleted") - alice #$> ("/_get chat #1 count=2", chat, [(0, "connected"), (1, "Full deletion: off")]) + alice #$> ("/_get chat #1 count=2", chat, [(0, "connected"), (0, "connected")]) bob #$> ("/_get chat #1 count=1", chat, [(0, "hello!")]) cath #$> ("/_get chat #1 count=1", chat, [(0, "hello!")]) @@ -1264,7 +1264,7 @@ testGroupMessageDelete = msgItemId2 <- lastItemId alice alice #$> ("/_delete item #1 " <> msgItemId2 <> " internal", id, "message deleted") - alice #$> ("/_get chat #1 count=2", chat', [((0, "connected"), Nothing), ((1, "Full deletion: off"), Nothing)]) + alice #$> ("/_get chat #1 count=2", chat', [((0, "connected"), Nothing), ((0, "connected"), Nothing)]) bob #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((1, "hi alic"), Just (0, "hello!"))]) cath #$> ("/_get chat #1 count=2", chat', [((0, "hello!"), Nothing), ((0, "hi alic"), Just (0, "hello!"))]) @@ -1311,7 +1311,7 @@ testGroupMessageDeleteMultiple = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath threadDelay 1000000 alice #> "#team hello" @@ -1348,7 +1348,7 @@ testGroupMessageDeleteMultipleManyBatches = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath bob ##> "/set receipts all off" bob <## "ok" @@ -1499,9 +1499,9 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile alice ##> "/g team" alice <## "group #team is created" alice <## "to add members use /a team or /create link #team" - alice ##> "/set delete #team off" - alice <## "updated group preferences:" - alice <## "Full deletion: off" + -- alice ##> "/set delete #team off" + -- alice <## "updated group preferences:" + -- alice <## "Full deletion: off" addMember "team" alice bob GRAdmin bob ##> "/j team" concurrentlyN_ @@ -1561,6 +1561,7 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile alice <## "Voice messages: on" alice <## "Files and media: on" alice <## "SimpleX links: on" + alice <## "Member reports: on" alice <## "Recent history: on" bobAddedDan :: HasCallStack => TestCC -> IO () bobAddedDan cc = do @@ -1572,7 +1573,7 @@ testGroupModerate = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath alice ##> "/mr team cath member" concurrentlyN_ [ alice <## "#team: you changed the role of cath from admin to member", @@ -1604,7 +1605,7 @@ testGroupModerateOwn = testChat2 aliceProfile bobProfile $ \alice bob -> do createGroup2 "team" alice bob - disableFullDeletion2 "team" alice bob + -- disableFullDeletion2 "team" alice bob threadDelay 1000000 alice #> "#team hello" bob <# "#team alice> hello" @@ -1619,7 +1620,7 @@ testGroupModerateMultiple = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath threadDelay 1000000 alice #> "#team hello" @@ -1655,7 +1656,7 @@ testGroupModerateFullDelete = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath alice ##> "/mr team cath member" concurrentlyN_ [ alice <## "#team: you changed the role of cath from admin to member", @@ -1694,7 +1695,7 @@ testGroupDelayedModeration ps = do withNewTestChatCfg ps cfg "alice" aliceProfile $ \alice -> do withNewTestChatCfg ps cfg "bob" bobProfile $ \bob -> do createGroup2 "team" alice bob - disableFullDeletion2 "team" alice bob + -- disableFullDeletion2 "team" alice bob withNewTestChatCfg ps cfg "cath" cathProfile $ \cath -> do connectUsers alice cath addMember "team" alice cath GRMember @@ -1742,7 +1743,7 @@ testGroupDelayedModerationFullDelete ps = do withNewTestChatCfg ps cfg "alice" aliceProfile $ \alice -> do withNewTestChatCfg ps cfg "bob" bobProfile $ \bob -> do createGroup2 "team" alice bob - disableFullDeletion2 "team" alice bob + -- disableFullDeletion2 "team" alice bob withNewTestChatCfg ps cfg "cath" cathProfile $ \cath -> do connectUsers alice cath addMember "team" alice cath GRMember @@ -3998,6 +3999,12 @@ testGroupMsgForwardReport = cath <## "#team: alice changed the role of bob from admin to moderator" ] + alice ##> "/mr team cath member" + concurrentlyN_ + [ alice <## "#team: you changed the role of cath from admin to member", + bob <## "#team: alice changed the role of cath from admin to member", + cath <## "#team: alice changed your role from admin to member" + ] cath ##> "/report #team content hi there" cath <# "#team > bob hi there" cath <## " report content" @@ -4127,7 +4134,7 @@ testGroupMsgForwardDeletion = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do setupGroupForwarding3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath bob #> "#team hi there" alice <# "#team bob> hi there" @@ -4845,7 +4852,7 @@ testGroupHistoryDeletedMessage = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup2 "team" alice bob - disableFullDeletion2 "team" alice bob + -- disableFullDeletion2 "team" alice bob alice #> "#team hello" bob <# "#team alice> hello" @@ -5535,7 +5542,7 @@ testBlockForAllMarkedBlocked = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath threadDelay 1000000 @@ -5623,7 +5630,7 @@ testBlockForAllFullDelete = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath alice ##> "/set delete #team on" alice <## "updated group preferences:" @@ -5704,7 +5711,7 @@ testBlockForAllAnotherAdminUnblocks = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath bob #> "#team 1" [alice, cath] *<# "#team bob> 1" @@ -5733,7 +5740,7 @@ testBlockForAllBeforeJoining = testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath bob #> "#team 1" [alice, cath] *<# "#team bob> 1" @@ -5802,7 +5809,7 @@ testBlockForAllCantRepeat = testChat3 aliceProfile bobProfile cathProfile $ \alice bob cath -> do createGroup3 "team" alice bob cath - disableFullDeletion3 "team" alice bob cath + -- disableFullDeletion3 "team" alice bob cath alice ##> "/unblock for all #team bob" alice <## "bad chat command: already unblocked" @@ -5919,7 +5926,7 @@ testGroupMemberReports = testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do createGroup3 "jokes" alice bob cath - disableFullDeletion3 "jokes" alice bob cath + -- disableFullDeletion3 "jokes" alice bob cath alice ##> "/mr jokes bob moderator" concurrentlyN_ [ alice <## "#jokes: you changed the role of bob from admin to moderator", diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index a6eab378d9..7bb25dbe9b 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -300,11 +300,12 @@ groupFeatures'' dir = [ ((dir, e2eeInfoNoPQStr), Nothing, Nothing), ((dir, "Disappearing messages: off"), Nothing, Nothing), ((dir, "Direct messages: on"), Nothing, Nothing), - ((dir, "Full deletion: on for moderators"), Nothing, Nothing), + ((dir, "Full deletion: off"), Nothing, Nothing), ((dir, "Message reactions: on"), Nothing, Nothing), ((dir, "Voice messages: on"), Nothing, Nothing), ((dir, "Files and media: on"), Nothing, Nothing), ((dir, "SimpleX links: on"), Nothing, Nothing), + ((dir, "Member reports: on"), Nothing, Nothing), ((dir, "Recent history: on"), Nothing, Nothing) ] diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 4f191384ae..cb293895a9 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -101,7 +101,7 @@ testChatPreferences :: Maybe Preferences testChatPreferences = Just Preferences {voice = Just VoicePreference {allow = FAYes}, fullDelete = Nothing, timedMessages = Nothing, calls = Nothing, reactions = Just ReactionsPreference {allow = FAYes}} testGroupPreferences :: Maybe GroupPreferences -testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing} +testGroupPreferences = Just GroupPreferences {timedMessages = Nothing, directMessages = Nothing, reactions = Just ReactionsGroupPreference {enable = FEOn}, voice = Just VoiceGroupPreference {enable = FEOn, role = Nothing}, files = Nothing, fullDelete = Nothing, simplexLinks = Nothing, history = Nothing, reports = Nothing} testProfile :: Profile testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences}