core, ui: report preference (#5620)

* core: report preference

* fix tests

* ios: disable reports toggle until 6.4

* android, desktop: reports preference

* ui: section

* boolean
This commit is contained in:
Evgeny 2025-02-10 09:06:16 +00:00 committed by GitHub
parent ff35643533
commit 205ced1c1d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 177 additions and 54 deletions

View file

@ -1354,7 +1354,11 @@ struct ChatView: View {
if ci.chatDir != .groupSnd { if ci.chatDir != .groupSnd {
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) { if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
moderateButton(ci, groupInfo) 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) reportButton(ci)
} }
} }

View file

@ -37,6 +37,7 @@ struct GroupPreferencesView: View {
featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) featureSection(.voice, $preferences.voice.enable, $preferences.voice.role)
featureSection(.files, $preferences.files.enable, $preferences.files.role) featureSection(.files, $preferences.files.enable, $preferences.files.role)
featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
featureSection(.reports, $preferences.reports.enable)
featureSection(.history, $preferences.history.enable) featureSection(.history, $preferences.history.enable)
if groupInfo.isOwner { if groupInfo.isOwner {
@ -89,6 +90,7 @@ struct GroupPreferencesView: View {
settingsRow(icon, color: color) { settingsRow(icon, color: color) {
Toggle(feature.text, isOn: enable) Toggle(feature.text, isOn: enable)
} }
.disabled(feature == .reports) // remove in 6.4
if timedOn { if timedOn {
DropdownCustomTimePicker( DropdownCustomTimePicker(
selection: $preferences.timedMessages.ttl, selection: $preferences.timedMessages.ttl,

View file

@ -711,6 +711,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
case voice case voice
case files case files
case simplexLinks case simplexLinks
case reports
case history case history
public var id: Self { self } public var id: Self { self }
@ -731,6 +732,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
case .voice: true case .voice: true
case .files: true case .files: true
case .simplexLinks: true case .simplexLinks: true
case .reports: false
case .history: 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 .voice: return NSLocalizedString("Voice messages", comment: "chat feature")
case .files: return NSLocalizedString("Files and media", comment: "chat feature") case .files: return NSLocalizedString("Files and media", comment: "chat feature")
case .simplexLinks: return NSLocalizedString("SimpleX links", 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") 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 .voice: return "mic"
case .files: return "doc" case .files: return "doc"
case .simplexLinks: return "link.circle" case .simplexLinks: return "link.circle"
case .reports: return "flag"
case .history: return "clock" case .history: return "clock"
} }
} }
@ -770,6 +774,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
case .voice: return "mic.fill" case .voice: return "mic.fill"
case .files: return "doc.fill" case .files: return "doc.fill"
case .simplexLinks: return "link.circle.fill" case .simplexLinks: return "link.circle.fill"
case .reports: return "flag.fill"
case .history: return "clock.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 .on: return "Allow to send SimpleX links."
case .off: return "Prohibit sending 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: case .history:
switch enabled { switch enabled {
case .on: return "Send up to 100 last messages to new members." 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 .on: return "Members can send SimpleX links."
case .off: return "SimpleX links are prohibited." 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: case .history:
switch enabled { switch enabled {
case .on: return "Up to 100 last messages are sent to new members." 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 voice: RoleGroupPreference
public var files: RoleGroupPreference public var files: RoleGroupPreference
public var simplexLinks: RoleGroupPreference public var simplexLinks: RoleGroupPreference
public var reports: GroupPreference
public var history: GroupPreference public var history: GroupPreference
public init( public init(
@ -1017,6 +1033,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
voice: RoleGroupPreference, voice: RoleGroupPreference,
files: RoleGroupPreference, files: RoleGroupPreference,
simplexLinks: RoleGroupPreference, simplexLinks: RoleGroupPreference,
reports: GroupPreference,
history: GroupPreference history: GroupPreference
) { ) {
self.timedMessages = timedMessages self.timedMessages = timedMessages
@ -1026,6 +1043,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
self.voice = voice self.voice = voice
self.files = files self.files = files
self.simplexLinks = simplexLinks self.simplexLinks = simplexLinks
self.reports = reports
self.history = history self.history = history
} }
@ -1037,6 +1055,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
voice: RoleGroupPreference(enable: .on, role: nil), voice: RoleGroupPreference(enable: .on, role: nil),
files: RoleGroupPreference(enable: .on, role: nil), files: RoleGroupPreference(enable: .on, role: nil),
simplexLinks: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil),
reports: GroupPreference(enable: .on),
history: GroupPreference(enable: .on) history: GroupPreference(enable: .on)
) )
} }
@ -1049,6 +1068,7 @@ public struct GroupPreferences: Codable, Hashable {
public var voice: RoleGroupPreference? public var voice: RoleGroupPreference?
public var files: RoleGroupPreference? public var files: RoleGroupPreference?
public var simplexLinks: RoleGroupPreference? public var simplexLinks: RoleGroupPreference?
public var reports: GroupPreference?
public var history: GroupPreference? public var history: GroupPreference?
public init( public init(
@ -1059,6 +1079,7 @@ public struct GroupPreferences: Codable, Hashable {
voice: RoleGroupPreference? = nil, voice: RoleGroupPreference? = nil,
files: RoleGroupPreference? = nil, files: RoleGroupPreference? = nil,
simplexLinks: RoleGroupPreference? = nil, simplexLinks: RoleGroupPreference? = nil,
reports: GroupPreference? = nil,
history: GroupPreference? = nil history: GroupPreference? = nil
) { ) {
self.timedMessages = timedMessages self.timedMessages = timedMessages
@ -1068,6 +1089,7 @@ public struct GroupPreferences: Codable, Hashable {
self.voice = voice self.voice = voice
self.files = files self.files = files
self.simplexLinks = simplexLinks self.simplexLinks = simplexLinks
self.reports = reports
self.history = history self.history = history
} }
@ -1079,6 +1101,7 @@ public struct GroupPreferences: Codable, Hashable {
voice: RoleGroupPreference(enable: .on, role: nil), voice: RoleGroupPreference(enable: .on, role: nil),
files: RoleGroupPreference(enable: .on, role: nil), files: RoleGroupPreference(enable: .on, role: nil),
simplexLinks: RoleGroupPreference(enable: .on, role: nil), simplexLinks: RoleGroupPreference(enable: .on, role: nil),
reports: GroupPreference(enable: .on),
history: GroupPreference(enable: .on) history: GroupPreference(enable: .on)
) )
} }
@ -1092,6 +1115,7 @@ public func toGroupPreferences(_ fullPreferences: FullGroupPreferences) -> Group
voice: fullPreferences.voice, voice: fullPreferences.voice,
files: fullPreferences.files, files: fullPreferences.files,
simplexLinks: fullPreferences.simplexLinks, simplexLinks: fullPreferences.simplexLinks,
reports: fullPreferences.reports,
history: fullPreferences.history history: fullPreferences.history
) )
} }

View file

@ -27,6 +27,7 @@ extension ChatLike {
case .files: p.files.on(for: groupInfo.membership) case .files: p.files.on(for: groupInfo.membership)
case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership) case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership)
case .history: p.history.on case .history: p.history.on
case .reports: p.reports.on
} }
} else { } else {
return true return true

View file

@ -1225,18 +1225,7 @@ data class Chat(
fun groupFeatureEnabled(feature: GroupFeature): Boolean = fun groupFeatureEnabled(feature: GroupFeature): Boolean =
if (chatInfo is ChatInfo.Group) { if (chatInfo is ChatInfo.Group) {
val groupInfo = chatInfo.groupInfo chatInfo.groupInfo.groupFeatureEnabled(feature)
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
}
} else { } else {
true true
} }
@ -1780,6 +1769,21 @@ data class GroupInfo (
val canModerate: Boolean val canModerate: Boolean
get() = membership.memberRole >= GroupMemberRole.Moderator && membership.memberActive 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 { companion object {
val sampleData = GroupInfo( val sampleData = GroupInfo(
groupId = 1, groupId = 1,

View file

@ -5160,6 +5160,7 @@ enum class GroupFeature: Feature {
@SerialName("voice") Voice, @SerialName("voice") Voice,
@SerialName("files") Files, @SerialName("files") Files,
@SerialName("simplexLinks") SimplexLinks, @SerialName("simplexLinks") SimplexLinks,
@SerialName("reports") Reports,
@SerialName("history") History; @SerialName("history") History;
override val hasParam: Boolean get() = when(this) { override val hasParam: Boolean get() = when(this) {
@ -5176,6 +5177,7 @@ enum class GroupFeature: Feature {
Voice -> true Voice -> true
Files -> true Files -> true
SimplexLinks -> true SimplexLinks -> true
Reports -> false
History -> false History -> false
} }
@ -5188,6 +5190,7 @@ enum class GroupFeature: Feature {
Voice -> generalGetString(MR.strings.voice_messages) Voice -> generalGetString(MR.strings.voice_messages)
Files -> generalGetString(MR.strings.files_and_media) Files -> generalGetString(MR.strings.files_and_media)
SimplexLinks -> generalGetString(MR.strings.simplex_links) SimplexLinks -> generalGetString(MR.strings.simplex_links)
Reports -> generalGetString(MR.strings.group_reports_member_reports)
History -> generalGetString(MR.strings.recent_history) History -> generalGetString(MR.strings.recent_history)
} }
@ -5200,6 +5203,7 @@ enum class GroupFeature: Feature {
Voice -> painterResource(MR.images.ic_keyboard_voice) Voice -> painterResource(MR.images.ic_keyboard_voice)
Files -> painterResource(MR.images.ic_draft) Files -> painterResource(MR.images.ic_draft)
SimplexLinks -> painterResource(MR.images.ic_link) SimplexLinks -> painterResource(MR.images.ic_link)
Reports -> painterResource(MR.images.ic_flag)
History -> painterResource(MR.images.ic_schedule) History -> painterResource(MR.images.ic_schedule)
} }
@ -5212,6 +5216,7 @@ enum class GroupFeature: Feature {
Voice -> painterResource(MR.images.ic_keyboard_voice_filled) Voice -> painterResource(MR.images.ic_keyboard_voice_filled)
Files -> painterResource(MR.images.ic_draft_filled) Files -> painterResource(MR.images.ic_draft_filled)
SimplexLinks -> painterResource(MR.images.ic_link) SimplexLinks -> painterResource(MR.images.ic_link)
Reports -> painterResource(MR.images.ic_flag_filled)
History -> painterResource(MR.images.ic_schedule_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.ON -> generalGetString(MR.strings.allow_to_send_simplex_links)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_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) { History -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history) GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_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.ON -> generalGetString(MR.strings.group_members_can_send_simplex_links)
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.simplex_links_are_prohibited_in_group) 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) { History -> when(enabled) {
GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members) 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) GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members)
@ -5400,6 +5413,7 @@ data class FullGroupPreferences(
val voice: RoleGroupPreference, val voice: RoleGroupPreference,
val files: RoleGroupPreference, val files: RoleGroupPreference,
val simplexLinks: RoleGroupPreference, val simplexLinks: RoleGroupPreference,
val reports: GroupPreference,
val history: GroupPreference, val history: GroupPreference,
) { ) {
fun toGroupPreferences(): GroupPreferences = fun toGroupPreferences(): GroupPreferences =
@ -5411,7 +5425,8 @@ data class FullGroupPreferences(
voice = voice, voice = voice,
files = files, files = files,
simplexLinks = simplexLinks, simplexLinks = simplexLinks,
history = history reports = reports,
history = history,
) )
companion object { companion object {
@ -5423,6 +5438,7 @@ data class FullGroupPreferences(
voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
reports = GroupPreference(GroupFeatureEnabled.ON),
history = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON),
) )
} }
@ -5437,6 +5453,7 @@ data class GroupPreferences(
val voice: RoleGroupPreference? = null, val voice: RoleGroupPreference? = null,
val files: RoleGroupPreference? = null, val files: RoleGroupPreference? = null,
val simplexLinks: RoleGroupPreference? = null, val simplexLinks: RoleGroupPreference? = null,
val reports: GroupPreference? = null,
val history: GroupPreference? = null, val history: GroupPreference? = null,
) { ) {
companion object { companion object {
@ -5448,6 +5465,7 @@ data class GroupPreferences(
voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), voice = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), files = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null), simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
reports = GroupPreference(GroupFeatureEnabled.ON),
history = GroupPreference(GroupFeatureEnabled.ON), history = GroupPreference(GroupFeatureEnabled.ON),
) )
} }

View file

@ -132,6 +132,11 @@ private fun GroupPreferencesLayout(
applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role))) 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) SectionDividerSpaced(true, maxBottomPadding = false)
val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) } val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) }
FeatureSection(GroupFeature.History, enableHistory, null, groupInfo, preferences, onTTLUpdated) { enable, _ -> FeatureSection(GroupFeature.History, enableHistory, null, groupInfo, preferences, onTTLUpdated) { enable, _ ->
@ -169,6 +174,7 @@ private fun FeatureSection(
feature.text, feature.text,
icon, icon,
iconTint, iconTint,
disabled = feature == GroupFeature.Reports, // remove in 6.4
checked = enableFeature.value == GroupFeatureEnabled.ON, checked = enableFeature.value == GroupFeatureEnabled.ON,
) { checked -> ) { checked ->
onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value) onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value)

View file

@ -400,7 +400,7 @@ fun ChatItemView(
val groupInfo = cItem.memberToModerate(cInfo)?.first val groupInfo = cItem.memberToModerate(cInfo)?.first
if (groupInfo != null) { if (groupInfo != null) {
ModerateItemAction(cItem, questionText = moderateMessageQuestionText(cInfo.featureEnabled(ChatFeature.FullDelete), 1), showMenu, deleteMessage) 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) ReportItemAction(cItem, composeState, showMenu)
} }
} }

View file

@ -2064,6 +2064,8 @@
<string name="prohibit_sending_simplex_links">Prohibit sending SimpleX links</string> <string name="prohibit_sending_simplex_links">Prohibit sending SimpleX links</string>
<string name="enable_sending_recent_history">Send up to 100 last messages to new members.</string> <string name="enable_sending_recent_history">Send up to 100 last messages to new members.</string>
<string name="disable_sending_recent_history">Do not send history to new members.</string> <string name="disable_sending_recent_history">Do not send history to new members.</string>
<string name="enable_sending_member_reports">Allow to report messsages to moderators.</string>
<string name="disable_sending_member_reports">Prohibit reporting messages to moderators.</string>
<string name="group_members_can_send_disappearing">Members can send disappearing messages.</string> <string name="group_members_can_send_disappearing">Members can send disappearing messages.</string>
<string name="disappearing_messages_are_prohibited">Disappearing messages are prohibited.</string> <string name="disappearing_messages_are_prohibited">Disappearing messages are prohibited.</string>
<string name="group_members_can_send_dms">Members can send direct messages.</string> <string name="group_members_can_send_dms">Members can send direct messages.</string>
@ -2082,6 +2084,8 @@
<string name="simplex_links_are_prohibited_in_group">SimpleX links are prohibited.</string> <string name="simplex_links_are_prohibited_in_group">SimpleX links are prohibited.</string>
<string name="recent_history_is_sent_to_new_members">Up to 100 last messages are sent to new members.</string> <string name="recent_history_is_sent_to_new_members">Up to 100 last messages are sent to new members.</string>
<string name="recent_history_is_not_sent_to_new_members">History is not sent to new members.</string> <string name="recent_history_is_not_sent_to_new_members">History is not sent to new members.</string>
<string name="group_members_can_send_reports">Members can report messsages to moderators.</string>
<string name="member_reports_are_prohibited">Reporting messages is prohibited in this group.</string>
<string name="delete_after">Delete after</string> <string name="delete_after">Delete after</string>
<string name="ttl_sec">%d sec</string> <string name="ttl_sec">%d sec</string>
<string name="ttl_s">%ds</string> <string name="ttl_s">%ds</string>

View file

@ -3066,7 +3066,7 @@ processChatCommand' vr = \case
findProhibited :: [ComposedMessageReq] -> Maybe GroupFeature findProhibited :: [ComposedMessageReq] -> Maybe GroupFeature
findProhibited = findProhibited =
foldr' 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 Nothing
processComposedMessages :: CM ChatResponse processComposedMessages :: CM ChatResponse
processComposedMessages = do processComposedMessages = do
@ -3974,6 +3974,7 @@ chatCommandP =
"/set disappear #" *> (SetGroupTimedMessages <$> displayNameP <*> (A.space *> timedTTLOnOffP)), "/set disappear #" *> (SetGroupTimedMessages <$> displayNameP <*> (A.space *> timedTTLOnOffP)),
"/set disappear @" *> (SetContactTimedMessages <$> displayNameP <*> optional (A.space *> timedMessagesEnabledP)), "/set disappear @" *> (SetContactTimedMessages <$> displayNameP <*> optional (A.space *> timedMessagesEnabledP)),
"/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))), "/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))),
"/set reports #" *> (SetGroupFeature (AGFNR SGFReports) <$> displayNameP <*> _strP),
"/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole), "/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole),
("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito, ("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito,
"/set device name " *> (SetLocalDeviceName <$> textP), "/set device name " *> (SetLocalDeviceName <$> textP),

View file

@ -320,12 +320,18 @@ quoteContent mc qmc ciFile_
qFileName = maybe qText (T.pack . getFileName) ciFile_ qFileName = maybe qText (T.pack . getFileName) ciFile_
qTextOrFile = if T.null qText then qFileName else qText qTextOrFile = if T.null qText then qFileName else qText
prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe MarkdownList -> Maybe f -> Maybe GroupFeature prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature
prohibitedGroupContent gInfo m mc ft file_ prohibitedGroupContent gInfo@GroupInfo {membership = GroupMember {memberRole = userRole}} m mc ft file_ sent
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice | isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice
| not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles | 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 | prohibitedSimplexLinks gInfo m ft = Just GFSimplexLinks
| otherwise = Nothing | 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 :: GroupInfo -> GroupMember -> Maybe MarkdownList -> Bool
prohibitedSimplexLinks gInfo m ft = prohibitedSimplexLinks gInfo m ft =

View file

@ -1720,7 +1720,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> Bool -> CM () newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> Bool -> CM ()
newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs forwarded newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs forwarded
| blockedByAdmin m = createBlockedByAdmin | 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 Just f -> rejected f
Nothing -> Nothing ->
withStore' (\db -> getCIModeration db vr user gInfo memberId sharedMsgId_) >>= \case 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_ withStore' $ \db -> deleteCIModeration db gInfo memberId sharedMsgId_
Nothing -> createContentItem Nothing -> createContentItem
where 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 timed' = if forwarded then rcvCITimed_ (Just Nothing) itemTTL else rcvGroupCITimed gInfo itemTTL
live' = fromMaybe False live_ live' = fromMaybe False live_
ExtMsgContent content mentions fInv_ itemTTL live_ = mcExtMsgContent mc ExtMsgContent content mentions fInv_ itemTTL live_ = mcExtMsgContent mc

View file

@ -410,8 +410,8 @@ forwardedToGroupMembers ms forwardedMsgs =
XGrpMemRestrict mId _ -> Just mId XGrpMemRestrict mId _ -> Just mId
_ -> Nothing _ -> Nothing
_ -> Nothing _ -> Nothing
hasReport = any isReport forwardedMsgs hasReport = any isReportEvent forwardedMsgs
isReport ChatMessage {chatMsgEvent} = case encoding @e of isReportEvent ChatMessage {chatMsgEvent} = case encoding @e of
SJson -> case chatMsgEvent of SJson -> case chatMsgEvent of
XMsgNew mc -> case mcExtMsgContent mc of XMsgNew mc -> case mcExtMsgContent mc of
ExtMsgContent {content = MCReport {}} -> True ExtMsgContent {content = MCReport {}} -> True
@ -600,6 +600,11 @@ isVoice = \case
MCVoice {} -> True MCVoice {} -> True
_ -> False _ -> False
isReport :: MsgContent -> Bool
isReport = \case
MCReport {} -> True
_ -> False
msgContentTag :: MsgContent -> MsgContentTag msgContentTag :: MsgContent -> MsgContentTag
msgContentTag = \case msgContentTag = \case
MCText _ -> MCText_ MCText _ -> MCText_

View file

@ -2453,10 +2453,10 @@ markReceivedGroupReportsDeleted db User {userId} GroupInfo {groupId, membership}
[sql| [sql|
UPDATE chat_items UPDATE chat_items
SET item_deleted = ?, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, updated_at = ? 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 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.Connection -> User -> GroupInfo -> GroupMemberId -> SharedMsgId -> ExceptT StoreError IO (CChatItem 'CTGroup)
getGroupChatItemBySharedMsgId db user@User {userId} g@GroupInfo {groupId} groupMemberId sharedMsgId = do getGroupChatItemBySharedMsgId db user@User {userId} g@GroupInfo {groupId} groupMemberId sharedMsgId = do

View file

@ -3322,6 +3322,15 @@ Query:
Plan: Plan:
SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?) 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: Query:
UPDATE chat_items UPDATE chat_items
SET item_deleted = ?, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, updated_at = ? SET item_deleted = ?, item_deleted_ts = ?, item_deleted_by_group_member_id = ?, updated_at = ?

View file

@ -149,6 +149,7 @@ data GroupFeature
| GFVoice | GFVoice
| GFFiles | GFFiles
| GFSimplexLinks | GFSimplexLinks
| GFReports
| GFHistory | GFHistory
deriving (Show) deriving (Show)
@ -160,6 +161,7 @@ data SGroupFeature (f :: GroupFeature) where
SGFVoice :: SGroupFeature 'GFVoice SGFVoice :: SGroupFeature 'GFVoice
SGFFiles :: SGroupFeature 'GFFiles SGFFiles :: SGroupFeature 'GFFiles
SGFSimplexLinks :: SGroupFeature 'GFSimplexLinks SGFSimplexLinks :: SGroupFeature 'GFSimplexLinks
SGFReports :: SGroupFeature 'GFReports
SGFHistory :: SGroupFeature 'GFHistory SGFHistory :: SGroupFeature 'GFHistory
deriving instance Show (SGroupFeature f) deriving instance Show (SGroupFeature f)
@ -185,6 +187,7 @@ groupFeatureNameText = \case
GFVoice -> "Voice messages" GFVoice -> "Voice messages"
GFFiles -> "Files and media" GFFiles -> "Files and media"
GFSimplexLinks -> "SimpleX links" GFSimplexLinks -> "SimpleX links"
GFReports -> "Member reports"
GFHistory -> "Recent history" GFHistory -> "Recent history"
groupFeatureNameText' :: SGroupFeature f -> Text groupFeatureNameText' :: SGroupFeature f -> Text
@ -208,11 +211,12 @@ allGroupFeatures =
AGF SGFVoice, AGF SGFVoice,
AGF SGFFiles, AGF SGFFiles,
AGF SGFSimplexLinks, AGF SGFSimplexLinks,
AGF SGFReports,
AGF SGFHistory AGF SGFHistory
] ]
groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f) 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 SGFTimedMessages -> timedMessages
SGFDirectMessages -> directMessages SGFDirectMessages -> directMessages
SGFFullDelete -> fullDelete SGFFullDelete -> fullDelete
@ -220,6 +224,7 @@ groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reac
SGFVoice -> voice SGFVoice -> voice
SGFFiles -> files SGFFiles -> files
SGFSimplexLinks -> simplexLinks SGFSimplexLinks -> simplexLinks
SGFReports -> reports
SGFHistory -> history SGFHistory -> history
toGroupFeature :: SGroupFeature f -> GroupFeature toGroupFeature :: SGroupFeature f -> GroupFeature
@ -231,6 +236,7 @@ toGroupFeature = \case
SGFVoice -> GFVoice SGFVoice -> GFVoice
SGFFiles -> GFFiles SGFFiles -> GFFiles
SGFSimplexLinks -> GFSimplexLinks SGFSimplexLinks -> GFSimplexLinks
SGFReports -> GFReports
SGFHistory -> GFHistory SGFHistory -> GFHistory
class GroupPreferenceI p where class GroupPreferenceI p where
@ -243,7 +249,7 @@ instance GroupPreferenceI (Maybe GroupPreferences) where
getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs) getGroupPreference pt prefs = fromMaybe (getGroupPreference pt defaultGroupPrefs) (groupPrefSel pt =<< prefs)
instance GroupPreferenceI FullGroupPreferences where 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 SGFTimedMessages -> timedMessages
SGFDirectMessages -> directMessages SGFDirectMessages -> directMessages
SGFFullDelete -> fullDelete SGFFullDelete -> fullDelete
@ -251,6 +257,7 @@ instance GroupPreferenceI FullGroupPreferences where
SGFVoice -> voice SGFVoice -> voice
SGFFiles -> files SGFFiles -> files
SGFSimplexLinks -> simplexLinks SGFSimplexLinks -> simplexLinks
SGFReports -> reports
SGFHistory -> history SGFHistory -> history
{-# INLINE getGroupPreference #-} {-# INLINE getGroupPreference #-}
@ -263,6 +270,7 @@ data GroupPreferences = GroupPreferences
voice :: Maybe VoiceGroupPreference, voice :: Maybe VoiceGroupPreference,
files :: Maybe FilesGroupPreference, files :: Maybe FilesGroupPreference,
simplexLinks :: Maybe SimplexLinksGroupPreference, simplexLinks :: Maybe SimplexLinksGroupPreference,
reports :: Maybe ReportsGroupPreference,
history :: Maybe HistoryGroupPreference history :: Maybe HistoryGroupPreference
} }
deriving (Eq, Show) deriving (Eq, Show)
@ -296,6 +304,7 @@ setGroupPreference_ f pref prefs =
SGFVoice -> prefs {voice = pref} SGFVoice -> prefs {voice = pref}
SGFFiles -> prefs {files = pref} SGFFiles -> prefs {files = pref}
SGFSimplexLinks -> prefs {simplexLinks = pref} SGFSimplexLinks -> prefs {simplexLinks = pref}
SGFReports -> prefs {reports = pref}
SGFHistory -> prefs {history = pref} SGFHistory -> prefs {history = pref}
setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences setGroupTimedMessagesPreference :: TimedMessagesGroupPreference -> Maybe GroupPreferences -> GroupPreferences
@ -325,6 +334,7 @@ data FullGroupPreferences = FullGroupPreferences
voice :: VoiceGroupPreference, voice :: VoiceGroupPreference,
files :: FilesGroupPreference, files :: FilesGroupPreference,
simplexLinks :: SimplexLinksGroupPreference, simplexLinks :: SimplexLinksGroupPreference,
reports :: ReportsGroupPreference,
history :: HistoryGroupPreference history :: HistoryGroupPreference
} }
deriving (Eq, Show) deriving (Eq, Show)
@ -377,22 +387,23 @@ defaultGroupPrefs =
FullGroupPreferences FullGroupPreferences
{ timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400}, { timedMessages = TimedMessagesGroupPreference {enable = FEOff, ttl = Just 86400},
directMessages = DirectMessagesGroupPreference {enable = FEOff, role = Nothing}, directMessages = DirectMessagesGroupPreference {enable = FEOff, role = Nothing},
fullDelete = FullDeleteGroupPreference {enable = FEOn, role = Just GRModerator}, fullDelete = FullDeleteGroupPreference {enable = FEOff, role = Nothing},
reactions = ReactionsGroupPreference {enable = FEOn}, reactions = ReactionsGroupPreference {enable = FEOn},
voice = VoiceGroupPreference {enable = FEOn, role = Nothing}, voice = VoiceGroupPreference {enable = FEOn, role = Nothing},
files = FilesGroupPreference {enable = FEOn, role = Nothing}, files = FilesGroupPreference {enable = FEOn, role = Nothing},
simplexLinks = SimplexLinksGroupPreference {enable = FEOn, role = Nothing}, simplexLinks = SimplexLinksGroupPreference {enable = FEOn, role = Nothing},
reports = ReportsGroupPreference {enable = FEOn},
history = HistoryGroupPreference {enable = FEOff} history = HistoryGroupPreference {enable = FEOff}
} }
emptyGroupPrefs :: GroupPreferences 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 -> GroupPreferences
businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice} = businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice} =
defaultBusinessGroupPrefs defaultBusinessGroupPrefs
{ timedMessages = Just TimedMessagesGroupPreference {enable = maybe FEOff enableFeature timedMessages, ttl = maybe Nothing prefParam timedMessages}, { 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}, reactions = Just ReactionsGroupPreference {enable = maybe FEOn enableFeature reactions},
voice = Just VoiceGroupPreference {enable = maybe FEOff enableFeature voice, role = Nothing} voice = Just VoiceGroupPreference {enable = maybe FEOff enableFeature voice, role = Nothing}
} }
@ -412,6 +423,7 @@ defaultBusinessGroupPrefs =
voice = Just $ VoiceGroupPreference FEOff Nothing, voice = Just $ VoiceGroupPreference FEOff Nothing,
files = Just $ FilesGroupPreference FEOn Nothing, files = Just $ FilesGroupPreference FEOn Nothing,
simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing, simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing,
reports = Just $ ReportsGroupPreference FEOff,
history = Just $ HistoryGroupPreference FEOn history = Just $ HistoryGroupPreference FEOn
} }
@ -512,6 +524,10 @@ data SimplexLinksGroupPreference = SimplexLinksGroupPreference
{enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole} {enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole}
deriving (Eq, Show) deriving (Eq, Show)
data ReportsGroupPreference = ReportsGroupPreference
{enable :: GroupFeatureEnabled}
deriving (Eq, Show)
data HistoryGroupPreference = HistoryGroupPreference data HistoryGroupPreference = HistoryGroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show) deriving (Eq, Show)
@ -550,6 +566,9 @@ instance HasField "enable" FilesGroupPreference GroupFeatureEnabled where
instance HasField "enable" SimplexLinksGroupPreference GroupFeatureEnabled where instance HasField "enable" SimplexLinksGroupPreference GroupFeatureEnabled where
hasField p@SimplexLinksGroupPreference {enable} = (\e -> p {enable = e}, enable) 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 instance HasField "enable" HistoryGroupPreference GroupFeatureEnabled where
hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable) hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable)
@ -595,6 +614,12 @@ instance GroupFeatureI 'GFSimplexLinks where
groupPrefParam _ = Nothing groupPrefParam _ = Nothing
groupPrefRole SimplexLinksGroupPreference {role} = role groupPrefRole SimplexLinksGroupPreference {role} = role
instance GroupFeatureI 'GFReports where
type GroupFeaturePreference 'GFReports = ReportsGroupPreference
sGroupFeature = SGFReports
groupPrefParam _ = Nothing
groupPrefRole _ = Nothing
instance GroupFeatureI 'GFHistory where instance GroupFeatureI 'GFHistory where
type GroupFeaturePreference 'GFHistory = HistoryGroupPreference type GroupFeaturePreference 'GFHistory = HistoryGroupPreference
sGroupFeature = SGFHistory sGroupFeature = SGFHistory
@ -607,6 +632,8 @@ instance GroupFeatureNoRoleI 'GFFullDelete
instance GroupFeatureNoRoleI 'GFReactions instance GroupFeatureNoRoleI 'GFReactions
instance GroupFeatureNoRoleI 'GFReports
instance GroupFeatureNoRoleI 'GFHistory instance GroupFeatureNoRoleI 'GFHistory
instance HasField "role" DirectMessagesGroupPreference (Maybe GroupMemberRole) where instance HasField "role" DirectMessagesGroupPreference (Maybe GroupMemberRole) where
@ -761,6 +788,7 @@ mergeGroupPreferences groupPreferences =
voice = pref SGFVoice, voice = pref SGFVoice,
files = pref SGFFiles, files = pref SGFFiles,
simplexLinks = pref SGFSimplexLinks, simplexLinks = pref SGFSimplexLinks,
reports = pref SGFReports,
history = pref SGFHistory history = pref SGFHistory
} }
where where
@ -777,6 +805,7 @@ toGroupPreferences groupPreferences =
voice = pref SGFVoice, voice = pref SGFVoice,
files = pref SGFFiles, files = pref SGFFiles,
simplexLinks = pref SGFSimplexLinks, simplexLinks = pref SGFSimplexLinks,
reports = pref SGFReports,
history = pref SGFHistory history = pref SGFHistory
} }
where where
@ -885,6 +914,8 @@ $(J.deriveJSON defaultJSON ''FilesGroupPreference)
$(J.deriveJSON defaultJSON ''SimplexLinksGroupPreference) $(J.deriveJSON defaultJSON ''SimplexLinksGroupPreference)
$(J.deriveJSON defaultJSON ''ReportsGroupPreference)
$(J.deriveJSON defaultJSON ''HistoryGroupPreference) $(J.deriveJSON defaultJSON ''HistoryGroupPreference)
$(J.deriveJSON defaultJSON ''GroupPreferences) $(J.deriveJSON defaultJSON ''GroupPreferences)

View file

@ -559,7 +559,7 @@ testGroup2 =
] ]
dan <##> alice dan <##> alice
-- show last messages -- 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 alice -- these strings are expected in any order because of sorting by time and rounding of time for sent
<##? <##?
( map (ConsoleString . ("#club " <> )) groupFeatureStrs ( map (ConsoleString . ("#club " <> )) groupFeatureStrs
@ -1226,7 +1226,7 @@ testGroupMessageDelete =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
threadDelay 1000000 threadDelay 1000000
-- alice, bob: msg id 5, cath: msg id 4 (after group invitations & group events) -- alice, bob: msg id 5, cath: msg id 4 (after group invitations & group events)
alice #> "#team hello!" alice #> "#team hello!"
@ -1238,7 +1238,7 @@ testGroupMessageDelete =
msgItemId1 <- lastItemId alice msgItemId1 <- lastItemId alice
alice #$> ("/_delete item #1 " <> msgItemId1 <> " internal", id, "message deleted") 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!")]) bob #$> ("/_get chat #1 count=1", chat, [(0, "hello!")])
cath #$> ("/_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 msgItemId2 <- lastItemId alice
alice #$> ("/_delete item #1 " <> msgItemId2 <> " internal", id, "message deleted") 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!"))]) 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!"))]) 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 $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
threadDelay 1000000 threadDelay 1000000
alice #> "#team hello" alice #> "#team hello"
@ -1348,7 +1348,7 @@ testGroupMessageDeleteMultipleManyBatches =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
bob ##> "/set receipts all off" bob ##> "/set receipts all off"
bob <## "ok" bob <## "ok"
@ -1499,9 +1499,9 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile
alice ##> "/g team" alice ##> "/g team"
alice <## "group #team is created" alice <## "group #team is created"
alice <## "to add members use /a team <name> or /create link #team" alice <## "to add members use /a team <name> or /create link #team"
alice ##> "/set delete #team off" -- alice ##> "/set delete #team off"
alice <## "updated group preferences:" -- alice <## "updated group preferences:"
alice <## "Full deletion: off" -- alice <## "Full deletion: off"
addMember "team" alice bob GRAdmin addMember "team" alice bob GRAdmin
bob ##> "/j team" bob ##> "/j team"
concurrentlyN_ concurrentlyN_
@ -1561,6 +1561,7 @@ testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile
alice <## "Voice messages: on" alice <## "Voice messages: on"
alice <## "Files and media: on" alice <## "Files and media: on"
alice <## "SimpleX links: on" alice <## "SimpleX links: on"
alice <## "Member reports: on"
alice <## "Recent history: on" alice <## "Recent history: on"
bobAddedDan :: HasCallStack => TestCC -> IO () bobAddedDan :: HasCallStack => TestCC -> IO ()
bobAddedDan cc = do bobAddedDan cc = do
@ -1572,7 +1573,7 @@ testGroupModerate =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
alice ##> "/mr team cath member" alice ##> "/mr team cath member"
concurrentlyN_ concurrentlyN_
[ alice <## "#team: you changed the role of cath from admin to member", [ alice <## "#team: you changed the role of cath from admin to member",
@ -1604,7 +1605,7 @@ testGroupModerateOwn =
testChat2 aliceProfile bobProfile $ testChat2 aliceProfile bobProfile $
\alice bob -> do \alice bob -> do
createGroup2 "team" alice bob createGroup2 "team" alice bob
disableFullDeletion2 "team" alice bob -- disableFullDeletion2 "team" alice bob
threadDelay 1000000 threadDelay 1000000
alice #> "#team hello" alice #> "#team hello"
bob <# "#team alice> hello" bob <# "#team alice> hello"
@ -1619,7 +1620,7 @@ testGroupModerateMultiple =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
threadDelay 1000000 threadDelay 1000000
alice #> "#team hello" alice #> "#team hello"
@ -1655,7 +1656,7 @@ testGroupModerateFullDelete =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
alice ##> "/mr team cath member" alice ##> "/mr team cath member"
concurrentlyN_ concurrentlyN_
[ alice <## "#team: you changed the role of cath from admin to member", [ 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 "alice" aliceProfile $ \alice -> do
withNewTestChatCfg ps cfg "bob" bobProfile $ \bob -> do withNewTestChatCfg ps cfg "bob" bobProfile $ \bob -> do
createGroup2 "team" alice bob createGroup2 "team" alice bob
disableFullDeletion2 "team" alice bob -- disableFullDeletion2 "team" alice bob
withNewTestChatCfg ps cfg "cath" cathProfile $ \cath -> do withNewTestChatCfg ps cfg "cath" cathProfile $ \cath -> do
connectUsers alice cath connectUsers alice cath
addMember "team" alice cath GRMember addMember "team" alice cath GRMember
@ -1742,7 +1743,7 @@ testGroupDelayedModerationFullDelete ps = do
withNewTestChatCfg ps cfg "alice" aliceProfile $ \alice -> do withNewTestChatCfg ps cfg "alice" aliceProfile $ \alice -> do
withNewTestChatCfg ps cfg "bob" bobProfile $ \bob -> do withNewTestChatCfg ps cfg "bob" bobProfile $ \bob -> do
createGroup2 "team" alice bob createGroup2 "team" alice bob
disableFullDeletion2 "team" alice bob -- disableFullDeletion2 "team" alice bob
withNewTestChatCfg ps cfg "cath" cathProfile $ \cath -> do withNewTestChatCfg ps cfg "cath" cathProfile $ \cath -> do
connectUsers alice cath connectUsers alice cath
addMember "team" alice cath GRMember addMember "team" alice cath GRMember
@ -3998,6 +3999,12 @@ testGroupMsgForwardReport =
cath <## "#team: alice changed the role of bob from admin to moderator" 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 ##> "/report #team content hi there"
cath <# "#team > bob hi there" cath <# "#team > bob hi there"
cath <## " report content" cath <## " report content"
@ -4127,7 +4134,7 @@ testGroupMsgForwardDeletion =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
setupGroupForwarding3 "team" alice bob cath setupGroupForwarding3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
bob #> "#team hi there" bob #> "#team hi there"
alice <# "#team bob> hi there" alice <# "#team bob> hi there"
@ -4845,7 +4852,7 @@ testGroupHistoryDeletedMessage =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup2 "team" alice bob createGroup2 "team" alice bob
disableFullDeletion2 "team" alice bob -- disableFullDeletion2 "team" alice bob
alice #> "#team hello" alice #> "#team hello"
bob <# "#team alice> hello" bob <# "#team alice> hello"
@ -5535,7 +5542,7 @@ testBlockForAllMarkedBlocked =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
threadDelay 1000000 threadDelay 1000000
@ -5623,7 +5630,7 @@ testBlockForAllFullDelete =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
alice ##> "/set delete #team on" alice ##> "/set delete #team on"
alice <## "updated group preferences:" alice <## "updated group preferences:"
@ -5704,7 +5711,7 @@ testBlockForAllAnotherAdminUnblocks =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
bob #> "#team 1" bob #> "#team 1"
[alice, cath] *<# "#team bob> 1" [alice, cath] *<# "#team bob> 1"
@ -5733,7 +5740,7 @@ testBlockForAllBeforeJoining =
testChat4 aliceProfile bobProfile cathProfile danProfile $ testChat4 aliceProfile bobProfile cathProfile danProfile $
\alice bob cath dan -> do \alice bob cath dan -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
bob #> "#team 1" bob #> "#team 1"
[alice, cath] *<# "#team bob> 1" [alice, cath] *<# "#team bob> 1"
@ -5802,7 +5809,7 @@ testBlockForAllCantRepeat =
testChat3 aliceProfile bobProfile cathProfile $ testChat3 aliceProfile bobProfile cathProfile $
\alice bob cath -> do \alice bob cath -> do
createGroup3 "team" alice bob cath createGroup3 "team" alice bob cath
disableFullDeletion3 "team" alice bob cath -- disableFullDeletion3 "team" alice bob cath
alice ##> "/unblock for all #team bob" alice ##> "/unblock for all #team bob"
alice <## "bad chat command: already unblocked" alice <## "bad chat command: already unblocked"
@ -5919,7 +5926,7 @@ testGroupMemberReports =
testChat4 aliceProfile bobProfile cathProfile danProfile $ testChat4 aliceProfile bobProfile cathProfile danProfile $
\alice bob cath dan -> do \alice bob cath dan -> do
createGroup3 "jokes" alice bob cath createGroup3 "jokes" alice bob cath
disableFullDeletion3 "jokes" alice bob cath -- disableFullDeletion3 "jokes" alice bob cath
alice ##> "/mr jokes bob moderator" alice ##> "/mr jokes bob moderator"
concurrentlyN_ concurrentlyN_
[ alice <## "#jokes: you changed the role of bob from admin to moderator", [ alice <## "#jokes: you changed the role of bob from admin to moderator",

View file

@ -300,11 +300,12 @@ groupFeatures'' dir =
[ ((dir, e2eeInfoNoPQStr), Nothing, Nothing), [ ((dir, e2eeInfoNoPQStr), Nothing, Nothing),
((dir, "Disappearing messages: off"), Nothing, Nothing), ((dir, "Disappearing messages: off"), Nothing, Nothing),
((dir, "Direct messages: on"), 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, "Message reactions: on"), Nothing, Nothing),
((dir, "Voice messages: on"), Nothing, Nothing), ((dir, "Voice messages: on"), Nothing, Nothing),
((dir, "Files and media: on"), Nothing, Nothing), ((dir, "Files and media: on"), Nothing, Nothing),
((dir, "SimpleX links: on"), Nothing, Nothing), ((dir, "SimpleX links: on"), Nothing, Nothing),
((dir, "Member reports: on"), Nothing, Nothing),
((dir, "Recent history: on"), Nothing, Nothing) ((dir, "Recent history: on"), Nothing, Nothing)
] ]

View file

@ -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}} testChatPreferences = Just Preferences {voice = Just VoicePreference {allow = FAYes}, fullDelete = Nothing, timedMessages = Nothing, calls = Nothing, reactions = Just ReactionsPreference {allow = FAYes}}
testGroupPreferences :: Maybe GroupPreferences 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
testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences} testProfile = Profile {displayName = "alice", fullName = "Alice", image = Just (ImageData "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="), contactLink = Nothing, preferences = testChatPreferences}