From 2df13dad36f306f5f73a549f9fd4d376f4f6df32 Mon Sep 17 00:00:00 2001 From: sh <37271604+shumvgolove@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:10:19 +0000 Subject: [PATCH] core: add custom data commands, fix groups parser (#6691) * core: add custom data commands, fix groups parser - Add APISetGroupCustomData and APISetContactCustomData to ChatCommand, with parsers (/_set custom #, /_set custom @) and processors following the APISetChatUIThemes pattern - Fix APIListGroups parser missing space ("/_groups" -> "/_groups ") to align with auto-generated cmdString - Add chatCommandsDocsData entries for APISetGroupCustomData, APISetContactCustomData, and APISetUserAutoAcceptMemberContacts * core: named fields for codegen, run codegen - Use named record fields for APISetGroupCustomData, APISetContactCustomData, APISetUserAutoAcceptMemberContacts (required for chatCommandsDocsData field resolution) - Fix OnOff field name to "onOff" (avoids clash with User field) - Remove APISetUserAutoAcceptMemberContacts from undocumentedCommands - Regenerate COMMANDS.md and commands.ts * nodejs: add ChatApi wrappers for custom data and apiGetChat - apiSetGroupCustomData, apiSetContactCustomData - apiSetAutoAcceptMemberContacts - apiGetChat (manual wrapper, APIGetChat undocumented) --- bots/api/COMMANDS.md | 114 ++++++++++++++++++ bots/src/API/Docs/Commands.hs | 6 +- .../types/typescript/src/commands.ts | 45 +++++++ packages/simplex-chat-nodejs/src/api.ts | 41 +++++++ src/Simplex/Chat/Controller.hs | 4 +- src/Simplex/Chat/Library/Commands.hs | 12 ++ 6 files changed, 219 insertions(+), 3 deletions(-) diff --git a/bots/api/COMMANDS.md b/bots/api/COMMANDS.md index b408d8eb30..9f63770df6 100644 --- a/bots/api/COMMANDS.md +++ b/bots/api/COMMANDS.md @@ -50,6 +50,9 @@ This file is generated automatically. - [APIListContacts](#apilistcontacts) - [APIListGroups](#apilistgroups) - [APIDeleteChat](#apideletechat) +- [APISetGroupCustomData](#apisetgroupcustomdata) +- [APISetContactCustomData](#apisetcontactcustomdata) +- [APISetUserAutoAcceptMemberContacts](#apisetuserautoacceptmembercontacts) [User profile commands](#user-profile-commands) - [ShowActiveUser](#showactiveuser) @@ -1526,6 +1529,117 @@ ChatCmdError: Command error (only used in WebSockets API). --- +### APISetGroupCustomData + +Set group custom data. + +*Network usage*: no. + +**Parameters**: +- groupId: int64 +- customData: JSONObject? + +**Syntax**: + +``` +/_set custom #[ ] +``` + +```javascript +'/_set custom #' + groupId + (customData ? ' ' + JSON.stringify(customData) : '') // JavaScript +``` + +```python +'/_set custom #' + str(groupId) + ((' ' + json.dumps(customData)) if customData is not None else '') # Python +``` + +**Responses**: + +CmdOk: Ok. +- type: "cmdOk" +- user_: [User](./TYPES.md#user)? + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APISetContactCustomData + +Set contact custom data. + +*Network usage*: no. + +**Parameters**: +- contactId: int64 +- customData: JSONObject? + +**Syntax**: + +``` +/_set custom @[ ] +``` + +```javascript +'/_set custom @' + contactId + (customData ? ' ' + JSON.stringify(customData) : '') // JavaScript +``` + +```python +'/_set custom @' + str(contactId) + ((' ' + json.dumps(customData)) if customData is not None else '') # Python +``` + +**Responses**: + +CmdOk: Ok. +- type: "cmdOk" +- user_: [User](./TYPES.md#user)? + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APISetUserAutoAcceptMemberContacts + +Set auto-accept member contacts. + +*Network usage*: no. + +**Parameters**: +- userId: int64 +- onOff: bool + +**Syntax**: + +``` +/_set accept member contacts on|off +``` + +```javascript +'/_set accept member contacts ' + userId + ' ' + (onOff ? 'on' : 'off') // JavaScript +``` + +```python +'/_set accept member contacts ' + str(userId) + ' ' + ('on' if onOff else 'off') # Python +``` + +**Responses**: + +CmdOk: Ok. +- type: "cmdOk" +- user_: [User](./TYPES.md#user)? + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + ## User profile commands Most bots don't need to use these commands, as bot profile can be configured manually via CLI or desktop client. These commands can be used by bots that need to manage multiple user profiles (e.g., the profiles of support agents). diff --git a/bots/src/API/Docs/Commands.hs b/bots/src/API/Docs/Commands.hs index 35745f9b42..77b5f66417 100644 --- a/bots/src/API/Docs/Commands.hs +++ b/bots/src/API/Docs/Commands.hs @@ -142,7 +142,10 @@ chatCommandsDocsData = "Commands to list and delete conversations.", [ ("APIListContacts", [], "Get contacts.", ["CRContactsList", "CRChatCmdError"], [], Nothing, "/_contacts " <> Param "userId"), ("APIListGroups", [], "Get groups.", ["CRGroupsList", "CRChatCmdError"], [], Nothing, "/_groups " <> Param "userId" <> Optional "" (" @" <> Param "$0") "contactId_" <> Optional "" (" " <> Param "$0") "search"), - ("APIDeleteChat", [], "Delete chat.", ["CRContactDeleted", "CRContactConnectionDeleted", "CRGroupDeletedUser", "CRChatCmdError"], [], Just UNBackground, "/_delete " <> Param "chatRef" <> " " <> Param "chatDeleteMode") + ("APIDeleteChat", [], "Delete chat.", ["CRContactDeleted", "CRContactConnectionDeleted", "CRGroupDeletedUser", "CRChatCmdError"], [], Just UNBackground, "/_delete " <> Param "chatRef" <> " " <> Param "chatDeleteMode"), + ("APISetGroupCustomData", [], "Set group custom data.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set custom #" <> Param "groupId" <> Optional "" (" " <> Json "$0") "customData"), + ("APISetContactCustomData", [], "Set contact custom data.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set custom @" <> Param "contactId" <> Optional "" (" " <> Json "$0") "customData"), + ("APISetUserAutoAcceptMemberContacts", [], "Set auto-accept member contacts.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set accept member contacts " <> Param "userId" <> " " <> OnOff "onOff") -- ("APIChatItemsRead", [], "Mark items as read.", ["CRItemsReadForChat"], [], Nothing, ""), -- ("APIChatRead", [], "Mark chat as read.", ["CRCmdOk"], [], Nothing, ""), -- ("APIChatUnread", [], "Mark chat as unread.", ["CRCmdOk"], [], Nothing, ""), @@ -398,7 +401,6 @@ undocumentedCommands = "APISetServerOperators", "APISetUserContactReceipts", "APISetUserGroupReceipts", - "APISetUserAutoAcceptMemberContacts", "APISetUserServers", "APISetUserUIThemes", "APIStandaloneFileInfo", diff --git a/packages/simplex-chat-client/types/typescript/src/commands.ts b/packages/simplex-chat-client/types/typescript/src/commands.ts index 66f4f6ec5f..edeabe7837 100644 --- a/packages/simplex-chat-client/types/typescript/src/commands.ts +++ b/packages/simplex-chat-client/types/typescript/src/commands.ts @@ -557,6 +557,51 @@ export namespace APIDeleteChat { } } +// Set group custom data. +// Network usage: no. +export interface APISetGroupCustomData { + groupId: number // int64 + customData?: object +} + +export namespace APISetGroupCustomData { + export type Response = CR.CmdOk | CR.ChatCmdError + + export function cmdString(self: APISetGroupCustomData): string { + return '/_set custom #' + self.groupId + (self.customData ? ' ' + JSON.stringify(self.customData) : '') + } +} + +// Set contact custom data. +// Network usage: no. +export interface APISetContactCustomData { + contactId: number // int64 + customData?: object +} + +export namespace APISetContactCustomData { + export type Response = CR.CmdOk | CR.ChatCmdError + + export function cmdString(self: APISetContactCustomData): string { + return '/_set custom @' + self.contactId + (self.customData ? ' ' + JSON.stringify(self.customData) : '') + } +} + +// Set auto-accept member contacts. +// Network usage: no. +export interface APISetUserAutoAcceptMemberContacts { + userId: number // int64 + onOff: boolean +} + +export namespace APISetUserAutoAcceptMemberContacts { + export type Response = CR.CmdOk | CR.ChatCmdError + + export function cmdString(self: APISetUserAutoAcceptMemberContacts): string { + return '/_set accept member contacts ' + self.userId + ' ' + (self.onOff ? 'on' : 'off') + } +} + // User profile commands // Most bots don't need to use these commands, as bot profile can be configured manually via CLI or desktop client. These commands can be used by bots that need to manage multiple user profiles (e.g., the profiles of support agents). diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index dc87055f87..c3e85b3915 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -747,6 +747,47 @@ export class ChatApi { throw new ChatCommandError("error deleting chat", r) } + /** + * Set group custom data. + * Network usage: no. + */ + async apiSetGroupCustomData(groupId: number, customData?: object): Promise { + const r = await this.sendChatCmd(CC.APISetGroupCustomData.cmdString({groupId, customData})) + if (r.type === "cmdOk") return + throw new ChatCommandError("error setting group custom data", r) + } + + /** + * Set contact custom data. + * Network usage: no. + */ + async apiSetContactCustomData(contactId: number, customData?: object): Promise { + const r = await this.sendChatCmd(CC.APISetContactCustomData.cmdString({contactId, customData})) + if (r.type === "cmdOk") return + throw new ChatCommandError("error setting contact custom data", r) + } + + /** + * Set auto-accept member contacts. + * Network usage: no. + */ + async apiSetAutoAcceptMemberContacts(userId: number, onOff: boolean): Promise { + const r = await this.sendChatCmd(CC.APISetUserAutoAcceptMemberContacts.cmdString({userId, onOff})) + if (r.type === "cmdOk") return + throw new ChatCommandError("error setting auto-accept member contacts", r) + } + + /** + * Get chat items. + * Network usage: no. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async apiGetChat(chatType: T.ChatType, chatId: number, count: number): Promise { + const r: any = await this.sendChatCmd(`/_get chat ${T.ChatType.cmdString(chatType)}${chatId} count=${count}`) + if (r.type === "apiChat") return r.chat + throw new ChatCommandError("error getting chat", r) + } + /** * Get active user profile * Network usage: no. diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 9703226e1a..24dfaf46a1 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -275,7 +275,7 @@ data ChatCommand | SetUserContactReceipts UserMsgReceiptSettings | APISetUserGroupReceipts UserId UserMsgReceiptSettings | SetUserGroupReceipts UserMsgReceiptSettings - | APISetUserAutoAcceptMemberContacts UserId Bool + | APISetUserAutoAcceptMemberContacts {userId :: UserId, onOff :: Bool} | SetUserAutoAcceptMemberContacts Bool | APIHideUser UserId UserPwd | APIUnhideUser UserId UserPwd @@ -362,6 +362,8 @@ data ChatCommand | APISetConnectionAlias {connectionId :: Int64, localAlias :: LocalAlias} | APISetUserUIThemes UserId (Maybe UIThemeEntityOverrides) | APISetChatUIThemes ChatRef (Maybe UIThemeEntityOverrides) + | APISetGroupCustomData {groupId :: GroupId, customData :: Maybe CustomData} + | APISetContactCustomData {contactId :: ContactId, customData :: Maybe CustomData} | APIGetNtfToken | APIRegisterToken DeviceToken NotificationsMode | APIVerifyToken DeviceToken C.CbNonce ByteString diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index e7e68554f0..fce4f8438c 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -1409,6 +1409,16 @@ processChatCommand vr nm = \case liftIO $ setGroupUIThemes db user g uiThemes ok user _ -> throwCmdError "not supported" + APISetGroupCustomData groupId customData_ -> withUser $ \user -> do + withFastStore $ \db -> do + g <- getGroupInfo db vr user groupId + liftIO $ setGroupCustomData db user g customData_ + ok user + APISetContactCustomData contactId customData_ -> withUser $ \user -> do + withFastStore $ \db -> do + ct <- getContact db vr user contactId + liftIO $ setContactCustomData db user ct customData_ + ok user APIGetNtfToken -> withUser' $ \_ -> crNtfToken <$> withAgent getNtfToken APIRegisterToken token mode -> withUser $ \_ -> CRNtfTokenStatus <$> withAgent (\a -> registerNtfToken a nm token mode) @@ -4415,6 +4425,8 @@ chatCommandP = "/_set prefs @" *> (APISetContactPrefs <$> A.decimal <* A.space <*> jsonP), "/_set theme user " *> (APISetUserUIThemes <$> A.decimal <*> optional (A.space *> jsonP)), "/_set theme " *> (APISetChatUIThemes <$> chatRefP <*> optional (A.space *> jsonP)), + "/_set custom #" *> (APISetGroupCustomData <$> A.decimal <*> optional (A.space *> jsonP)), + "/_set custom @" *> (APISetContactCustomData <$> A.decimal <*> optional (A.space *> jsonP)), "/_ntf get" $> APIGetNtfToken, "/_ntf register " *> (APIRegisterToken <$> strP_ <*> strP), "/_ntf verify " *> (APIVerifyToken <$> strP <* A.space <*> strP <* A.space <*> strP),