core: support chats in channels, send as owner in support chats (#6870)

* core: test support chats in channels, CLI defaults to sending as member in support chat

* ui: enable support chats in channels

* use correct scope when sending from UI

* more readable

* remove test output

* show member support chat in channels

* preference for support chats

* ios: types for support preference

* mp: support preference types

* show support preference in UI

* fix ios

* make support preference optional in JSON parser

* update string

* change strings, pass parameters to prefs

* refactor kotlin

* take support preference into account

* refactor core

* do not show broadcast placeholder in support scope

* move role check, add pref check on update

* support preference test (failing)

* fix version

* fix tests

* warning alert when enabling chats with admins

* revert on dismiss

* update text and icons

* query plans

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
Evgeny
2026-04-26 14:37:16 +01:00
committed by GitHub
parent 504ef253cb
commit 63c278818e
34 changed files with 503 additions and 93 deletions
+14 -6
View File
@@ -625,7 +625,10 @@ processChatCommand vr nm = \case
mapM_ assertNoMentions cms
withContactLock "sendMessage" chatId $
sendContactContentMessages user chatId live itemTTL (L.map composedMessageReq cms)
SRGroup chatId gsScope asGroup ->
SRGroup chatId gsScope asGroup -> do
case gsScope of
Just (GCSMemberSupport _) -> when asGroup $ throwCmdError "cannot send as group in support scope"
Nothing -> pure ()
withGroupLock "sendMessage" chatId $ do
(gInfo, cmrs) <- withFastStore $ \db -> do
g <- getGroupInfo db vr user chatId
@@ -2375,7 +2378,7 @@ processChatCommand vr nm = \case
forM scope_ $ \(GSNMemberSupport mName_) ->
GCSMemberSupport <$> mapM (getGroupMemberIdByName db user gId) mName_
(gInfo, cScope_,) <$> liftIO (getMessageMentions db user gId msg)
let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo)
let sendRef = SRGroup (groupId' gInfo) cScope_ (sendAsGroup' gInfo cScope_)
processChatCommand vr nm $ APISendMessages sendRef False Nothing [ComposedMessage Nothing Nothing mc mentions]
SNLocal -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
@@ -3128,7 +3131,7 @@ processChatCommand vr nm = \case
qiId <- getGroupChatItemIdByText db user gId cName quotedMsg
(gInfo, qiId,) <$> liftIO (getMessageMentions db user gId msg)
let mc = MCText msg
processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions]
processChatCommand vr nm $ APISendMessages (SRGroup (groupId' gInfo) Nothing (sendAsGroup' gInfo Nothing)) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions]
ClearNoteFolder -> withUser $ \user -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
processChatCommand vr nm $ APIClearChat (ChatRef CTLocal folderId Nothing)
@@ -3404,7 +3407,7 @@ processChatCommand vr nm = \case
_ -> throwCmdError "not supported"
pure $ ChatRef cType chatId Nothing
getSendAsGroup :: User -> ChatRef -> CM ShowGroupAsSender
getSendAsGroup user' (ChatRef CTGroup chatId _) = sendAsGroup' <$> withFastStore (\db -> getGroupInfo db vr user' chatId)
getSendAsGroup user' (ChatRef CTGroup chatId scope) = (`sendAsGroup'` scope) <$> withFastStore (\db -> getGroupInfo db vr user' chatId)
getSendAsGroup _ _ = pure False
getChatRefAndMentions :: User -> ChatName -> Text -> CM (ChatRef, Map MemberName GroupMemberId)
getChatRefAndMentions user cName msg = do
@@ -4490,7 +4493,7 @@ processChatCommand vr nm = \case
ChatRef CTDirect cId _ -> a $ SRDirect cId
ChatRef CTGroup gId scope -> do
gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
a $ SRGroup gId scope (sendAsGroup' gInfo)
a $ SRGroup gId scope (sendAsGroup' gInfo scope)
_ -> throwCmdError "not supported"
getSharedMsgId :: CM SharedMsgId
getSharedMsgId = do
@@ -5020,7 +5023,7 @@ chatCommandP =
("/help" <|> "/h") $> ChatHelp HSMain,
("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile),
"/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP),
("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> groupProfile),
("/public group" <|> "/pg") *> (NewPublicGroup <$> incognitoP <* " relays=" <*> strP <* A.space <* char_ '#' <*> channelProfile),
"/_public group " *> (APINewPublicGroup <$> A.decimal <*> incognitoOnOffP <*> _strP <* A.space <*> jsonP),
"/_get relays #" *> (APIGetGroupRelays <$> A.decimal),
("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)),
@@ -5150,6 +5153,7 @@ chatCommandP =
"/set disappear @" *> (SetContactTimedMessages <$> displayNameP <*> optional (A.space *> timedMessagesEnabledP)),
"/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))),
"/set reports #" *> (SetGroupFeature (AGFNR SGFReports) <$> displayNameP <*> _strP),
"/set support #" *> (SetGroupFeature (AGFNR SGFSupport) <$> displayNameP <*> (A.space *> strP)),
"/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole),
"/set admission review #" *> (SetGroupMemberAdmissionReview <$> displayNameP <*> (A.space *> memberCriteriaP)),
("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito,
@@ -5287,6 +5291,10 @@ chatCommandP =
history = Just HistoryGroupPreference {enable = FEOn}
}
pure GroupProfile {displayName = gName, fullName = "", shortDescr, description = Nothing, image = Nothing, publicGroup = Nothing, groupPreferences, memberAdmission = Nothing}
channelProfile = do
p@GroupProfile {groupPreferences = prefs_} <- groupProfile
let prefs = (fromMaybe emptyGroupPrefs prefs_) {support = Just SupportGroupPreference {enable = FEOff}} :: GroupPreferences
pure p {groupPreferences = Just prefs}
memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing)
shortDescrP = do
descr <- A.takeWhile1 isSpace *> (T.dropWhileEnd isSpace <$> textP) <|> pure ""
+5
View File
@@ -338,12 +338,17 @@ quoteContent mc qmc ciFile_
prohibitedGroupContent :: GroupInfo -> GroupMember -> Maybe GroupChatScopeInfo -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature
prohibitedGroupContent gInfo@GroupInfo {membership = mem@GroupMember {memberRole = userRole}} m scopeInfo mc ft file_ sent
| not supportAllowed = Just GFSupport
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) && not hostApprovalVoice = Just GFVoice
| isNothing scopeInfo && not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles
| isNothing scopeInfo && isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports
| isNothing scopeInfo && prohibitedSimplexLinks gInfo m mc ft = Just GFSimplexLinks
| otherwise = Nothing
where
supportAllowed = case scopeInfo of
Just (GCSIMemberSupport scopeMem_) ->
groupFeatureAllowed SGFSupport gInfo || isJust (supportChat $ fromMaybe mem scopeMem_)
Nothing -> True
hostApprovalVoice
| sent = userRole >= GRAdmin && sendApprovalPhase
| otherwise = memberCategory m == GCHostMember && hostApprovalPhase
+32 -17
View File
@@ -1535,7 +1535,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
memberCanSend :: Maybe GroupMember -> Maybe MsgScope -> CM (Maybe DeliveryTaskContext) -> CM (Maybe DeliveryTaskContext)
memberCanSend Nothing _ a = a -- channel message - was previously checked and allowed by relay
memberCanSend (Just m@GroupMember {memberRole}) msgScope a = case msgScope of
Just MSMember {} -> a
Just (MSMember mId)
| sameMemberId mId m || memberRole >= GRModerator -> a
| otherwise -> messageError "member is not allowed to send to this support chat" $> Nothing
Nothing
| memberRole > GRObserver || memberPending m -> a
| otherwise -> messageError "member is not allowed to send messages" $> Nothing
@@ -1837,13 +1839,19 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- This patches initial sharedMsgId into chat item when locally deleted chat item
-- received an update from the sender, so that it can be referenced later (e.g. by broadcast delete).
-- Chat item and update message which created it will have different sharedMsgId in this case...
let timed_ = rcvContactCITimed ct ttl
ts = ciContentTexts content
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateDirectChatItem' db user contactId ci content True live Nothing Nothing
toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci')
if isVoice mc && not (featureAllowed SCFVoice forContact ct)
then do
let ciContent = ciContentNoParse $ CIRcvChatFeatureRejected CFVoice
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs ciContent Nothing Nothing False M.empty
toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci)
else do
let timed_ = rcvContactCITimed ct ttl
ts = ciContentTexts content
(ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateDirectChatItem' db user contactId ci content True live Nothing Nothing
toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci')
where
brokerTs = metaBrokerTs msgMeta
content = CIRcvMsgContent mc
@@ -2073,15 +2081,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m mc msgScope_
pure (gInfo', CDGroupRcv gInfo' scopeInfo m', mentions', scopeInfo)
Nothing -> pure (gInfo, CDChannelRcv gInfo Nothing, mentions, Nothing)
(ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions'
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateGroupChatItem db user groupId ci content True live Nothing
ci'' <- case chatDir of
CDGroupRcv gi' _ m' -> blockedMemberCI gi' m' ci'
CDChannelRcv {} -> pure ci'
toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci'')
pure $ Just $ infoToDeliveryContext gInfo' scopeInfo showGroupAsSender
case m_ >>= \m -> prohibitedGroupContent gInfo' m scopeInfo mc ft_ (Nothing :: Maybe String) False of
Just f -> do
let ciContent = ciContentNoParse $ CIRcvGroupFeatureRejected f
(ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs ciContent Nothing timed_ False M.empty
groupMsgToView cInfo ci
pure Nothing
Nothing -> do
(ci, cInfo) <- saveRcvChatItem' user chatDir msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions'
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateGroupChatItem db user groupId ci content True live Nothing
ci'' <- case chatDir of
CDGroupRcv gi' _ m' -> blockedMemberCI gi' m' ci'
CDChannelRcv {} -> pure ci'
toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci'')
pure $ Just $ infoToDeliveryContext gInfo' scopeInfo showGroupAsSender
where
content = CIRcvMsgContent mc
ts@(_, ft_) = msgContentTexts mc
+5
View File
@@ -119,6 +119,11 @@ checkChatType x = case testEquality (chatTypeI @c) (chatTypeI @c') of
data GroupChatScope = GCSMemberSupport {groupMemberId_ :: Maybe GroupMemberId} -- Nothing means own conversation with support
deriving (Eq, Show, Ord)
sendAsGroup' :: GroupInfo -> Maybe GroupChatScope -> Bool
sendAsGroup' gInfo@GroupInfo {membership} scope = case scope of
Nothing -> useRelays' gInfo && memberRole' membership == GROwner
Just (GCSMemberSupport _) -> False
data GroupChatScopeTag
= GCSTMemberSupport_
deriving (Eq, Show)
+5 -2
View File
@@ -1555,6 +1555,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
(Binary invId, groupLink, minVersion reqChatVRange, maxVersion reqChatVRange, groupId)
insertOwner_ currentTs groupId = do
let MemberIdRole {memberId, memberRole} = fromMember
VersionRange minV maxV = reqChatVRange
(localDisplayName, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs
indexInGroup <- getUpdateNextIndexInGroup_ db groupId
liftIO $ do
@@ -1563,11 +1564,13 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
[sql|
INSERT INTO group_members
( group_id, index_in_group, member_id, member_role, member_category, member_status,
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
peer_chat_min_version, peer_chat_max_version)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
( (groupId, indexInGroup, memberId, memberRole, GCHostMember, GSMemAccepted)
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
:. (minV, maxV)
)
insertedRowId db
@@ -523,8 +523,9 @@ SEARCH users USING COVERING INDEX sqlite_autoindex_users_1 (contact_id=?)
Query:
INSERT INTO group_members
( group_id, index_in_group, member_id, member_role, member_category, member_status,
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at,
peer_chat_min_version, peer_chat_max_version)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
Plan:
SEARCH group_relays USING COVERING INDEX idx_group_relays_group_member_id (group_member_id=?)
-3
View File
@@ -494,9 +494,6 @@ data GroupInfo = GroupInfo
useRelays' :: GroupInfo -> Bool
useRelays' GroupInfo {useRelays} = isTrue useRelays
sendAsGroup' :: GroupInfo -> Bool
sendAsGroup' gInfo@GroupInfo {membership} = useRelays' gInfo && memberRole' membership == GROwner
groupId' :: GroupInfo -> GroupId
groupId' GroupInfo {groupId} = groupId
+39 -8
View File
@@ -176,6 +176,7 @@ data GroupFeature
| GFSimplexLinks
| GFReports
| GFHistory
| GFSupport
| GFSessions
| GFComments
deriving (Show)
@@ -190,6 +191,7 @@ data SGroupFeature (f :: GroupFeature) where
SGFSimplexLinks :: SGroupFeature 'GFSimplexLinks
SGFReports :: SGroupFeature 'GFReports
SGFHistory :: SGroupFeature 'GFHistory
SGFSupport :: SGroupFeature 'GFSupport
SGFSessions :: SGroupFeature 'GFSessions
SGFComments :: SGroupFeature 'GFComments
@@ -218,6 +220,7 @@ groupFeatureNameText = \case
GFSimplexLinks -> "SimpleX links"
GFReports -> "Member reports"
GFHistory -> "Recent history"
GFSupport -> "Chat with admins"
GFSessions -> "Chat sessions"
GFComments -> "Comments"
@@ -248,11 +251,12 @@ allGroupFeatures =
AGF SGFFiles,
AGF SGFSimplexLinks,
AGF SGFReports,
AGF SGFHistory
AGF SGFHistory,
AGF SGFSupport
]
groupPrefSel :: SGroupFeature f -> GroupPreferences -> Maybe (GroupFeaturePreference f)
groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, sessions, comments} = case f of
groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of
SGFTimedMessages -> timedMessages
SGFDirectMessages -> directMessages
SGFFullDelete -> fullDelete
@@ -262,6 +266,7 @@ groupPrefSel f GroupPreferences {timedMessages, directMessages, fullDelete, reac
SGFSimplexLinks -> simplexLinks
SGFReports -> reports
SGFHistory -> history
SGFSupport -> support
SGFSessions -> sessions
SGFComments -> comments
@@ -276,6 +281,7 @@ toGroupFeature = \case
SGFSimplexLinks -> GFSimplexLinks
SGFReports -> GFReports
SGFHistory -> GFHistory
SGFSupport -> GFSupport
SGFSessions -> GFSessions
SGFComments -> GFComments
@@ -289,7 +295,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, reports, history, sessions, comments} = case f of
getGroupPreference f FullGroupPreferences {timedMessages, directMessages, fullDelete, reactions, voice, files, simplexLinks, reports, history, support, sessions, comments} = case f of
SGFTimedMessages -> timedMessages
SGFDirectMessages -> directMessages
SGFFullDelete -> fullDelete
@@ -299,6 +305,7 @@ instance GroupPreferenceI FullGroupPreferences where
SGFSimplexLinks -> simplexLinks
SGFReports -> reports
SGFHistory -> history
SGFSupport -> support
SGFSessions -> sessions
SGFComments -> comments
{-# INLINE getGroupPreference #-}
@@ -314,6 +321,7 @@ data GroupPreferences = GroupPreferences
simplexLinks :: Maybe SimplexLinksGroupPreference,
reports :: Maybe ReportsGroupPreference,
history :: Maybe HistoryGroupPreference,
support :: Maybe SupportGroupPreference,
sessions :: Maybe SessionsGroupPreference,
comments :: Maybe CommentsGroupPreference,
commands :: Maybe [ChatBotCommand]
@@ -365,6 +373,7 @@ setGroupPreference_ f pref prefs =
SGFSimplexLinks -> prefs {simplexLinks = pref}
SGFReports -> prefs {reports = pref}
SGFHistory -> prefs {history = pref}
SGFSupport -> prefs {support = pref}
SGFSessions -> prefs {sessions = pref}
SGFComments -> prefs {comments = pref}
@@ -408,6 +417,7 @@ data FullGroupPreferences = FullGroupPreferences
simplexLinks :: SimplexLinksGroupPreference,
reports :: ReportsGroupPreference,
history :: HistoryGroupPreference,
support :: SupportGroupPreference,
sessions :: SessionsGroupPreference,
comments :: CommentsGroupPreference,
commands :: ListDef ChatBotCommand
@@ -478,13 +488,14 @@ defaultGroupPrefs =
simplexLinks = SimplexLinksGroupPreference {enable = FEOn, role = Nothing},
reports = ReportsGroupPreference {enable = FEOn},
history = HistoryGroupPreference {enable = FEOff},
support = SupportGroupPreference {enable = FEOn},
sessions = SessionsGroupPreference {enable = FEOff, role = Nothing},
comments = CommentsGroupPreference {enable = FEOff, duration = Nothing},
commands = ListDef []
}
emptyGroupPrefs :: GroupPreferences
emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
emptyGroupPrefs = GroupPreferences Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
businessGroupPrefs :: Preferences -> GroupPreferences
businessGroupPrefs Preferences {timedMessages, fullDelete, reactions, voice, files, sessions, commands} =
@@ -515,6 +526,7 @@ defaultBusinessGroupPrefs =
simplexLinks = Just $ SimplexLinksGroupPreference FEOn Nothing,
reports = Just $ ReportsGroupPreference FEOff,
history = Just $ HistoryGroupPreference FEOn,
support = Just $ SupportGroupPreference FEOn,
sessions = Just $ SessionsGroupPreference FEOn Nothing,
comments = Just $ CommentsGroupPreference FEOff Nothing,
commands = Nothing
@@ -647,6 +659,10 @@ data HistoryGroupPreference = HistoryGroupPreference
{enable :: GroupFeatureEnabled}
deriving (Eq, Show)
data SupportGroupPreference = SupportGroupPreference
{enable :: GroupFeatureEnabled}
deriving (Eq, Show)
data SessionsGroupPreference = SessionsGroupPreference
{enable :: GroupFeatureEnabled, role :: Maybe GroupMemberRole}
deriving (Eq, Show)
@@ -699,6 +715,9 @@ instance HasField "enable" ReportsGroupPreference GroupFeatureEnabled where
instance HasField "enable" HistoryGroupPreference GroupFeatureEnabled where
hasField p@HistoryGroupPreference {enable} = (\e -> p {enable = e}, enable)
instance HasField "enable" SupportGroupPreference GroupFeatureEnabled where
hasField p@SupportGroupPreference {enable} = (\e -> p {enable = e}, enable)
instance HasField "enable" SessionsGroupPreference GroupFeatureEnabled where
hasField p@SessionsGroupPreference {enable} = (\e -> p {enable = e}, enable)
@@ -759,6 +778,12 @@ instance GroupFeatureI 'GFHistory where
groupPrefParam _ = Nothing
groupPrefRole _ = Nothing
instance GroupFeatureI 'GFSupport where
type GroupFeaturePreference 'GFSupport = SupportGroupPreference
sGroupFeature = SGFSupport
groupPrefParam _ = Nothing
groupPrefRole _ = Nothing
instance GroupFeatureI 'GFSessions where
type GroupFeaturePreference 'GFSessions = SessionsGroupPreference
sGroupFeature = SGFSessions
@@ -781,6 +806,8 @@ instance GroupFeatureNoRoleI 'GFReports
instance GroupFeatureNoRoleI 'GFHistory
instance GroupFeatureNoRoleI 'GFSupport
instance GroupFeatureNoRoleI 'GFComments
instance HasField "role" DirectMessagesGroupPreference (Maybe GroupMemberRole) where
@@ -973,6 +1000,7 @@ mergeGroupPreferences groupPreferences =
simplexLinks = pref SGFSimplexLinks,
reports = pref SGFReports,
history = pref SGFHistory,
support = pref SGFSupport,
sessions = pref SGFSessions,
comments = pref SGFComments,
commands = ListDef $ fromMaybe [] $ groupPreferences >>= commands_
@@ -993,6 +1021,7 @@ toGroupPreferences groupPreferences@FullGroupPreferences {commands = ListDef cmd
simplexLinks = pref SGFSimplexLinks,
reports = pref SGFReports,
history = pref SGFHistory,
support = pref SGFSupport,
sessions = pref SGFSessions,
comments = pref SGFComments,
commands = Just cmds
@@ -1123,11 +1152,13 @@ $(J.deriveJSON defaultJSON ''ReportsGroupPreference)
$(J.deriveJSON defaultJSON ''HistoryGroupPreference)
$(J.deriveToJSON defaultJSON ''SessionsGroupPreference)
$(J.deriveToJSON defaultJSON ''SupportGroupPreference)
instance FromJSON SessionsGroupPreference where
parseJSON v = $(J.mkParseJSON defaultJSON ''SessionsGroupPreference) v
omittedField = Just SessionsGroupPreference {enable = FEOff, role = Nothing}
instance FromJSON SupportGroupPreference where
parseJSON v = $(J.mkParseJSON defaultJSON ''SupportGroupPreference) v
omittedField = Just SupportGroupPreference {enable = FEOn}
$(J.deriveJSON defaultJSON ''SessionsGroupPreference)
$(J.deriveToJSON defaultJSON ''CommentsGroupPreference)