diff --git a/bots/api/COMMANDS.md b/bots/api/COMMANDS.md index 96fe5422cd..80319341a1 100644 --- a/bots/api/COMMANDS.md +++ b/bots/api/COMMANDS.md @@ -33,6 +33,8 @@ This file is generated automatically. - [APIRemoveMembers](#apiremovemembers) - [APILeaveGroup](#apileavegroup) - [APIListMembers](#apilistmembers) +- [APICreateMemberContact](#apicreatemembercontact) +- [APISendMemberContactInvitation](#apisendmembercontactinvitation) - [APINewGroup](#apinewgroup) - [APINewPublicGroup](#apinewpublicgroup) - [APIGetGroupRelays](#apigetgrouprelays) @@ -52,6 +54,11 @@ This file is generated automatically. - [APIConnectPlan](#apiconnectplan) - [APIConnect](#apiconnect) - [Connect](#connect) +- [APIPrepareContact](#apipreparecontact) +- [APIPrepareGroup](#apipreparegroup) +- [APIConnectPreparedContact](#apiconnectpreparedcontact) +- [APIConnectPreparedGroup](#apiconnectpreparedgroup) +- [APIConnectContactViaAddress](#apiconnectcontactviaaddress) - [APIAcceptContact](#apiacceptcontact) - [APIRejectContact](#apirejectcontact) @@ -59,6 +66,10 @@ This file is generated automatically. - [APIListContacts](#apilistcontacts) - [APIListGroups](#apilistgroups) - [APIGetChats](#apigetchats) +- [APIGetChat](#apigetchat) +- [APIChatItemsRead](#apichatitemsread) +- [APIContactInfo](#apicontactinfo) +- [APIGetChatItemInfo](#apigetchatiteminfo) - [APIDeleteChat](#apideletechat) - [APISetGroupCustomData](#apisetgroupcustomdata) - [APISetContactCustomData](#apisetcontactcustomdata) @@ -80,6 +91,9 @@ This file is generated automatically. [Chat management](#chat-management) - [StartChat](#startchat) - [APIStopChat](#apistopchat) +- [APIGetNetworkConfig](#apigetnetworkconfig) +- [APISetNetworkConfig](#apisetnetworkconfig) +- [APIGetUserServers](#apigetuserservers) [Remote control commands](#remote-control-commands) - [ConnectRemoteCtrl](#connectremotectrl) @@ -1092,6 +1106,93 @@ ChatCmdError: Command error (only used in WebSockets API). --- +### APICreateMemberContact + +Create a direct message contact with a group member. + +*Network usage*: interactive. + +**Parameters**: +- groupId: int64 +- groupMemberId: int64 + +**Syntax**: + +``` +/_create member contact # +``` + +```javascript +'/_create member contact #' + groupId + ' ' + groupMemberId // JavaScript +``` + +```python +'/_create member contact #' + str(groupId) + ' ' + str(groupMemberId) # Python +``` + +**Responses**: + +NewMemberContact: Member contact created. +- type: "newMemberContact" +- user: [User](./TYPES.md#user) +- contact: [Contact](./TYPES.md#contact) +- groupInfo: [GroupInfo](./TYPES.md#groupinfo) +- member: [GroupMember](./TYPES.md#groupmember) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +**Errors**: +- GroupMemberNotActive: Member is not connected yet. +- PeerChatVRangeIncompatible: Member's client version is older than required. + +--- + + +### APISendMemberContactInvitation + +Send a direct message invitation to a group member contact. + +*Network usage*: background. + +**Parameters**: +- contactId: int64 +- msgContent_: [MsgContent](./TYPES.md#msgcontent)? + +**Syntax**: + +``` +/_invite member contact @[ json ] +``` + +```javascript +'/_invite member contact @' + contactId + (msgContent_ ? ' json ' + JSON.stringify(msgContent_) : '') // JavaScript +``` + +```python +'/_invite member contact @' + str(contactId) + ((' json ' + json.dumps(msgContent_)) if msgContent_ is not None else '') # Python +``` + +**Responses**: + +NewMemberContactSentInv: Member contact invitation sent. +- type: "newMemberContactSentInv" +- user: [User](./TYPES.md#user) +- contact: [Contact](./TYPES.md#contact) +- groupInfo: [GroupInfo](./TYPES.md#groupinfo) +- member: [GroupMember](./TYPES.md#groupmember) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +**Errors**: +- GroupMemberNotActive: Member is not connected yet. + +--- + + ### APINewGroup Create group. @@ -1590,15 +1691,15 @@ Determine SimpleX link type and if the bot is already connected via this link or **Syntax**: ``` -/_connect plan +/_connect plan [ sig=] ``` ```javascript -'/_connect plan ' + userId + ' ' + connectTarget // JavaScript +'/_connect plan ' + userId + ' ' + connectTarget + PlanResolveMode.cmdString(resolveMode) + (linkOwnerSig ? ' sig=' + JSON.stringify(linkOwnerSig) : '') // JavaScript ``` ```python -'/_connect plan ' + str(userId) + ' ' + connectTarget # Python +'/_connect plan ' + str(userId) + ' ' + connectTarget + PlanResolveMode_cmd_string(resolveMode) + ((' sig=' + json.dumps(linkOwnerSig)) if linkOwnerSig is not None else '') # Python ``` **Responses**: @@ -1746,6 +1847,214 @@ ChatCmdError: Command error (only used in WebSockets API). --- +### APIPrepareContact + +Prepare contact chat from link data before connecting. + +*Network usage*: no. + +**Parameters**: +- userId: int64 +- connLink: [CreatedConnLink](./TYPES.md#createdconnlink) +- verifiedDomain: string? +- contactSLinkData: [ContactShortLinkData](./TYPES.md#contactshortlinkdata) + +**Syntax**: + +``` +/_prepare contact [ domain=] +``` + +```javascript +'/_prepare contact ' + userId + ' ' + CreatedConnLink.cmdString(connLink) + (verifiedDomain ? ' domain=' + verifiedDomain : '') + ' ' + JSON.stringify(contactSLinkData) // JavaScript +``` + +```python +'/_prepare contact ' + str(userId) + ' ' + CreatedConnLink_cmd_string(connLink) + ((' domain=' + verifiedDomain) if verifiedDomain is not None else '') + ' ' + json.dumps(contactSLinkData) # Python +``` + +**Responses**: + +NewPreparedChat: Prepared chat. +- type: "newPreparedChat" +- user: [User](./TYPES.md#user) +- chat: [AChat](./TYPES.md#achat) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIPrepareGroup + +Prepare group chat from link data before connecting. + +*Network usage*: no. + +**Parameters**: +- userId: int64 +- connLinkContact: [CreatedConnLink](./TYPES.md#createdconnlink) +- direct: bool +- verifiedDomain: string? +- groupSLinkData: [GroupShortLinkData](./TYPES.md#groupshortlinkdata) + +**Syntax**: + +``` +/_prepare group [ direct=off][ domain=] +``` + +```javascript +'/_prepare group ' + userId + ' ' + CreatedConnLink.cmdString(connLinkContact) + (!direct ? ' direct=off' : '') + (verifiedDomain ? ' domain=' + verifiedDomain : '') + ' ' + JSON.stringify(groupSLinkData) // JavaScript +``` + +```python +'/_prepare group ' + str(userId) + ' ' + CreatedConnLink_cmd_string(connLinkContact) + (' direct=off' if not direct else '') + ((' domain=' + verifiedDomain) if verifiedDomain is not None else '') + ' ' + json.dumps(groupSLinkData) # Python +``` + +**Responses**: + +NewPreparedChat: Prepared chat. +- type: "newPreparedChat" +- user: [User](./TYPES.md#user) +- chat: [AChat](./TYPES.md#achat) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIConnectPreparedContact + +Connect to prepared contact. + +*Network usage*: interactive. + +**Parameters**: +- contactId: int64 +- incognito: bool +- msgContent_: [MsgContent](./TYPES.md#msgcontent)? + +**Syntax**: + +``` +/_connect contact @[ incognito=on][ json ] +``` + +```javascript +'/_connect contact @' + contactId + (incognito ? ' incognito=on' : '') + (msgContent_ ? ' json ' + JSON.stringify(msgContent_) : '') // JavaScript +``` + +```python +'/_connect contact @' + str(contactId) + (' incognito=on' if incognito else '') + ((' json ' + json.dumps(msgContent_)) if msgContent_ is not None else '') # Python +``` + +**Responses**: + +StartedConnectionToContact: Connection to contact started (when connecting via prepared contact).. +- type: "startedConnectionToContact" +- user: [User](./TYPES.md#user) +- contact: [Contact](./TYPES.md#contact) +- customUserProfile: [Profile](./TYPES.md#profile)? + +ContactAlreadyExists: Contact already exists. +- type: "contactAlreadyExists" +- user: [User](./TYPES.md#user) +- contact: [Contact](./TYPES.md#contact) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIConnectPreparedGroup + +Connect to prepared group. + +*Network usage*: interactive. + +**Parameters**: +- groupId: int64 +- incognito: bool +- ownerContact: [GroupOwnerContact](./TYPES.md#groupownercontact)? +- msgContent_: [MsgContent](./TYPES.md#msgcontent)? + +**Syntax**: + +``` +/_connect group #[ incognito=on][ ][ json ] +``` + +```javascript +'/_connect group #' + groupId + (incognito ? ' incognito=on' : '') + (ownerContact ? ' ' + GroupOwnerContact.cmdString(ownerContact) : '') + (msgContent_ ? ' json ' + JSON.stringify(msgContent_) : '') // JavaScript +``` + +```python +'/_connect group #' + str(groupId) + (' incognito=on' if incognito else '') + ((' ' + GroupOwnerContact_cmd_string(ownerContact)) if ownerContact is not None else '') + ((' json ' + json.dumps(msgContent_)) if msgContent_ is not None else '') # Python +``` + +**Responses**: + +StartedConnectionToGroup: Connection to channel started (when connecting via channel link).. +- type: "startedConnectionToGroup" +- user: [User](./TYPES.md#user) +- groupInfo: [GroupInfo](./TYPES.md#groupinfo) +- customUserProfile: [Profile](./TYPES.md#profile)? +- relayResults: [[RelayConnectionResult](./TYPES.md#relayconnectionresult)] + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIConnectContactViaAddress + +Connect to existing contact via address in its profile. + +*Network usage*: interactive. + +**Parameters**: +- userId: int64 +- incognito: bool +- contactId: int64 + +**Syntax**: + +``` +/_connect contact [ incognito=on] +``` + +```javascript +'/_connect contact ' + userId + (incognito ? ' incognito=on' : '') + ' ' + contactId // JavaScript +``` + +```python +'/_connect contact ' + str(userId) + (' incognito=on' if incognito else '') + ' ' + str(contactId) # Python +``` + +**Responses**: + +SentInvitationToContact: Invitation sent to contact (when connecting via SimpleX name to a known contact address).. +- type: "sentInvitationToContact" +- user: [User](./TYPES.md#user) +- contact: [Contact](./TYPES.md#contact) +- customUserProfile: [Profile](./TYPES.md#profile)? + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + ### APIAcceptContact Accept contact request. @@ -1943,6 +2252,163 @@ ChatCmdError: Command error (only used in WebSockets API). --- +### APIGetChat + +Get chat messages. + +*Network usage*: no. + +**Parameters**: +- chatRef: [ChatRef](./TYPES.md#chatref) +- contentTag: [MsgContentTag](./TYPES.md#msgcontenttag)? +- chatPagination: [ChatPagination](./TYPES.md#chatpagination) +- search: string? + +**Syntax**: + +``` +/_get chat [ content=text|link|image|video|voice|file|report|chat] [ search=] +``` + +```javascript +'/_get chat ' + ChatRef.cmdString(chatRef) + (contentTag ? ' content=' + contentTag : '') + ' ' + ChatPagination.cmdString(chatPagination) + (search ? ' search=' + search : '') // JavaScript +``` + +```python +'/_get chat ' + ChatRef_cmd_string(chatRef) + ((' content=' + str(contentTag)) if contentTag is not None else '') + ' ' + ChatPagination_cmd_string(chatPagination) + ((' search=' + search) if search is not None else '') # Python +``` + +**Responses**: + +ApiChat: Chat and messages. +- type: "apiChat" +- user: [User](./TYPES.md#user) +- chat: [AChat](./TYPES.md#achat) +- navInfo: [NavigationInfo](./TYPES.md#navigationinfo)? + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIChatItemsRead + +Mark messages as read. + +*Network usage*: no. + +**Parameters**: +- chatRef: [ChatRef](./TYPES.md#chatref) +- chatItemIds: [int64] + +**Syntax**: + +``` +/_read chat items [,...] +``` + +```javascript +'/_read chat items ' + ChatRef.cmdString(chatRef) + ' ' + chatItemIds.join(',') // JavaScript +``` + +```python +'/_read chat items ' + ChatRef_cmd_string(chatRef) + ' ' + ','.join(map(str, chatItemIds)) # Python +``` + +**Responses**: + +ItemsReadForChat: Messages marked as read. +- type: "itemsReadForChat" +- user: [User](./TYPES.md#user) +- chatInfo: [ChatInfo](./TYPES.md#chatinfo) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIContactInfo + +Get contact information. + +*Network usage*: no. + +**Parameters**: +- contactId: int64 + +**Syntax**: + +``` +/_info @ +``` + +```javascript +'/_info @' + contactId // JavaScript +``` + +```python +'/_info @' + str(contactId) # Python +``` + +**Responses**: + +ContactInfo: Contact information. +- type: "contactInfo" +- user: [User](./TYPES.md#user) +- contact: [Contact](./TYPES.md#contact) +- connectionStats_: [ConnectionStats](./TYPES.md#connectionstats)? +- customUserProfile: [Profile](./TYPES.md#profile)? + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APIGetChatItemInfo + +Get message information. + +*Network usage*: no. + +**Parameters**: +- chatRef: [ChatRef](./TYPES.md#chatref) +- chatItemId: int64 + +**Syntax**: + +``` +/_get item info +``` + +```javascript +'/_get item info ' + ChatRef.cmdString(chatRef) + ' ' + chatItemId // JavaScript +``` + +```python +'/_get item info ' + ChatRef_cmd_string(chatRef) + ' ' + str(chatItemId) # Python +``` + +**Responses**: + +ChatItemInfo: Message information. +- type: "chatItemInfo" +- user: [User](./TYPES.md#user) +- chatItem: [AChatItem](./TYPES.md#achatitem) +- chatItemInfo: [ChatItemInfo](./TYPES.md#chatiteminfo) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + ### APIDeleteChat Delete chat. @@ -2502,6 +2968,104 @@ ChatStopped: Chat stopped. --- +### APIGetNetworkConfig + +Get network configuration. + +*Network usage*: no. + +**Syntax**: + +``` +/network +``` + +**Responses**: + +NetworkConfig: Network configuration. +- type: "networkConfig" +- networkConfig: [NetworkConfig](./TYPES.md#networkconfig) + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + +### APISetNetworkConfig + +Set network configuration. + +*Network usage*: background. + +**Parameters**: +- networkConfig: [NetworkConfig](./TYPES.md#networkconfig) + +**Syntax**: + +``` +/_network +``` + +```javascript +'/_network ' + JSON.stringify(networkConfig) // JavaScript +``` + +```python +'/_network ' + json.dumps(networkConfig) # 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) + +--- + + +### APIGetUserServers + +Get user servers and chat relays, grouped by operator. + +*Network usage*: no. + +**Parameters**: +- userId: int64 + +**Syntax**: + +``` +/_servers +``` + +```javascript +'/_servers ' + userId // JavaScript +``` + +```python +'/_servers ' + str(userId) # Python +``` + +**Responses**: + +UserServers: User servers and chat relays, grouped by operator. +- type: "userServers" +- user: [User](./TYPES.md#user) +- userServers: [[UserOperatorServers](./TYPES.md#useroperatorservers)] + +ChatCmdError: Command error (only used in WebSockets API). +- type: "chatCmdError" +- chatError: [ChatError](./TYPES.md#chaterror) + +--- + + ## Remote control commands Allows a bot to accept an incoming remote control session from a SimpleX Desktop client, giving the desktop live access to the bot's SimpleX instance. diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 897c540678..4720f3ae33 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -50,8 +50,11 @@ This file is generated automatically. - [ChatInfo](#chatinfo) - [ChatItem](#chatitem) - [ChatItemDeletion](#chatitemdeletion) +- [ChatItemInfo](#chatiteminfo) +- [ChatItemVersion](#chatitemversion) - [ChatLayout](#chatlayout) - [ChatListQuery](#chatlistquery) +- [ChatPagination](#chatpagination) - [ChatPeerType](#chatpeertype) - [ChatRef](#chatref) - [ChatSettings](#chatsettings) @@ -65,6 +68,7 @@ This file is generated automatically. - [CommandErrorType](#commanderrortype) - [CommentsGroupPreference](#commentsgrouppreference) - [ComposedMessage](#composedmessage) +- [ConditionsAcceptance](#conditionsacceptance) - [ConnStatus](#connstatus) - [ConnType](#conntype) - [Connection](#connection) @@ -72,6 +76,7 @@ This file is generated automatically. - [ConnectionErrorType](#connectionerrortype) - [ConnectionMode](#connectionmode) - [ConnectionPlan](#connectionplan) +- [ConnectionStats](#connectionstats) - [Contact](#contact) - [ContactAddressPlan](#contactaddressplan) - [ContactShortLinkData](#contactshortlinkdata) @@ -117,19 +122,23 @@ This file is generated automatically. - [GroupMemberRole](#groupmemberrole) - [GroupMemberSettings](#groupmembersettings) - [GroupMemberStatus](#groupmemberstatus) +- [GroupOwnerContact](#groupownercontact) - [GroupPreference](#grouppreference) - [GroupPreferences](#grouppreferences) - [GroupProfile](#groupprofile) - [GroupRelay](#grouprelay) - [GroupShortLinkData](#groupshortlinkdata) - [GroupShortLinkInfo](#groupshortlinkinfo) +- [GroupSndStatus](#groupsndstatus) - [GroupSummary](#groupsummary) - [GroupSupportChat](#groupsupportchat) - [GroupType](#grouptype) - [HandshakeError](#handshakeerror) +- [HostMode](#hostmode) - [InlineFileMode](#inlinefilemode) - [InvitationLinkPlan](#invitationlinkplan) - [InvitedBy](#invitedby) +- [KeepAliveOpts](#keepaliveopts) - [LinkContent](#linkcontent) - [LinkOwnerSig](#linkownersig) - [LinkPreview](#linkpreview) @@ -138,8 +147,10 @@ This file is generated automatically. - [LocalProfile](#localprofile) - [MarkdownBlock](#markdownblock) - [MemberCriteria](#membercriteria) +- [MemberDeliveryStatus](#memberdeliverystatus) - [MsgChatLink](#msgchatlink) - [MsgContent](#msgcontent) +- [MsgContentTag](#msgcontenttag) - [MsgDecryptError](#msgdecrypterror) - [MsgDirection](#msgdirection) - [MsgErrorType](#msgerrortype) @@ -149,9 +160,13 @@ This file is generated automatically. - [MsgSigStatus](#msgsigstatus) - [MsgVerified](#msgverified) - [NameErrorType](#nameerrortype) +- [NavigationInfo](#navigationinfo) +- [NetworkConfig](#networkconfig) - [NetworkError](#networkerror) +- [NetworkTimeout](#networktimeout) - [NewUser](#newuser) - [NoteFolder](#notefolder) +- [OperatorTag](#operatortag) - [OwnerVerification](#ownerverification) - [PaginationByTime](#paginationbytime) - [PendingContactConnection](#pendingcontactconnection) @@ -166,6 +181,7 @@ This file is generated automatically. - [PublicGroupAccess](#publicgroupaccess) - [PublicGroupData](#publicgroupdata) - [PublicGroupProfile](#publicgroupprofile) +- [QueueStatus](#queuestatus) - [RCErrorType](#rcerrortype) - [RatchetSyncState](#ratchetsyncstate) - [RcvConnEvent](#rcvconnevent) @@ -175,6 +191,8 @@ This file is generated automatically. - [RcvFileTransfer](#rcvfiletransfer) - [RcvGroupEvent](#rcvgroupevent) - [RcvMsgError](#rcvmsgerror) +- [RcvQueueInfo](#rcvqueueinfo) +- [RcvSwitchStatus](#rcvswitchstatus) - [RelayCapabilities](#relaycapabilities) - [RelayConnectionResult](#relayconnectionresult) - [RelayProfile](#relayprofile) @@ -185,8 +203,14 @@ This file is generated automatically. - [ReportReason](#reportreason) - [RoleGroupPreference](#rolegrouppreference) - [SMPAgentError](#smpagenterror) +- [SMPProxyFallback](#smpproxyfallback) +- [SMPProxyMode](#smpproxymode) +- [SMPWebPortServers](#smpwebportservers) - [SecurityCode](#securitycode) - [SegmentRange](#segmentrange) +- [ServerOperator](#serveroperator) +- [ServerRoles](#serverroles) +- [ServerRolesOverride](#serverrolesoverride) - [SimplePreference](#simplepreference) - [SimplexDomain](#simplexdomain) - [SimplexDomainClaim](#simplexdomainclaim) @@ -201,6 +225,9 @@ This file is generated automatically. - [SndError](#snderror) - [SndFileTransfer](#sndfiletransfer) - [SndGroupEvent](#sndgroupevent) +- [SndQueueInfo](#sndqueueinfo) +- [SndSwitchStatus](#sndswitchstatus) +- [SocksMode](#socksmode) - [SrvError](#srverror) - [StoreError](#storeerror) - [SubscriptionStatus](#subscriptionstatus) @@ -210,6 +237,7 @@ This file is generated automatically. - [TimedMessagesGroupPreference](#timedmessagesgrouppreference) - [TimedMessagesPreference](#timedmessagespreference) - [TransportError](#transporterror) +- [TransportSessionMode](#transportsessionmode) - [UIColorMode](#uicolormode) - [UIColors](#uicolors) - [UIThemeEntityOverride](#uithemeentityoverride) @@ -222,8 +250,10 @@ This file is generated automatically. - [UserContactRequest](#usercontactrequest) - [UserContactRequestRef](#usercontactrequestref) - [UserInfo](#userinfo) +- [UserOperatorServers](#useroperatorservers) - [UserProfileUpdateSummary](#userprofileupdatesummary) - [UserPwdHash](#userpwdhash) +- [UserServer](#userserver) - [VersionRange](#versionrange) - [XFTPErrorType](#xftperrortype) - [XFTPRcvFile](#xftprcvfile) @@ -1592,6 +1622,30 @@ Message deletion result. - toChatItem: [AChatItem](#achatitem)? +--- + +## ChatItemInfo + +**Record type**: +- itemVersions: [[ChatItemVersion](#chatitemversion)] +- memberDeliveryStatuses: [[MemberDeliveryStatus](#memberdeliverystatus)]? +- forwardedFromChatItem: [AChatItem](#achatitem)? +- fileXftpServers: [string] + + +--- + +## ChatItemVersion + +**Record type**: +- chatItemVersionId: int64 +- msgContent: [MsgContent](#msgcontent) +- formattedText: [[FormattedText](#formattedtext)]? +- markdownBlocks: [[MarkdownBlock](#markdownblock)]? +- itemVersionTs: UTCTime +- createdAt: UTCTime + + --- ## ChatLayout @@ -1617,6 +1671,50 @@ Search: - search: string +--- + +## ChatPagination + +**Discriminated union type**: + +Last: +- type: "last" +- count: int + +After: +- type: "after" +- chatItemId: int64 +- count: int + +Before: +- type: "before" +- chatItemId: int64 +- count: int + +Around: +- type: "around" +- chatItemId: int64 +- count: int + +Initial: +- type: "initial" +- count: int + +**Syntax**: + +``` +count=|after= count=|before= count=|around= count=|initial= +``` + +```javascript +type == 'last' ? 'count=' + count : type == 'after' ? 'after=' + chatItemId + ' count=' + count : type == 'before' ? 'before=' + chatItemId + ' count=' + count : type == 'around' ? 'around=' + chatItemId + ' count=' + count : 'initial=' + count // JavaScript +``` + +```python +'count=' + str(count) if str(type) == 'last' else 'after=' + str(chatItemId) + ' count=' + str(count) if str(type) == 'after' else 'before=' + str(chatItemId) + ' count=' + str(count) if str(type) == 'before' else 'around=' + str(chatItemId) + ' count=' + str(count) if str(type) == 'around' else 'initial=' + str(count) # Python +``` + + --- ## ChatPeerType @@ -1813,6 +1911,22 @@ LARGE: - mentions: {string : int64} +--- + +## ConditionsAcceptance + +**Discriminated union type**: + +Accepted: +- type: "accepted" +- acceptedAt: UTCTime? +- autoAccepted: bool + +Required: +- type: "required" +- deadline: UTCTime? + + --- ## ConnStatus @@ -1966,6 +2080,19 @@ Error: - chatError: [ChatError](#chaterror) +--- + +## ConnectionStats + +**Record type**: +- connAgentVersion: int +- rcvQueuesInfo: [[RcvQueueInfo](#rcvqueueinfo)] +- sndQueuesInfo: [[SndQueueInfo](#sndqueueinfo)] +- ratchetSyncState: [RatchetSyncState](#ratchetsyncstate) +- ratchetSyncSupported: bool +- subStatus: [SubscriptionStatus](#subscriptionstatus)? + + --- ## Contact @@ -2759,6 +2886,29 @@ UpdateRequired: - "creator" +--- + +## GroupOwnerContact + +**Record type**: +- contactId: int64 +- memberId: string + +**Syntax**: + +``` +contact= owner= +``` + +```javascript +'contact=' + contactId + ' owner=' + memberId // JavaScript +``` + +```python +'contact=' + str(contactId) + ' owner=' + memberId # Python +``` + + --- ## GroupPreference @@ -2836,6 +2986,41 @@ UpdateRequired: - publicGroupId: string? +--- + +## GroupSndStatus + +**Discriminated union type**: + +New: +- type: "new" + +Forwarded: +- type: "forwarded" + +Inactive: +- type: "inactive" + +Sent: +- type: "sent" + +Rcvd: +- type: "rcvd" +- msgRcptStatus: [MsgReceiptStatus](#msgreceiptstatus) + +Error: +- type: "error" +- agentError: [SndError](#snderror) + +Warning: +- type: "warning" +- agentError: [SndError](#snderror) + +Invalid: +- type: "invalid" +- text: string + + --- ## GroupSummary @@ -2877,6 +3062,16 @@ UpdateRequired: - "BAD_SERVICE" +--- + +## HostMode + +**Enum type**: +- "onionViaSocks" +- "onion" +- "public" + + --- ## InlineFileMode @@ -2926,6 +3121,16 @@ Unknown: - type: "unknown" +--- + +## KeepAliveOpts + +**Record type**: +- keepIdle: int +- keepIntvl: int +- keepCnt: int + + --- ## LinkContent @@ -3056,6 +3261,16 @@ Rule: - "all" +--- + +## MemberDeliveryStatus + +**Record type**: +- groupMemberId: int64 +- memberDeliveryStatus: [GroupSndStatus](#groupsndstatus) +- sentViaProxy: bool? + + --- ## MsgChatLink @@ -3134,6 +3349,21 @@ Unknown: - json: JSONObject +--- + +## MsgContentTag + +**Enum type**: +- "text" +- "link" +- "image" +- "video" +- "voice" +- "file" +- "report" +- "chat" + + --- ## MsgDecryptError @@ -3252,6 +3482,38 @@ RESOLVER: - resolverErr: string +--- + +## NavigationInfo + +**Record type**: +- afterUnread: int +- afterTotal: int + + +--- + +## NetworkConfig + +**Record type**: +- socksProxy: string? +- socksMode: [SocksMode](#socksmode) +- hostMode: [HostMode](#hostmode) +- requiredHostMode: bool +- sessionMode: [TransportSessionMode](#transportsessionmode) +- smpProxyMode: [SMPProxyMode](#smpproxymode) +- smpProxyFallback: [SMPProxyFallback](#smpproxyfallback) +- smpWebPortServers: [SMPWebPortServers](#smpwebportservers) +- tcpConnectTimeout: [NetworkTimeout](#networktimeout) +- tcpTimeout: [NetworkTimeout](#networktimeout) +- tcpTimeoutPerKb: int64 +- rcvConcurrency: int +- tcpKeepAlive: [KeepAliveOpts](#keepaliveopts)? +- smpPingInterval: int64 +- smpPingCount: int +- logTLSErrors: bool + + --- ## NetworkError @@ -3280,6 +3542,15 @@ SubscribeError: - subscribeError: string +--- + +## NetworkTimeout + +**Record type**: +- backgroundTimeout: int +- interactiveTimeout: int + + --- ## NewUser @@ -3305,6 +3576,15 @@ SubscribeError: - unread: bool +--- + +## OperatorTag + +**Enum type**: +- "simplex" +- "flux" + + --- ## OwnerVerification @@ -3371,6 +3651,20 @@ count= - "unknown" - "never" +**Syntax**: + +``` + resolve=allGroups| resolve=never| +``` + +```javascript +self == 'allGroups' ? ' resolve=allGroups' : self == 'never' ? ' resolve=never' : '' // JavaScript +``` + +```python +' resolve=allGroups' if str(self) == 'allGroups' else ' resolve=never' if str(self) == 'never' else '' # Python +``` + --- @@ -3506,6 +3800,18 @@ NO_SESSION: - publicGroupAccess: [PublicGroupAccess](#publicgroupaccess)? +--- + +## QueueStatus + +**Enum type**: +- "new" +- "confirmed" +- "secured" +- "active" +- "disabled" + + --- ## RCErrorType @@ -3763,6 +4069,30 @@ ParseError: - parseError: string +--- + +## RcvQueueInfo + +**Record type**: +- rcvServer: string +- status: [QueueStatus](#queuestatus) +- rcvSwitchStatus: [RcvSwitchStatus](#rcvswitchstatus)? +- canAbortSwitch: bool +- subStatus: [SubscriptionStatus](#subscriptionstatus) + + +--- + +## RcvSwitchStatus + +**Enum type**: +- "switch_started" +- "sending_qadd" +- "sending_quse" +- "received_message" +- "received_qend" + + --- ## RelayCapabilities @@ -3920,6 +4250,37 @@ A_SERVICE: - serviceError: [AgentServiceError](#agentserviceerror) +--- + +## SMPProxyFallback + +**Enum type**: +- "allow" +- "allowProtected" +- "prohibit" + + +--- + +## SMPProxyMode + +**Enum type**: +- "always" +- "unknown" +- "unprotected" +- "never" + + +--- + +## SMPWebPortServers + +**Enum type**: +- "all" +- "preset" +- "off" + + --- ## SecurityCode @@ -3938,6 +4299,42 @@ A_SERVICE: - to: int +--- + +## ServerOperator + +**Record type**: +- operatorId: int64 +- operatorTag: [OperatorTag](#operatortag)? +- tradeName: string +- legalName: string? +- serverDomains: [string] +- conditionsAcceptance: [ConditionsAcceptance](#conditionsacceptance) +- enabled: bool +- smpRoles: [ServerRoles](#serverroles) +- xftpRoles: [ServerRoles](#serverroles) + + +--- + +## ServerRoles + +**Record type**: +- storage: bool +- proxy: bool +- names: bool + + +--- + +## ServerRolesOverride + +**Record type**: +- storage: bool? +- proxy: bool? +- names: bool? + + --- ## SimplePreference @@ -4154,6 +4551,36 @@ UserPendingReview: - type: "userPendingReview" +--- + +## SndQueueInfo + +**Record type**: +- sndServer: string +- status: [QueueStatus](#queuestatus) +- sndSwitchStatus: [SndSwitchStatus](#sndswitchstatus)? + + +--- + +## SndSwitchStatus + +**Enum type**: +- "sending_qkey" +- "sending_qtest" +- "securing_queue" +- "sending_qend" + + +--- + +## SocksMode + +**Enum type**: +- "always" +- "onion" + + --- ## SrvError @@ -4614,6 +5041,17 @@ Handshake: - handshakeErr: [HandshakeError](#handshakeerror) +--- + +## TransportSessionMode + +**Enum type**: +- "user" +- "session" +- "server" +- "entity" + + --- ## UIColorMode @@ -4774,6 +5212,17 @@ Handshake: - unreadCount: int +--- + +## UserOperatorServers + +**Record type**: +- operator: [ServerOperator](#serveroperator)? +- smpServers: [[UserServer](#userserver)] +- xftpServers: [[UserServer](#userserver)] +- chatRelays: [[UserChatRelay](#userchatrelay)] + + --- ## UserProfileUpdateSummary @@ -4793,6 +5242,20 @@ Handshake: - salt: string +--- + +## UserServer + +**Record type**: +- serverId: int64 +- server: string +- preset: bool +- tested: bool? +- enabled: bool +- roles: [ServerRolesOverride](#serverrolesoverride) +- deleted: bool + + --- ## VersionRange diff --git a/bots/src/API/Docs/Commands.hs b/bots/src/API/Docs/Commands.hs index fe81b430b4..5a82fd8f42 100644 --- a/bots/src/API/Docs/Commands.hs +++ b/bots/src/API/Docs/Commands.hs @@ -127,6 +127,8 @@ chatCommandsDocsData = ("APIRemoveMembers", [], "Remove members. Requires Admin role.", ["CRUserDeletedMembers", "CRChatCmdError"], ["CEGroupMemberNotFound"], Just UNBackground, "/_remove #" <> Param "groupId" <> " " <> Join ',' "groupMemberIds" <> OnOffParam "messages" "withMessages" (Just False)), ("APILeaveGroup", [], "Leave group.", ["CRLeftMemberUser", "CRChatCmdError"], [], Just UNBackground, "/_leave #" <> Param "groupId"), ("APIListMembers", [], "Get group members.", ["CRGroupMembers", "CRChatCmdError"], [], Nothing, "/_members #" <> Param "groupId"), + ("APICreateMemberContact", [], "Create a direct message contact with a group member.", ["CRNewMemberContact", "CRChatCmdError"], [TD "CEGroupMemberNotActive" "Member is not connected yet", TD "CEPeerChatVRangeIncompatible" "Member's client version is older than required"], Just UNInteractive, "/_create member contact #" <> Param "groupId" <> " " <> Param "groupMemberId"), + ("APISendMemberContactInvitation", [], "Send a direct message invitation to a group member contact.", ["CRNewMemberContactSentInv", "CRChatCmdError"], [TD "CEGroupMemberNotActive" "Member is not connected yet"], Just UNBackground, "/_invite member contact @" <> Param "contactId" <> Optional "" (" json " <> Json "$0") "msgContent_"), ("APINewGroup", [], "Create group.", ["CRGroupCreated", "CRChatCmdError"], [], Nothing, "/_group " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False) <> " " <> Json "groupProfile"), ("APINewPublicGroup", [], "Create public group.", ["CRPublicGroupCreated", "CRPublicGroupCreationFailed", "CRChatCmdError"], [], Just UNInteractive, "/_public group " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False) <> " " <> Join ',' "relayIds" <> " " <> Json "groupProfile"), ("APIGetGroupRelays", [], "Get group relays.", ["CRGroupRelays", "CRChatCmdError"], [], Nothing, "/_get relays #" <> Param "groupId"), @@ -148,9 +150,14 @@ chatCommandsDocsData = "These commands may be used to create connections. Most bots do not need to use them - bot users will connect via bot address with auto-accept enabled.", [ ("APIAddContact", [], "Create 1-time invitation link.", ["CRInvitation", "CRChatCmdError"], [], Just UNInteractive, "/_connect " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False)), -- `Maybe` in `connectTarget :: Maybe ConnectTarget` is used to signal parse failure to the runtime (the handler returns CEInvalidConnReq on Nothing); it is NOT API-level optionality. The parameter is required from callers. - ("APIConnectPlan", [], "Determine SimpleX link type and if the bot is already connected via this link or name.", ["CRConnectionPlan", "CRChatCmdError"], [], Just UNInteractive, "/_connect plan " <> Param "userId" <> " " <> Param "connectTarget"), + ("APIConnectPlan", [], "Determine SimpleX link type and if the bot is already connected via this link or name.", ["CRConnectionPlan", "CRChatCmdError"], [], Just UNInteractive, "/_connect plan " <> Param "userId" <> " " <> Param "connectTarget" <> Param "resolveMode" <> Optional "" (" sig=" <> Json "$0") "linkOwnerSig"), ("APIConnect", [], "Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link.", ["CRSentConfirmation", "CRContactAlreadyExists", "CRSentInvitation", "CRChatCmdError"], [], Just UNInteractive, "/_connect " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False) <> Optional "" (" " <> Param "$0") "preparedLink_"), ("Connect", [], "Connect via SimpleX link or name as string in the active user profile.", ["CRSentConfirmation", "CRContactAlreadyExists", "CRSentInvitation", "CRConnectionPlan", "CRSentInvitationToContact", "CRStartedConnectionToContact", "CRStartedConnectionToGroup", "CRChatCmdError"], [], Just UNInteractive, "/connect" <> Optional "" (" " <> Param "$0") "connTarget_"), + ("APIPrepareContact", [], "Prepare contact chat from link data before connecting.", ["CRNewPreparedChat", "CRChatCmdError"], [], Nothing, "/_prepare contact " <> Param "userId" <> " " <> Param "connLink" <> Optional "" (" domain=" <> Param "$0") "verifiedDomain" <> " " <> Json "contactSLinkData"), + ("APIPrepareGroup", [], "Prepare group chat from link data before connecting.", ["CRNewPreparedChat", "CRChatCmdError"], [], Nothing, "/_prepare group " <> Param "userId" <> " " <> Param "connLinkContact" <> OnOffParam "direct" "direct" (Just True) <> Optional "" (" domain=" <> Param "$0") "verifiedDomain" <> " " <> Json "groupSLinkData"), + ("APIConnectPreparedContact", [], "Connect to prepared contact.", ["CRStartedConnectionToContact", "CRContactAlreadyExists", "CRChatCmdError"], [], Just UNInteractive, "/_connect contact @" <> Param "contactId" <> OnOffParam "incognito" "incognito" (Just False) <> Optional "" (" json " <> Json "$0") "msgContent_"), + ("APIConnectPreparedGroup", [], "Connect to prepared group.", ["CRStartedConnectionToGroup", "CRChatCmdError"], [], Just UNInteractive, "/_connect group #" <> Param "groupId" <> OnOffParam "incognito" "incognito" (Just False) <> Optional "" (" " <> Param "$0") "ownerContact" <> Optional "" (" json " <> Json "$0") "msgContent_"), + ("APIConnectContactViaAddress", [], "Connect to existing contact via address in its profile.", ["CRSentInvitationToContact", "CRChatCmdError"], [], Just UNInteractive, "/_connect contact " <> Param "userId" <> OnOffParam "incognito" "incognito" (Just False) <> " " <> Param "contactId"), ("APIAcceptContact", ["incognito"], "Accept contact request.", ["CRAcceptingContactRequest", "CRChatCmdError"], [], Just UNInteractive, "/_accept " <> Param "contactReqId"), ("APIRejectContact", [], "Reject contact request. The user who sent the request is **not notified**.", ["CRContactRequestRejected", "CRChatCmdError"], [], Nothing, "/_reject " <> Param "contactReqId") ] @@ -160,17 +167,18 @@ chatCommandsDocsData = [ ("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"), ("APIGetChats", [], "Get chat previews. Supports time-based pagination — use this instead of APIListContacts / APIListGroups when scanning at scale (those load every record into memory and fail on large databases).", ["CRApiChats", "CRChatCmdError"], [], Nothing, "/_get chats " <> Param "userId" <> OnOffParam "pcc" "pendingConnections" (Just False) <> Optional "" (" " <> Param "$0") "pagination" <> " " <> Json "query"), + ("APIGetChat", [], "Get chat messages.", ["CRApiChat", "CRChatCmdError"], [], Nothing, "/_get chat " <> Param "chatRef" <> Optional "" (" content=" <> Param "$0") "contentTag" <> " " <> Param "chatPagination" <> Optional "" (" search=" <> Param "$0") "search"), + ("APIChatItemsRead", [], "Mark messages as read.", ["CRItemsReadForChat", "CRChatCmdError"], [], Nothing, "/_read chat items " <> Param "chatRef" <> " " <> Join ',' "chatItemIds"), + ("APIContactInfo", [], "Get contact information.", ["CRContactInfo", "CRChatCmdError"], [], Nothing, "/_info @" <> Param "contactId"), + ("APIGetChatItemInfo", [], "Get message information.", ["CRChatItemInfo", "CRChatCmdError"], [], Nothing, "/_get item info " <> Param "chatRef" <> " " <> Param "chatItemId"), ("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"), ("APISetUserAutoAcceptGroupInvitations", [], "Set auto-accept group invitations.", ["CRCmdOk", "CRChatCmdError"], [], Nothing, "/_set accept group invitations " <> 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, ""), -- ("APIClearChat", [], "Clear chat.", ["CRChatCleared"], [], Nothing, ""), - -- ("APIGetChat", [], "Get chat.", ["CRApiChat"], [], Nothing, ""), - -- ("APIGetChatItemInfo", [], "Get message information.", ["CRChatItemInfo"], [], Nothing, ""), -- ("APIGetChatItems", [], "Get the most recent messages from all chats.", ["CRChatItems"], [], Nothing, ""), -- ("APIGetChats", [], "Get chats.", ["CRApiChats"], [], Nothing, ""), -- ("APISetChatSettings", [], "Set chat settings.", ["CRCmdOk"], [], Nothing, ""), @@ -209,7 +217,10 @@ chatCommandsDocsData = ( "Chat management", "These commands should not be used with CLI-based bots", [ ("StartChat", [], "Start chat controller.", ["CRChatStarted", "CRChatRunning"], [], Nothing, "/_start" <> OnOffParam "main" "mainApp" Nothing <> OnOffParam "snd_files" "enableSndFiles" (Just True) <> OnOffParam "service_requests" "serviceRequests" (Just False)), - ("APIStopChat", [], "Stop chat controller.", ["CRChatStopped"], [], Nothing, "/_stop") + ("APIStopChat", [], "Stop chat controller.", ["CRChatStopped"], [], Nothing, "/_stop"), + ("APIGetNetworkConfig", [], "Get network configuration.", ["CRNetworkConfig", "CRChatCmdError"], [], Nothing, "/network"), + ("APISetNetworkConfig", [], "Set network configuration.", ["CRCmdOk", "CRChatCmdError"], [], Just UNBackground, "/_network " <> Json "networkConfig"), + ("APIGetUserServers", [], "Get user servers and chat relays, grouped by operator.", ["CRUserServers", "CRChatCmdError"], [], Nothing, "/_servers " <> Param "userId") ] ), ( "Remote control commands", @@ -361,21 +372,14 @@ undocumentedCommands = "APIChangeConnectionUser", "APIChangePreparedContactUser", "APIChangePreparedGroupUser", - "APIChatItemsRead", "APIChatRead", "APIChatUnread", "APICheckToken", "APIClearChat", - "APIConnectContactViaAddress", - "APIConnectPreparedContact", - "APIConnectPreparedGroup", - "APIContactInfo", "APIContactQueueInfo", "APICreateBadgeInvoice", "APICreateChatItems", "APICreateChatTag", - "APICreateMemberContact", - "APISendMemberContactInvitation", "APIAcceptMemberContact", "APIDeleteChatTag", "APIDeleteMemberSupportChat", @@ -392,22 +396,18 @@ undocumentedCommands = "APIGetBadgeLedger", "APIGetBadgeState", "APIGetCallInvitations", - "APIGetChat", "APIGetChatContentTypes", - "APIGetChatItemInfo", "APIGetChatItems", "APIGetChatItemTTL", "APIGetChatTags", "APIGetConnNtfMessages", "APIGetContactCode", "APIGetGroupMemberCode", - "APIGetNetworkConfig", "APIGetNtfConns", "APIGetNtfToken", "APIGetReactionMembers", "APIGetServerOperators", "APIGetUsageConditions", - "APIGetUserServers", "APIGroupInfo", "APIGetUpdatedGroupLinkData", "APIGroupMemberInfo", @@ -416,8 +416,6 @@ undocumentedCommands = "APIImportArchive", "APIMuteUser", "APIPlanForwardChatItems", - "APIPrepareContact", - "APIPrepareGroup", "APIPurchaseBadge", "APIRedeemBadgeCode", "APIRegisterToken", @@ -446,7 +444,6 @@ undocumentedCommands = "APISetEncryptLocalFiles", "APISetGroupAlias", "APISetMemberSettings", - "APISetNetworkConfig", "APISetNetworkInfo", "APISetPublicGroupAccess", "APISetServerOperators", diff --git a/bots/src/API/Docs/Responses.hs b/bots/src/API/Docs/Responses.hs index d3ed348846..b806a41d40 100644 --- a/bots/src/API/Docs/Responses.hs +++ b/bots/src/API/Docs/Responses.hs @@ -47,6 +47,8 @@ chatResponsesDocsData :: [(ConsName, String)] chatResponsesDocsData = [ ("CRAcceptingContactRequest", "Contact request accepted"), ("CRActiveUser", "Active user profile"), + ("CRApiChat", "Chat and messages"), + ("CRChatItemInfo", "Message information"), ("CRChatItemNotChanged", "Message not changed"), ("CRChatItemReaction", "Message reaction"), ("CRChatItemUpdated", "Message updated"), @@ -61,6 +63,7 @@ chatResponsesDocsData = ("CRContactAlreadyExists", ""), ("CRContactConnectionDeleted", "Connection deleted"), ("CRContactDeleted", ""), + ("CRContactInfo", "Contact information"), ("CRContactPrefsUpdated", "Contact preferences updated"), ("CRContactRequestRejected", ""), ("CRContactsList", "Contacts"), @@ -80,11 +83,16 @@ chatResponsesDocsData = ("CRGroupsList", "Groups"), ("CRGroupDomainVerified", ""), ("CRInvitation", "One-time invitation"), + ("CRItemsReadForChat", "Messages marked as read"), ("CRLeftMemberUser", "User left group"), ("CRMemberAccepted", "Member accepted to group"), ("CRMembersBlockedForAllUser", "Members blocked for all by admin"), ("CRMembersRoleUser", "Members role changed by user"), + ("CRNetworkConfig", "Network configuration"), ("CRNewChatItems", "New messages"), + ("CRNewMemberContact", "Member contact created"), + ("CRNewMemberContactSentInv", "Member contact invitation sent"), + ("CRNewPreparedChat", "Prepared chat"), ("CRRcvFileAccepted", "File accepted to be received"), ("CRRcvFileAcceptedSndCancelled", "File accepted, but no longer sent"), ("CRRcvFileCancelled", "Cancelled receiving file"), @@ -107,18 +115,16 @@ chatResponsesDocsData = ("CRUserDeletedMembers", "Members deleted"), ("CRUserProfileUpdated", "User profile updated"), ("CRUserProfileNoChange", "User profile was not changed"), + ("CRUserServers", "User servers and chat relays, grouped by operator"), ("CRUsersList", "Users"), ("CRApiChats", "Chat previews (paginated). Use this instead of CRContactsList / CRGroupsList when scanning at scale.") - -- ("CRApiChat", "Chat and messages"), -- ("CRChatCleared", ""), - -- ("CRChatItemInfo", "Message information"), -- ("CRChatItems", "The most recent messages"), -- ("CRConnectionAliasUpdated", ""), -- ("CRContactAliasUpdated", ""), -- ("CRContactRatchetSyncStarted", "Contact encryption synchronization started"), -- ("CRGroupAliasUpdated", ""), -- ("CRGroupMemberRatchetSyncStarted", "Member encryption synchronization started"), - -- ("CRItemsReadForChat", "Messages marked as read"), -- ("CRReactionMembers", "Members who set reaction on the message"), ] @@ -131,7 +137,6 @@ undocumentedResponses = "CRAgentSubsTotal", "CRAgentWorkersDetails", "CRAgentWorkersSummary", - "CRApiChat", "CRAppSettings", "CRArchiveExported", "CRArchiveImported", @@ -145,7 +150,6 @@ undocumentedResponses = "CRChatContentTypes", "CRChatHelp", "CRChatItemId", - "CRChatItemInfo", "CRChatItems", "CRChatItemTTL", "CRChatRelayTestResult", @@ -159,7 +163,6 @@ undocumentedResponses = "CRConnNtfMessages", "CRContactAliasUpdated", "CRContactCode", - "CRContactInfo", "CRContactDomainVerified", "CRContactRatchetSyncStarted", "CRContactSwitchAborted", @@ -182,16 +185,11 @@ undocumentedResponses = "CRGroupMemberSwitchStarted", "CRGroupProfile", "CRGroupUserChanged", - "CRItemsReadForChat", "CRJoinedGroupMember", "CRMemberSupportChatRead", "CRMemberSupportChatDeleted", "CRMemberSupportChats", - "CRNetworkConfig", - "CRNewMemberContact", - "CRNewMemberContactSentInv", "CRMemberContactAccepted", - "CRNewPreparedChat", "CRNtfConns", "CRNtfToken", "CRNtfTokenStatus", @@ -215,7 +213,6 @@ undocumentedResponses = "CRUserPrivacy", "CRUserProfile", "CRUserProfileImage", - "CRUserServers", "CRUserServersValidation", "CRVersionInfo", "CRWelcome" diff --git a/bots/src/API/Docs/Types.hs b/bots/src/API/Docs/Types.hs index 26553b58cb..5b39c7f1c4 100644 --- a/bots/src/API/Docs/Types.hs +++ b/bots/src/API/Docs/Types.hs @@ -16,7 +16,7 @@ module API.Docs.Types where import API.Docs.Syntax.Types import API.TypeInfo import Data.Bifunctor (second) -import Data.Char (isUpper, toLower, toUpper) +import Data.Char (isLower, isUpper, toLower, toUpper) import Data.List (find, mapAccumL, sortOn) import qualified Data.List.NonEmpty as L import qualified Data.Map.Strict as M @@ -43,13 +43,15 @@ import Simplex.Chat.Types.Shared import Simplex.Chat.Types.UITheme import Simplex.FileTransfer.Transport import Simplex.FileTransfer.Types hiding (RcvFileStatus) -- the type with the same name is used in simplex-chat. +import Simplex.Messaging.Agent.Env.SQLite (ServerRoles (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Client import Simplex.Messaging.Crypto.File import Simplex.Messaging.Parsers (dropPrefix, fstToLower) -import Simplex.Messaging.Protocol (BlockingInfo (..), BlockingReason (..), CommandError (..), ErrorType (..), NameErrorType (..), NetworkError (..), ProxyError (..)) +import Simplex.Messaging.Protocol (BlockingInfo (..), BlockingReason (..), CommandError (..), ErrorType (..), NameErrorType (..), NetworkError (..), ProtocolType (..), ProxyError (..)) import Simplex.Messaging.Protocol.Types (ClientNotice (..)) import Simplex.Messaging.Transport +import Simplex.Messaging.Transport.KeepAlive (KeepAliveOpts (..)) import Simplex.Chat.Remote.Types (CtrlAppInfo (..)) import Simplex.RemoteControl.Types import System.Console.ANSI.Types (Color (..)) @@ -160,6 +162,9 @@ consLower pfx = map toLower . dropPrefix pfx consSep :: String -> Char -> ConsName -> String consSep pfx sep = foldr (\c s -> if isUpper c then sep : toLower c : s else c : s) "" . dropPrefix pfx +consSnake :: String -> ConsName -> String +consSnake pfx = snd . foldr (\c (upper, s) -> (isUpper c, if isLower c && upper then c : '_' : s else toLower c : s)) (False, "") . dropPrefix pfx + dropSuffix :: String -> String -> String dropSuffix sfx s = let (s', sfx') = splitAt (length s - length sfx) s @@ -260,6 +265,7 @@ chatTypesDocsData = (sti @CommandErrorType, STUnion, "", [], "", ""), (sti @CommentsGroupPreference, STRecord, "", [], "", ""), (sti @ComposedMessage, STRecord, "", [], "", ""), + (sti @ConditionsAcceptance, STUnion, "CA", [], "", ""), (sti @Connection, STRecord, "", [], "", ""), (sti @ConnectionEntity, STUnion, "", [], "", ""), (sti @ConnectionErrorType, STUnion, "", [], "", ""), @@ -307,6 +313,7 @@ chatTypesDocsData = (sti @GroupMemberRole, STEnum' (dropPfxSfx "GR" ""), "", ["GRUnknown"], "", ""), (sti @GroupMemberSettings, STRecord, "", [], "", ""), (sti @GroupMemberStatus, STEnum' ((\case "group_deleted" -> "deleted"; "intro_invited" -> "intro-inv"; s -> s) . consSep "GSMem" '_'), "", [], "", ""), + (sti @GroupOwnerContact, STRecord, "", [], "contact=" <> Param "contactId" <> " owner=" <> Param "memberId", ""), (sti @GroupPreference, STRecord, "", [], "", ""), (removeField "_json" $ sti @GroupPreferences, STRecord, "", [], "", ""), (sti @GroupProfile, STRecord, "", [], "", ""), @@ -317,9 +324,11 @@ chatTypesDocsData = (sti @GroupSupportChat, STRecord, "", [], "", ""), (sti @GroupType, STEnum, "GT", ["GTUnknown"], "", ""), (sti @HandshakeError, STEnum, "", [], "", ""), + (sti @HostMode, STEnum, "HM", [], "", ""), (sti @InlineFileMode, STEnum, "IFM", [], "", ""), (sti @InvitationLinkPlan, STUnion, "ILP", [], "", ""), (sti @InvitedBy, STUnion, "IB", [], "", ""), + (sti @KeepAliveOpts, STRecord, "", [], "", ""), (sti @LinkContent, STUnion, "LC", [], "", ""), (sti @LinkOwnerSig, STRecord, "", [], "", ""), (sti @LinkPreview, STRecord, "", [], "", ""), @@ -339,12 +348,15 @@ chatTypesDocsData = (sti @MsgSigStatus, STEnum, "MSS", [], "", ""), (sti @MsgVerified, STUnion, "MV", [], "", ""), (sti @NameErrorType, STUnion, "", [], "", ""), + (sti @NetworkConfig, STRecord, "", [], "", ""), (sti @NetworkError, STUnion, "NE", [], "", ""), + (sti @NetworkTimeout, STRecord, "", [], "", ""), (sti @NewUser, STRecord, "", [], "", ""), (sti @NoteFolder, STRecord, "", [], "", ""), + (sti @OperatorTag, STEnum, "OT", [], "", ""), (sti @OwnerVerification, STUnion, "OV", [], "", ""), (sti @PendingContactConnection, STRecord, "", [], "", ""), - (sti @PlanResolveMode, STEnum, "PRM", [], "", ""), + (sti @PlanResolveMode, STEnum, "PRM", [], Choice "self" [("allGroups", " resolve=allGroups"), ("never", " resolve=never")] "", ""), (sti @PrefEnabled, STRecord, "", [], "", ""), (removeField "_json" $ sti @Preferences, STRecord, "", [], "", ""), (sti @PreparedContact, STRecord, "", [], "", ""), @@ -356,6 +368,7 @@ chatTypesDocsData = (sti @PublicGroupAccess, STRecord, "", [], "", ""), (sti @PublicGroupData, STRecord, "", [], "", ""), (sti @PublicGroupProfile, STRecord, "", [], "", ""), + (sti @QueueStatus, STEnum, "", [], "", ""), (sti @RatchetSyncState, STEnum, "RS", [], "", ""), (sti @RCErrorType, STUnion, "RCE", [], "", ""), (sti @RcvConnEvent, STUnion, "RCE", [], "", ""), @@ -376,6 +389,9 @@ chatTypesDocsData = (sti @RoleGroupPreference, STRecord, "", [], "", ""), (sti @SecurityCode, STRecord, "", [], "", ""), (sti @SegmentRange, STRecord, "", [], "", ""), + ((sti @ServerOperator) {typeName = "ServerOperator"}, STRecord, "", [], "", ""), + (sti @ServerRoles, STRecord, "", [], "", ""), + (sti @ServerRolesOverride, STRecord, "", [], "", ""), (sti @SimplePreference, STRecord, "", [], "", ""), (sti @SimplexDomain, STRecord, "", [], "", ""), (sti @SimplexDomainClaim, STRecord, "", [], "", ""), @@ -386,11 +402,15 @@ chatTypesDocsData = (sti @SimplexNameType, STEnum, "NT", [], "", ""), (sti @SimplexTLD, STEnum, "TLD", [], "", ""), (sti @SMPAgentError, STUnion, "", [], "", ""), + (sti @SMPProxyFallback, STEnum, "SPF", [], "", ""), + (sti @SMPProxyMode, STEnum, "SPM", [], "", ""), + (sti @SMPWebPortServers, STEnum, "SWP", [], "", ""), (sti @SndCIStatusProgress, STEnum, "SSP", [], "", ""), (sti @SndConnEvent, STUnion, "SCE", [], "", ""), (sti @SndError, STUnion, "SndErr", [], "", ""), (sti @SndFileTransfer, STRecord, "", [], "", ""), (sti @SndGroupEvent, STUnion, "SGE", [], "", ""), + (sti @SocksMode, STEnum, "SM", [], "", ""), (sti @SrvError, STUnion, "SrvErr", [], "", ""), (sti @StoreError, STUnion, "SE", [], "", ""), (sti @SubscriptionStatus, STUnion, "SS", [], "", ""), @@ -400,6 +420,7 @@ chatTypesDocsData = (sti @TimedMessagesGroupPreference, STRecord, "", [], "", ""), (sti @TimedMessagesPreference, STRecord, "", [], "", ""), (sti @TransportError, STUnion, "TE", [], "", ""), + (sti @TransportSessionMode, STEnum, "TSM", [], "", ""), (sti @UIColorMode, STEnum, "UCM", [], "", ""), (sti @UIColors, STRecord, "", [], "", ""), (sti @UIThemeEntityOverride, STRecord, "", [], "", ""), @@ -411,32 +432,34 @@ chatTypesDocsData = (sti @UserContactLink, STRecord, "", [], "", ""), (sti @UserContactRequest, STRecord, "", [], "", ""), (sti @UserInfo, STRecord, "", [], "", ""), + (sti @UserOperatorServers, STRecord, "", [], "", ""), (sti @UserProfileUpdateSummary, STRecord, "", [], "", ""), (sti @UserPwdHash, STRecord, "", [], "", ""), + ((sti @(UserServer 'PSMP)) {typeName = "UserServer"}, STRecord, "", [], "", ""), (sti @XFTPErrorType, STUnion, "", [], "", ""), (sti @XFTPRcvFile, STRecord, "", [], "", ""), (sti @XFTPSndFile, STRecord, "", [], "", ""), -- (sti @DatabaseError, STUnion, "DB", [], "", ""), - -- (sti @ChatItemInfo, STRecord, "", [], "", ""), - -- (sti @ChatItemVersion, STRecord, "", [], "", ""), + (sti @ChatItemInfo, STRecord, "", [], "", ""), + (sti @ChatItemVersion, STRecord, "", [], "", ""), (sti @ChatListQuery, STUnion, "CLQ", [], "", ""), -- (sti @ChatName, STRecord, "", [], "", ""), - -- (sti @ChatPagination, STRecord, "CP", [], "", ""), - -- (sti @ConnectionStats, STRecord, "", [], "", ""), - -- (sti @GroupSndStatus, STUnion, "GSS", [], "", ""), - -- (sti @MemberDeliveryStatus, STRecord, "", [], "", ""), + (sti @ChatPagination, STUnion, "CP", [], Choice "self" [("last", "count=" <> Param "count"), ("after", "after=" <> Param "chatItemId" <> " count=" <> Param "count"), ("before", "before=" <> Param "chatItemId" <> " count=" <> Param "count"), ("around", "around=" <> Param "chatItemId" <> " count=" <> Param "count")] ("initial=" <> Param "count"), ""), + (sti @ConnectionStats, STRecord, "", [], "", ""), + (sti @GroupSndStatus, STUnion, "GSS", [], "", ""), + (sti @MemberDeliveryStatus, STRecord, "", [], "", ""), -- (sti @MemberReaction, STRecord, "", [], "", ""), - -- (sti @MsgContentTag, (STEnum' $ dropPfxSfx "MC" '_'), "", ["MCUnknown_"], "", ""), - -- (sti @NavigationInfo, STRecord, "", [], "", ""), + (sti @MsgContentTag, STEnum' (dropPfxSfx "MC" "_"), "", ["MCUnknown_"], "", ""), + (sti @NavigationInfo, STRecord, "", [], "", ""), -- PTAfter / PTBefore are hidden — bots only need "tail last N chats". -- The wire format is parsed by paginationByTimeP in -- src/Simplex/Chat/Library/Commands.hs. - (sti @PaginationByTime, STUnion1, "PT", ["PTAfter", "PTBefore"], "count=" <> Param "count", "") - -- (sti @RcvQueueInfo, STRecord, "", [], "", ""), - -- (sti @RcvSwitchStatus, STEnum, "", [], "", ""), -- incorrect + (sti @PaginationByTime, STUnion1, "PT", ["PTAfter", "PTBefore"], "count=" <> Param "count", ""), + (sti @RcvQueueInfo, STRecord, "", [], "", ""), + (sti @RcvSwitchStatus, STEnum' (consSnake "RS"), "", [], "", ""), -- (sti @SendRef, STRecord, "", [], "", ""), - -- (sti @SndQueueInfo, STRecord, "", [], "", ""), - -- (sti @SndSwitchStatus, STEnum, "", [], "", ""), -- incorrect + (sti @SndQueueInfo, STRecord, "", [], "", ""), + (sti @SndSwitchStatus, STEnum' (consSnake "SS"), "", [], "", "") ] data SimplePreference = SimplePreference {allow :: FeatureAllowed} deriving (Generic) @@ -499,6 +522,7 @@ deriving instance Generic CommandError deriving instance Generic CommandErrorType deriving instance Generic CommentsGroupPreference deriving instance Generic ComposedMessage +deriving instance Generic ConditionsAcceptance deriving instance Generic Connection deriving instance Generic ConnectionEntity deriving instance Generic ConnectionErrorType @@ -546,6 +570,7 @@ deriving instance Generic GroupMemberRef deriving instance Generic GroupMemberRole deriving instance Generic GroupMemberSettings deriving instance Generic GroupMemberStatus +deriving instance Generic GroupOwnerContact deriving instance Generic GroupPreference deriving instance Generic GroupPreferences deriving instance Generic GroupProfile @@ -556,6 +581,7 @@ deriving instance Generic GroupType deriving instance Generic GroupSummary deriving instance Generic GroupSupportChat deriving instance Generic HandshakeError +deriving instance Generic HostMode deriving instance Generic InlineFileMode deriving instance Generic InvitationLinkPlan deriving instance Generic InvitedBy @@ -566,6 +592,7 @@ deriving instance Generic JSONCIDeleted deriving instance Generic JSONCIDirection deriving instance Generic JSONCIFileStatus deriving instance Generic JSONCIStatus +deriving instance Generic KeepAliveOpts deriving instance Generic LinkContent deriving instance Generic LinkOwnerSig deriving instance Generic LinkPreview @@ -585,9 +612,12 @@ deriving instance Generic MsgReceiptStatus deriving instance Generic MsgSigStatus deriving instance Generic MsgVerified deriving instance Generic NameErrorType +deriving instance Generic NetworkConfig deriving instance Generic NetworkError +deriving instance Generic NetworkTimeout deriving instance Generic NewUser deriving instance Generic NoteFolder +deriving instance Generic OperatorTag deriving instance Generic OwnerVerification deriving instance Generic PendingContactConnection deriving instance Generic PlanResolveMode @@ -602,6 +632,7 @@ deriving instance Generic ProxyError deriving instance Generic PublicGroupAccess deriving instance Generic PublicGroupData deriving instance Generic PublicGroupProfile +deriving instance Generic QueueStatus deriving instance Generic RatchetSyncState deriving instance Generic RCErrorType deriving instance Generic RcvConnEvent @@ -621,6 +652,9 @@ deriving instance Generic RemoteCtrlStopReason deriving instance Generic ReportReason deriving instance Generic SecurityCode deriving instance Generic SegmentRange +deriving instance Generic (ServerOperator' 'DBStored) +deriving instance Generic ServerRoles +deriving instance Generic ServerRolesOverride deriving instance Generic SimplexDomain deriving instance Generic SimplexDomainClaim deriving instance Generic SimplexDomainError @@ -630,11 +664,15 @@ deriving instance Generic SimplexNameInfo deriving instance Generic SimplexNameType deriving instance Generic SimplexTLD deriving instance Generic SMPAgentError +deriving instance Generic SMPProxyFallback +deriving instance Generic SMPProxyMode +deriving instance Generic SMPWebPortServers deriving instance Generic SndCIStatusProgress deriving instance Generic SndConnEvent deriving instance Generic SndError deriving instance Generic SndFileTransfer deriving instance Generic SndGroupEvent +deriving instance Generic SocksMode deriving instance Generic SrvError deriving instance Generic StoreError deriving instance Generic SubscriptionStatus @@ -644,6 +682,7 @@ deriving instance Generic TableAlign deriving instance Generic TimedMessagesGroupPreference deriving instance Generic TimedMessagesPreference deriving instance Generic TransportError +deriving instance Generic TransportSessionMode deriving instance Generic UIColorMode deriving instance Generic UIColors deriving instance Generic UIThemeEntityOverride @@ -655,28 +694,30 @@ deriving instance Generic UserContact deriving instance Generic UserContactLink deriving instance Generic UserContactRequest deriving instance Generic UserInfo +deriving instance Generic UserOperatorServers deriving instance Generic UserProfileUpdateSummary deriving instance Generic UserPwdHash +deriving instance Generic (UserServer' 'DBStored p) deriving instance Generic XFTPErrorType deriving instance Generic XFTPRcvFile deriving instance Generic XFTPSndFile -- deriving instance Generic DatabaseError --- deriving instance Generic ChatItemInfo --- deriving instance Generic ChatItemVersion +deriving instance Generic ChatItemInfo +deriving instance Generic ChatItemVersion deriving instance Generic ChatListQuery -- deriving instance Generic ChatName --- deriving instance Generic ChatPagination --- deriving instance Generic ConnectionStats +deriving instance Generic ChatPagination +deriving instance Generic ConnectionStats -- deriving instance Generic Group --- deriving instance Generic GroupSndStatus --- deriving instance Generic MemberDeliveryStatus +deriving instance Generic GroupSndStatus +deriving instance Generic MemberDeliveryStatus -- deriving instance Generic MemberReaction --- deriving instance Generic MsgContentTag --- deriving instance Generic NavigationInfo +deriving instance Generic MsgContentTag +deriving instance Generic NavigationInfo deriving instance Generic PaginationByTime --- deriving instance Generic RcvQueueInfo --- deriving instance Generic RcvSwitchStatus +deriving instance Generic RcvQueueInfo +deriving instance Generic RcvSwitchStatus -- deriving instance Generic SendRef --- deriving instance Generic SndQueueInfo --- deriving instance Generic SndSwitchStatus +deriving instance Generic SndQueueInfo +deriving instance Generic SndSwitchStatus diff --git a/bots/src/API/TypeInfo.hs b/bots/src/API/TypeInfo.hs index 59bfe4e52e..fc1c894be3 100644 --- a/bots/src/API/TypeInfo.hs +++ b/bots/src/API/TypeInfo.hs @@ -174,8 +174,11 @@ toTypeInfo tr = "BoolDef" -> ST TBool [] "PQEncryption" -> ST TBool [] "PQSupport" -> ST TBool [] + "AChatInfo" -> ST "ChatInfo" [] "ACreatedConnLink" -> ST "CreatedConnLink" [] "UserChatRelay'" -> ST "UserChatRelay" [] + "ServerOperator'" -> ST "ServerOperator" [] + "UserServer'" -> ST "UserServer" [] "CChatItem" -> ST "ChatItem" [] "FormatColor" -> ST "Color" [] "CustomData" -> ST "JSONObject" [] @@ -219,11 +222,13 @@ toTypeInfo tr = "PrivateKey", "ProofPresHeader", "PublicKey", + "ProtoServerWithAuth", "ProtocolServer", "RCSignedInvitation", "SbKey", "SharedMsgId", "Signature", + "SocksProxyWithAuth", "StrJSON", "TransportHost", "UIColor", diff --git a/packages/simplex-chat-client/types/typescript/src/commands.ts b/packages/simplex-chat-client/types/typescript/src/commands.ts index fc1530f674..cef8bc0494 100644 --- a/packages/simplex-chat-client/types/typescript/src/commands.ts +++ b/packages/simplex-chat-client/types/typescript/src/commands.ts @@ -389,6 +389,36 @@ export namespace APIListMembers { } } +// Create a direct message contact with a group member. +// Network usage: interactive. +export interface APICreateMemberContact { + groupId: number // int64 + groupMemberId: number // int64 +} + +export namespace APICreateMemberContact { + export type Response = CR.NewMemberContact | CR.ChatCmdError + + export function cmdString(self: APICreateMemberContact): string { + return '/_create member contact #' + self.groupId + ' ' + self.groupMemberId + } +} + +// Send a direct message invitation to a group member contact. +// Network usage: background. +export interface APISendMemberContactInvitation { + contactId: number // int64 + msgContent_?: T.MsgContent +} + +export namespace APISendMemberContactInvitation { + export type Response = CR.NewMemberContactSentInv | CR.ChatCmdError + + export function cmdString(self: APISendMemberContactInvitation): string { + return '/_invite member contact @' + self.contactId + (self.msgContent_ ? ' json ' + JSON.stringify(self.msgContent_) : '') + } +} + // Create group. // Network usage: no. export interface APINewGroup { @@ -586,7 +616,7 @@ export namespace APIConnectPlan { export type Response = CR.ConnectionPlan | CR.ChatCmdError export function cmdString(self: APIConnectPlan): string { - return '/_connect plan ' + self.userId + ' ' + self.connectTarget + return '/_connect plan ' + self.userId + ' ' + self.connectTarget + T.PlanResolveMode.cmdString(self.resolveMode) + (self.linkOwnerSig ? ' sig=' + JSON.stringify(self.linkOwnerSig) : '') } } @@ -629,6 +659,90 @@ export namespace Connect { } } +// Prepare contact chat from link data before connecting. +// Network usage: no. +export interface APIPrepareContact { + userId: number // int64 + connLink: T.CreatedConnLink + verifiedDomain?: string + contactSLinkData: T.ContactShortLinkData +} + +export namespace APIPrepareContact { + export type Response = CR.NewPreparedChat | CR.ChatCmdError + + export function cmdString(self: APIPrepareContact): string { + return '/_prepare contact ' + self.userId + ' ' + T.CreatedConnLink.cmdString(self.connLink) + (self.verifiedDomain ? ' domain=' + self.verifiedDomain : '') + ' ' + JSON.stringify(self.contactSLinkData) + } +} + +// Prepare group chat from link data before connecting. +// Network usage: no. +export interface APIPrepareGroup { + userId: number // int64 + connLinkContact: T.CreatedConnLink + direct: boolean + verifiedDomain?: string + groupSLinkData: T.GroupShortLinkData +} + +export namespace APIPrepareGroup { + export type Response = CR.NewPreparedChat | CR.ChatCmdError + + export function cmdString(self: APIPrepareGroup): string { + return '/_prepare group ' + self.userId + ' ' + T.CreatedConnLink.cmdString(self.connLinkContact) + (!self.direct ? ' direct=off' : '') + (self.verifiedDomain ? ' domain=' + self.verifiedDomain : '') + ' ' + JSON.stringify(self.groupSLinkData) + } +} + +// Connect to prepared contact. +// Network usage: interactive. +export interface APIConnectPreparedContact { + contactId: number // int64 + incognito: boolean + msgContent_?: T.MsgContent +} + +export namespace APIConnectPreparedContact { + export type Response = CR.StartedConnectionToContact | CR.ContactAlreadyExists | CR.ChatCmdError + + export function cmdString(self: APIConnectPreparedContact): string { + return '/_connect contact @' + self.contactId + (self.incognito ? ' incognito=on' : '') + (self.msgContent_ ? ' json ' + JSON.stringify(self.msgContent_) : '') + } +} + +// Connect to prepared group. +// Network usage: interactive. +export interface APIConnectPreparedGroup { + groupId: number // int64 + incognito: boolean + ownerContact?: T.GroupOwnerContact + msgContent_?: T.MsgContent +} + +export namespace APIConnectPreparedGroup { + export type Response = CR.StartedConnectionToGroup | CR.ChatCmdError + + export function cmdString(self: APIConnectPreparedGroup): string { + return '/_connect group #' + self.groupId + (self.incognito ? ' incognito=on' : '') + (self.ownerContact ? ' ' + T.GroupOwnerContact.cmdString(self.ownerContact) : '') + (self.msgContent_ ? ' json ' + JSON.stringify(self.msgContent_) : '') + } +} + +// Connect to existing contact via address in its profile. +// Network usage: interactive. +export interface APIConnectContactViaAddress { + userId: number // int64 + incognito: boolean + contactId: number // int64 +} + +export namespace APIConnectContactViaAddress { + export type Response = CR.SentInvitationToContact | CR.ChatCmdError + + export function cmdString(self: APIConnectContactViaAddress): string { + return '/_connect contact ' + self.userId + (self.incognito ? ' incognito=on' : '') + ' ' + self.contactId + } +} + // Accept contact request. // Network usage: interactive. export interface APIAcceptContact { @@ -708,6 +822,67 @@ export namespace APIGetChats { } } +// Get chat messages. +// Network usage: no. +export interface APIGetChat { + chatRef: T.ChatRef + contentTag?: T.MsgContentTag + chatPagination: T.ChatPagination + search?: string +} + +export namespace APIGetChat { + export type Response = CR.ApiChat | CR.ChatCmdError + + export function cmdString(self: APIGetChat): string { + return '/_get chat ' + T.ChatRef.cmdString(self.chatRef) + (self.contentTag ? ' content=' + self.contentTag : '') + ' ' + T.ChatPagination.cmdString(self.chatPagination) + (self.search ? ' search=' + self.search : '') + } +} + +// Mark messages as read. +// Network usage: no. +export interface APIChatItemsRead { + chatRef: T.ChatRef + chatItemIds: number[] // int64, non-empty +} + +export namespace APIChatItemsRead { + export type Response = CR.ItemsReadForChat | CR.ChatCmdError + + export function cmdString(self: APIChatItemsRead): string { + return '/_read chat items ' + T.ChatRef.cmdString(self.chatRef) + ' ' + self.chatItemIds.join(',') + } +} + +// Get contact information. +// Network usage: no. +export interface APIContactInfo { + contactId: number // int64 +} + +export namespace APIContactInfo { + export type Response = CR.ContactInfo | CR.ChatCmdError + + export function cmdString(self: APIContactInfo): string { + return '/_info @' + self.contactId + } +} + +// Get message information. +// Network usage: no. +export interface APIGetChatItemInfo { + chatRef: T.ChatRef + chatItemId: number // int64 +} + +export namespace APIGetChatItemInfo { + export type Response = CR.ChatItemInfo | CR.ChatCmdError + + export function cmdString(self: APIGetChatItemInfo): string { + return '/_get item info ' + T.ChatRef.cmdString(self.chatRef) + ' ' + self.chatItemId + } +} + // Delete chat. // Network usage: background. export interface APIDeleteChat { @@ -938,6 +1113,47 @@ export namespace APIStopChat { } } +// Get network configuration. +// Network usage: no. +export interface APIGetNetworkConfig { +} + +export namespace APIGetNetworkConfig { + export type Response = CR.NetworkConfig | CR.ChatCmdError + + export function cmdString(_self: APIGetNetworkConfig): string { + return '/network' + } +} + +// Set network configuration. +// Network usage: background. +export interface APISetNetworkConfig { + networkConfig: T.NetworkConfig +} + +export namespace APISetNetworkConfig { + export type Response = CR.CmdOk | CR.ChatCmdError + + export function cmdString(self: APISetNetworkConfig): string { + return '/_network ' + JSON.stringify(self.networkConfig) + } +} + +// Get user servers and chat relays, grouped by operator. +// Network usage: no. +export interface APIGetUserServers { + userId: number // int64 +} + +export namespace APIGetUserServers { + export type Response = CR.UserServers | CR.ChatCmdError + + export function cmdString(self: APIGetUserServers): string { + return '/_servers ' + self.userId + } +} + // Remote control commands // Allows a bot to accept an incoming remote control session from a SimpleX Desktop client, giving the desktop live access to the bot's SimpleX instance. diff --git a/packages/simplex-chat-client/types/typescript/src/responses.ts b/packages/simplex-chat-client/types/typescript/src/responses.ts index ef83e89e9d..561c02edba 100644 --- a/packages/simplex-chat-client/types/typescript/src/responses.ts +++ b/packages/simplex-chat-client/types/typescript/src/responses.ts @@ -6,6 +6,8 @@ import * as T from "./types" export type ChatResponse = | CR.AcceptingContactRequest | CR.ActiveUser + | CR.ApiChat + | CR.ChatItemInfo | CR.ChatItemNotChanged | CR.ChatItemReaction | CR.ChatItemUpdated @@ -20,6 +22,7 @@ export type ChatResponse = | CR.ContactAlreadyExists | CR.ContactConnectionDeleted | CR.ContactDeleted + | CR.ContactInfo | CR.ContactPrefsUpdated | CR.ContactRequestRejected | CR.ContactsList @@ -39,11 +42,16 @@ export type ChatResponse = | CR.GroupsList | CR.GroupDomainVerified | CR.Invitation + | CR.ItemsReadForChat | CR.LeftMemberUser | CR.MemberAccepted | CR.MembersBlockedForAllUser | CR.MembersRoleUser + | CR.NetworkConfig | CR.NewChatItems + | CR.NewMemberContact + | CR.NewMemberContactSentInv + | CR.NewPreparedChat | CR.RcvFileAccepted | CR.RcvFileAcceptedSndCancelled | CR.RcvFileCancelled @@ -66,6 +74,7 @@ export type ChatResponse = | CR.UserDeletedMembers | CR.UserProfileUpdated | CR.UserProfileNoChange + | CR.UserServers | CR.UsersList | CR.ApiChats @@ -73,6 +82,8 @@ export namespace CR { export type Tag = | "acceptingContactRequest" | "activeUser" + | "apiChat" + | "chatItemInfo" | "chatItemNotChanged" | "chatItemReaction" | "chatItemUpdated" @@ -87,6 +98,7 @@ export namespace CR { | "contactAlreadyExists" | "contactConnectionDeleted" | "contactDeleted" + | "contactInfo" | "contactPrefsUpdated" | "contactRequestRejected" | "contactsList" @@ -106,11 +118,16 @@ export namespace CR { | "groupsList" | "groupDomainVerified" | "invitation" + | "itemsReadForChat" | "leftMemberUser" | "memberAccepted" | "membersBlockedForAllUser" | "membersRoleUser" + | "networkConfig" | "newChatItems" + | "newMemberContact" + | "newMemberContactSentInv" + | "newPreparedChat" | "rcvFileAccepted" | "rcvFileAcceptedSndCancelled" | "rcvFileCancelled" @@ -133,6 +150,7 @@ export namespace CR { | "userDeletedMembers" | "userProfileUpdated" | "userProfileNoChange" + | "userServers" | "usersList" | "apiChats" @@ -151,6 +169,20 @@ export namespace CR { user: T.User } + export interface ApiChat extends Interface { + type: "apiChat" + user: T.User + chat: T.AChat + navInfo?: T.NavigationInfo + } + + export interface ChatItemInfo extends Interface { + type: "chatItemInfo" + user: T.User + chatItem: T.AChatItem + chatItemInfo: T.ChatItemInfo + } + export interface ChatItemNotChanged extends Interface { type: "chatItemNotChanged" user: T.User @@ -233,6 +265,14 @@ export namespace CR { contact: T.Contact } + export interface ContactInfo extends Interface { + type: "contactInfo" + user: T.User + contact: T.Contact + connectionStats_?: T.ConnectionStats + customUserProfile?: T.Profile + } + export interface ContactPrefsUpdated extends Interface { type: "contactPrefsUpdated" user: T.User @@ -363,6 +403,12 @@ export namespace CR { connection: T.PendingContactConnection } + export interface ItemsReadForChat extends Interface { + type: "itemsReadForChat" + user: T.User + chatInfo: T.ChatInfo + } + export interface LeftMemberUser extends Interface { type: "leftMemberUser" user: T.User @@ -394,12 +440,39 @@ export namespace CR { msgSigned: boolean } + export interface NetworkConfig extends Interface { + type: "networkConfig" + networkConfig: T.NetworkConfig + } + export interface NewChatItems extends Interface { type: "newChatItems" user: T.User chatItems: T.AChatItem[] } + export interface NewMemberContact extends Interface { + type: "newMemberContact" + user: T.User + contact: T.Contact + groupInfo: T.GroupInfo + member: T.GroupMember + } + + export interface NewMemberContactSentInv extends Interface { + type: "newMemberContactSentInv" + user: T.User + contact: T.Contact + groupInfo: T.GroupInfo + member: T.GroupMember + } + + export interface NewPreparedChat extends Interface { + type: "newPreparedChat" + user: T.User + chat: T.AChat + } + export interface RcvFileAccepted extends Interface { type: "rcvFileAccepted" user: T.User @@ -551,6 +624,12 @@ export namespace CR { user: T.User } + export interface UserServers extends Interface { + type: "userServers" + user: T.User + userServers: T.UserOperatorServers[] + } + export interface UsersList extends Interface { type: "usersList" users: T.UserInfo[] diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 8514cbd43c..318ab03feb 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -1887,6 +1887,22 @@ export interface ChatItemDeletion { toChatItem?: AChatItem } +export interface ChatItemInfo { + itemVersions: ChatItemVersion[] + memberDeliveryStatuses?: MemberDeliveryStatus[] + forwardedFromChatItem?: AChatItem + fileXftpServers: string[] +} + +export interface ChatItemVersion { + chatItemVersionId: number // int64 + msgContent: MsgContent + formattedText?: FormattedText[] + markdownBlocks?: MarkdownBlock[] + itemVersionTs: string // ISO-8601 timestamp + createdAt: string // ISO-8601 timestamp +} + export enum ChatLayout { Classic = "classic", Terminal = "terminal", @@ -1913,6 +1929,53 @@ export namespace ChatListQuery { } } +export type ChatPagination = + | ChatPagination.Last + | ChatPagination.After + | ChatPagination.Before + | ChatPagination.Around + | ChatPagination.Initial + +export namespace ChatPagination { + export type Tag = "last" | "after" | "before" | "around" | "initial" + + interface Interface { + type: Tag + } + + export interface Last extends Interface { + type: "last" + count: number // int + } + + export interface After extends Interface { + type: "after" + chatItemId: number // int64 + count: number // int + } + + export interface Before extends Interface { + type: "before" + chatItemId: number // int64 + count: number // int + } + + export interface Around extends Interface { + type: "around" + chatItemId: number // int64 + count: number // int + } + + export interface Initial extends Interface { + type: "initial" + count: number // int + } + + export function cmdString(self: ChatPagination): string { + return self.type == 'last' ? 'count=' + self.count : self.type == 'after' ? 'after=' + self.chatItemId + ' count=' + self.count : self.type == 'before' ? 'before=' + self.chatItemId + ' count=' + self.count : self.type == 'around' ? 'around=' + self.chatItemId + ' count=' + self.count : 'initial=' + self.count + } +} + export enum ChatPeerType { Human = "human", Bot = "bot", @@ -2076,6 +2139,27 @@ export interface ComposedMessage { mentions: {[key: string]: number} // string : int64 } +export type ConditionsAcceptance = ConditionsAcceptance.Accepted | ConditionsAcceptance.Required + +export namespace ConditionsAcceptance { + export type Tag = "accepted" | "required" + + interface Interface { + type: Tag + } + + export interface Accepted extends Interface { + type: "accepted" + acceptedAt?: string // ISO-8601 timestamp + autoAccepted: boolean + } + + export interface Required extends Interface { + type: "required" + deadline?: string // ISO-8601 timestamp + } +} + export type ConnStatus = | ConnStatus.New | ConnStatus.Prepared @@ -2280,6 +2364,15 @@ export namespace ConnectionPlan { } } +export interface ConnectionStats { + connAgentVersion: number // int + rcvQueuesInfo: RcvQueueInfo[] + sndQueuesInfo: SndQueueInfo[] + ratchetSyncState: RatchetSyncState + ratchetSyncSupported: boolean + subStatus?: SubscriptionStatus +} + export interface Contact { contactId: number // int64 localDisplayName: string @@ -3098,6 +3191,17 @@ export enum GroupMemberStatus { Creator = "creator", } +export interface GroupOwnerContact { + contactId: number // int64 + memberId: string +} + +export namespace GroupOwnerContact { + export function cmdString(self: GroupOwnerContact): string { + return 'contact=' + self.contactId + ' owner=' + self.memberId + } +} + export interface GroupPreference { enable: GroupFeatureEnabled } @@ -3151,6 +3255,68 @@ export interface GroupShortLinkInfo { publicGroupId?: string } +export type GroupSndStatus = + | GroupSndStatus.New + | GroupSndStatus.Forwarded + | GroupSndStatus.Inactive + | GroupSndStatus.Sent + | GroupSndStatus.Rcvd + | GroupSndStatus.Error + | GroupSndStatus.Warning + | GroupSndStatus.Invalid + +export namespace GroupSndStatus { + export type Tag = + | "new" + | "forwarded" + | "inactive" + | "sent" + | "rcvd" + | "error" + | "warning" + | "invalid" + + interface Interface { + type: Tag + } + + export interface New extends Interface { + type: "new" + } + + export interface Forwarded extends Interface { + type: "forwarded" + } + + export interface Inactive extends Interface { + type: "inactive" + } + + export interface Sent extends Interface { + type: "sent" + } + + export interface Rcvd extends Interface { + type: "rcvd" + msgRcptStatus: MsgReceiptStatus + } + + export interface Error extends Interface { + type: "error" + agentError: SndError + } + + export interface Warning extends Interface { + type: "warning" + agentError: SndError + } + + export interface Invalid extends Interface { + type: "invalid" + text: string + } +} + export interface GroupSummary { currentMembers: number // int64 publicMemberCount?: number // int64 @@ -3176,6 +3342,12 @@ export enum HandshakeError { BAD_SERVICE = "BAD_SERVICE", } +export enum HostMode { + OnionViaSocks = "onionViaSocks", + Onion = "onion", + Public = "public", +} + export enum InlineFileMode { Offer = "offer", Sent = "sent", @@ -3238,6 +3410,12 @@ export namespace InvitedBy { } } +export interface KeepAliveOpts { + keepIdle: number // int + keepIntvl: number // int + keepCnt: number // int +} + export type LinkContent = LinkContent.Page | LinkContent.Image | LinkContent.Video | LinkContent.Unknown export namespace LinkContent { @@ -3367,6 +3545,12 @@ export namespace MarkdownBlock { export enum MemberCriteria { All = "all", } + +export interface MemberDeliveryStatus { + groupMemberId: number // int64 + memberDeliveryStatus: GroupSndStatus + sentViaProxy?: boolean +} // Connection link sent in a message - only short links are allowed. export type MsgChatLink = MsgChatLink.Contact | MsgChatLink.Invitation | MsgChatLink.Group @@ -3472,6 +3656,17 @@ export namespace MsgContent { } } +export enum MsgContentTag { + Text = "text", + Link = "link", + Image = "image", + Video = "video", + Voice = "voice", + File = "file", + Report = "report", + Chat = "chat", +} + export enum MsgDecryptError { RatchetHeader = "ratchetHeader", TooManySkipped = "tooManySkipped", @@ -3597,6 +3792,30 @@ export namespace NameErrorType { } } +export interface NavigationInfo { + afterUnread: number // int + afterTotal: number // int +} + +export interface NetworkConfig { + socksProxy?: string + socksMode: SocksMode + hostMode: HostMode + requiredHostMode: boolean + sessionMode: TransportSessionMode + smpProxyMode: SMPProxyMode + smpProxyFallback: SMPProxyFallback + smpWebPortServers: SMPWebPortServers + tcpConnectTimeout: NetworkTimeout + tcpTimeout: NetworkTimeout + tcpTimeoutPerKb: number // int64 + rcvConcurrency: number // int + tcpKeepAlive?: KeepAliveOpts + smpPingInterval: number // int64 + smpPingCount: number // int + logTLSErrors: boolean +} + export type NetworkError = | NetworkError.ConnectError | NetworkError.TLSError @@ -3646,6 +3865,11 @@ export namespace NetworkError { } } +export interface NetworkTimeout { + backgroundTimeout: number // int + interactiveTimeout: number // int +} + export interface NewUser { profile?: Profile pastTimestamp: boolean @@ -3663,6 +3887,11 @@ export interface NoteFolder { unread: boolean } +export enum OperatorTag { + Simplex = "simplex", + Flux = "flux", +} + export type OwnerVerification = OwnerVerification.Verified | OwnerVerification.Failed export namespace OwnerVerification { @@ -3721,6 +3950,12 @@ export enum PlanResolveMode { Never = "never", } +export namespace PlanResolveMode { + export function cmdString(self: PlanResolveMode): string { + return self == 'allGroups' ? ' resolve=allGroups' : self == 'never' ? ' resolve=never' : '' + } +} + export interface PrefEnabled { forUser: boolean forContact: boolean @@ -3839,6 +4074,14 @@ export interface PublicGroupProfile { publicGroupAccess?: PublicGroupAccess } +export enum QueueStatus { + New = "new", + Confirmed = "confirmed", + Secured = "secured", + Active = "active", + Disabled = "disabled", +} + export type RCErrorType = | RCErrorType.Internal | RCErrorType.Identity @@ -4227,6 +4470,22 @@ export namespace RcvMsgError { } } +export interface RcvQueueInfo { + rcvServer: string + status: QueueStatus + rcvSwitchStatus?: RcvSwitchStatus + canAbortSwitch: boolean + subStatus: SubscriptionStatus +} + +export enum RcvSwitchStatus { + Switch_started = "switch_started", + Sending_qadd = "sending_qadd", + Sending_quse = "sending_quse", + Received_message = "received_message", + Received_qend = "received_qend", +} + export interface RelayCapabilities { webDomain?: string } @@ -4412,6 +4671,25 @@ export namespace SMPAgentError { } } +export enum SMPProxyFallback { + Allow = "allow", + AllowProtected = "allowProtected", + Prohibit = "prohibit", +} + +export enum SMPProxyMode { + Always = "always", + Unknown = "unknown", + Unprotected = "unprotected", + Never = "never", +} + +export enum SMPWebPortServers { + All = "all", + Preset = "preset", + Off = "off", +} + export interface SecurityCode { securityCode: string verifiedAt: string // ISO-8601 timestamp @@ -4422,6 +4700,30 @@ export interface SegmentRange { to: number // int } +export interface ServerOperator { + operatorId: number // int64 + operatorTag?: OperatorTag + tradeName: string + legalName?: string + serverDomains: string[] + conditionsAcceptance: ConditionsAcceptance + enabled: boolean + smpRoles: ServerRoles + xftpRoles: ServerRoles +} + +export interface ServerRoles { + storage: boolean + proxy: boolean + names: boolean +} + +export interface ServerRolesOverride { + storage?: boolean + proxy?: boolean + names?: boolean +} + export interface SimplePreference { allow: FeatureAllowed } @@ -4653,6 +4955,24 @@ export namespace SndGroupEvent { } } +export interface SndQueueInfo { + sndServer: string + status: QueueStatus + sndSwitchStatus?: SndSwitchStatus +} + +export enum SndSwitchStatus { + Sending_qkey = "sending_qkey", + Sending_qtest = "sending_qtest", + Securing_queue = "securing_queue", + Sending_qend = "sending_qend", +} + +export enum SocksMode { + Always = "always", + Onion = "onion", +} + export type SrvError = SrvError.Host | SrvError.Version | SrvError.Other export namespace SrvError { @@ -5395,6 +5715,13 @@ export namespace TransportError { } } +export enum TransportSessionMode { + User = "user", + Session = "session", + Server = "server", + Entity = "entity", +} + export enum UIColorMode { Light = "light", Dark = "dark", @@ -5507,6 +5834,13 @@ export interface UserInfo { unreadCount: number // int } +export interface UserOperatorServers { + operator?: ServerOperator + smpServers: UserServer[] + xftpServers: UserServer[] + chatRelays: UserChatRelay[] +} + export interface UserProfileUpdateSummary { updateSuccesses: number // int updateFailures: number // int @@ -5518,6 +5852,16 @@ export interface UserPwdHash { salt: string } +export interface UserServer { + serverId: number // int64 + server: string + preset: boolean + tested?: boolean + enabled: boolean + roles: ServerRolesOverride + deleted: boolean +} + export interface VersionRange { minVersion: number // int maxVersion: number // int diff --git a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md index bfd2034d94..da6ad81912 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.ChatApi.md @@ -6,7 +6,7 @@ # Class: ChatApi -Defined in: [src/api.ts:100](../api.ts#L100) +Defined in: [src/api.ts:103](../api.ts#L103) Main API class for interacting with the chat core library. @@ -16,7 +16,7 @@ Main API class for interacting with the chat core library. > `protected` **ctrl\_**: `bigint` \| `undefined` -Defined in: [src/api.ts:106](../api.ts#L106) +Defined in: [src/api.ts:109](../api.ts#L109) ## Accessors @@ -26,7 +26,7 @@ Defined in: [src/api.ts:106](../api.ts#L106) > **get** **ctrl**(): `bigint` -Defined in: [src/api.ts:345](../api.ts#L345) +Defined in: [src/api.ts:348](../api.ts#L348) Chat controller reference @@ -42,7 +42,7 @@ Chat controller reference > **get** **initialized**(): `boolean` -Defined in: [src/api.ts:331](../api.ts#L331) +Defined in: [src/api.ts:334](../api.ts#L334) Chat controller is initialized @@ -58,7 +58,7 @@ Chat controller is initialized > **get** **started**(): `boolean` -Defined in: [src/api.ts:338](../api.ts#L338) +Defined in: [src/api.ts:341](../api.ts#L341) Chat controller is started @@ -72,7 +72,7 @@ Chat controller is started > **apiAcceptContactRequest**(`contactReqId`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:773](../api.ts#L773) +Defined in: [src/api.ts:850](../api.ts#L850) Accept contact request. Network usage: interactive. @@ -93,7 +93,7 @@ Network usage: interactive. > **apiAcceptMember**(`groupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:593](../api.ts#L593) +Defined in: [src/api.ts:598](../api.ts#L598) Accept group member. Requires Admin role. Network usage: background. @@ -122,7 +122,7 @@ Network usage: background. > **apiAddMember**(`groupId`, `contactId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:563](../api.ts#L563) +Defined in: [src/api.ts:568](../api.ts#L568) Add contact to group. Requires bot to have Admin role. Network usage: interactive. @@ -151,7 +151,7 @@ Network usage: interactive. > **apiAddMemberViaGroup**(`groupId`, `viaGroupId`, `groupMemberId`, `memberRole`): `Promise`\<`GroupMember`\> -Defined in: [src/api.ts:573](../api.ts#L573) +Defined in: [src/api.ts:578](../api.ts#L578) Add member of another group to group. Requires bot to have Admin role in the group, and direct messages allowed for its role in another group. Network usage: interactive. @@ -184,7 +184,7 @@ Network usage: interactive. > **apiBlockMembersForAll**(`groupId`, `groupMemberIds`, `blocked`): `Promise`\<`void`\> -Defined in: [src/api.ts:613](../api.ts#L613) +Defined in: [src/api.ts:618](../api.ts#L618) Block members. Requires Moderator role. Network usage: background. @@ -213,7 +213,7 @@ Network usage: background. > **apiCancelFile**(`fileId`): `Promise`\<`void`\> -Defined in: [src/api.ts:553](../api.ts#L553) +Defined in: [src/api.ts:558](../api.ts#L558) Cancel file. Network usage: background. @@ -234,7 +234,7 @@ Network usage: background. > **apiChatItemReaction**(`chatType`, `chatId`, `chatItemId`, `add`, `reaction`): `Promise`\<`ACIReaction`\> -Defined in: [src/api.ts:526](../api.ts#L526) +Defined in: [src/api.ts:530](../api.ts#L530) Add/remove message reaction. Network usage: background. @@ -267,11 +267,40 @@ Network usage: background. *** +### apiChatItemsRead() + +> **apiChatItemsRead**(`chatType`, `chatId`, `chatItemIds`): `Promise`\<`ChatInfo`\> + +Defined in: [src/api.ts:967](../api.ts#L967) + +Mark messages as read. +Network usage: no. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### chatItemIds + +`number`[] + +#### Returns + +`Promise`\<`ChatInfo`\> + +*** + ### apiConnect() > **apiConnect**(`userId`, `incognito`, `preparedLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:742](../api.ts#L742) +Defined in: [src/api.ts:747](../api.ts#L747) Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link Network usage: interactive. @@ -300,7 +329,7 @@ Network usage: interactive. > **apiConnectActiveUser**(`connLink`): `Promise`\<[`ConnReqType`](api.Enumeration.ConnReqType.md)\> -Defined in: [src/api.ts:751](../api.ts#L751) +Defined in: [src/api.ts:828](../api.ts#L828) Connect via SimpleX link as string in the active user profile. Network usage: interactive. @@ -317,11 +346,40 @@ Network usage: interactive. *** +### apiConnectContactViaAddress() + +> **apiConnectContactViaAddress**(`userId`, `incognito`, `contactId`): `Promise`\<`SentInvitationToContact`\> + +Defined in: [src/api.ts:818](../api.ts#L818) + +Connect to existing contact via address in its profile. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +##### incognito + +`boolean` + +##### contactId + +`number` + +#### Returns + +`Promise`\<`SentInvitationToContact`\> + +*** + ### apiConnectPlan() -> **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> +> **apiConnectPlan**(`userId`, `connectionLink`): `Promise`\<\[`plan`: `ConnectionPlan`, `link`: `CreatedConnLink`, `planSimplexName?`: `SimplexNameInfo`, `otherSimplexName?`: `SimplexNameInfo`\]\> -Defined in: [src/api.ts:732](../api.ts#L732) +Defined in: [src/api.ts:737](../api.ts#L737) Determine SimpleX link type and if the bot is already connected via this link. Network usage: interactive. @@ -338,7 +396,120 @@ Network usage: interactive. #### Returns -`Promise`\<\[`ConnectionPlan`, `CreatedConnLink`\]\> +`Promise`\<\[`plan`: `ConnectionPlan`, `link`: `CreatedConnLink`, `planSimplexName?`: `SimplexNameInfo`, `otherSimplexName?`: `SimplexNameInfo`\]\> + +*** + +### apiConnectPreparedContact() + +> **apiConnectPreparedContact**(`contactId`, `incognito`, `msgContent?`): `Promise`\<`ContactAlreadyExists` \| `StartedConnectionToContact`\> + +Defined in: [src/api.ts:793](../api.ts#L793) + +Connect to prepared contact. +Network usage: interactive. + +#### Parameters + +##### contactId + +`number` + +##### incognito + +`boolean` + +##### msgContent? + +`MsgContent` + +#### Returns + +`Promise`\<`ContactAlreadyExists` \| `StartedConnectionToContact`\> + +*** + +### apiConnectPreparedGroup() + +> **apiConnectPreparedGroup**(`groupId`, `incognito`, `msgContent?`, `ownerContact?`): `Promise`\<`StartedConnectionToGroup`\> + +Defined in: [src/api.ts:808](../api.ts#L808) + +Connect to prepared group. +Network usage: interactive. + +#### Parameters + +##### groupId + +`number` + +##### incognito + +`boolean` + +##### msgContent? + +`MsgContent` + +##### ownerContact? + +`GroupOwnerContact` + +#### Returns + +`Promise`\<`StartedConnectionToGroup`\> + +*** + +### apiConnectPreparedLink() + +> **apiConnectPreparedLink**(`userId`, `incognito`, `preparedLink`): `Promise`\<`ContactAlreadyExists` \| `SentConfirmation` \| `SentInvitation`\> + +Defined in: [src/api.ts:757](../api.ts#L757) + +Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link. +Returns the response with the pending connection, or with the contact when it already exists. +Network usage: interactive. + +#### Parameters + +##### userId + +`number` + +##### incognito + +`boolean` + +##### preparedLink + +`CreatedConnLink` + +#### Returns + +`Promise`\<`ContactAlreadyExists` \| `SentConfirmation` \| `SentInvitation`\> + +*** + +### apiContactInfo() + +> **apiContactInfo**(`contactId`): `Promise`\<`ContactInfo`\> + +Defined in: [src/api.ts:987](../api.ts#L987) + +Get contact information. +Network usage: no. + +#### Parameters + +##### contactId + +`number` + +#### Returns + +`Promise`\<`ContactInfo`\> *** @@ -346,7 +517,7 @@ Network usage: interactive. > **apiCreateActiveUser**(`profile?`): `Promise`\<`User`\> -Defined in: [src/api.ts:910](../api.ts#L910) +Defined in: [src/api.ts:1047](../api.ts#L1047) Create new user profile Network usage: no. @@ -367,7 +538,7 @@ Network usage: no. > **apiCreateGroupLink**(`groupId`, `memberRole`): `Promise`\<`string`\> -Defined in: [src/api.ts:673](../api.ts#L673) +Defined in: [src/api.ts:678](../api.ts#L678) Create group link. Network usage: interactive. @@ -392,7 +563,7 @@ Network usage: interactive. > **apiCreateLink**(`userId`): `Promise`\<`string`\> -Defined in: [src/api.ts:719](../api.ts#L719) +Defined in: [src/api.ts:724](../api.ts#L724) Create 1-time invitation link. Network usage: interactive. @@ -413,7 +584,7 @@ Network usage: interactive. > **apiCreateMemberContact**(`groupId`, `groupMemberId`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:976](../api.ts#L976) +Defined in: [src/api.ts:1113](../api.ts#L1113) Create a direct message contact with a group member. Returns the created contact. @@ -439,7 +610,7 @@ Network usage: interactive. > **apiCreateUserAddress**(`userId`): `Promise`\<`CreatedConnLink`\> -Defined in: [src/api.ts:362](../api.ts#L362) +Defined in: [src/api.ts:365](../api.ts#L365) Create bot address. Network usage: interactive. @@ -460,7 +631,7 @@ Network usage: interactive. > **apiDeleteChat**(`chatType`, `chatId`, `deleteMode?`): `Promise`\<`void`\> -Defined in: [src/api.ts:832](../api.ts#L832) +Defined in: [src/api.ts:909](../api.ts#L909) Delete chat. Network usage: background. @@ -489,7 +660,7 @@ Network usage: background. > **apiDeleteChatItems**(`chatType`, `chatId`, `chatItemIds`, `deleteMode`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:501](../api.ts#L501) +Defined in: [src/api.ts:505](../api.ts#L505) Delete message. Network usage: background. @@ -522,7 +693,7 @@ Network usage: background. > **apiDeleteGroupLink**(`groupId`): `Promise`\<`void`\> -Defined in: [src/api.ts:695](../api.ts#L695) +Defined in: [src/api.ts:700](../api.ts#L700) Delete group link. Network usage: background. @@ -543,7 +714,7 @@ Network usage: background. > **apiDeleteMemberChatItem**(`groupId`, `chatItemIds`): `Promise`\<`ChatItemDeletion`[]\> -Defined in: [src/api.ts:516](../api.ts#L516) +Defined in: [src/api.ts:520](../api.ts#L520) Moderate message. Requires Moderator role (and higher than message author's). Network usage: background. @@ -568,7 +739,7 @@ Network usage: background. > **apiDeleteUser**(`userId`, `delSMPQueues`, `viewPwd?`): `Promise`\<`void`\> -Defined in: [src/api.ts:940](../api.ts#L940) +Defined in: [src/api.ts:1077](../api.ts#L1077) Delete user profile. Network usage: background. @@ -597,7 +768,7 @@ Network usage: background. > **apiDeleteUserAddress**(`userId`): `Promise`\<`void`\> -Defined in: [src/api.ts:372](../api.ts#L372) +Defined in: [src/api.ts:375](../api.ts#L375) Deletes a user address. Network usage: background. @@ -618,7 +789,7 @@ Network usage: background. > **apiGetActiveUser**(): `Promise`\<`User` \| `undefined`\> -Defined in: [src/api.ts:890](../api.ts#L890) +Defined in: [src/api.ts:1027](../api.ts#L1027) Get active user profile Network usage: no. @@ -631,9 +802,9 @@ Network usage: no. ### apiGetChat() -> **apiGetChat**(`chatType`, `chatId`, `count`): `Promise`\<`any`\> +> **apiGetChat**(`chatType`, `chatId`, `pagination`): `Promise`\<`AChat`\> -Defined in: [src/api.ts:880](../api.ts#L880) +Defined in: [src/api.ts:956](../api.ts#L956) Get chat items. Network usage: no. @@ -648,13 +819,42 @@ Network usage: no. `number` -##### count +##### pagination + +`number` \| `ChatPagination` + +#### Returns + +`Promise`\<`AChat`\> + +*** + +### apiGetChatItemInfo() + +> **apiGetChatItemInfo**(`chatType`, `chatId`, `chatItemId`): `Promise`\<`ChatItemInfo`\> + +Defined in: [src/api.ts:977](../api.ts#L977) + +Get message information. +Network usage: no. + +#### Parameters + +##### chatType + +`ChatType` + +##### chatId + +`number` + +##### chatItemId `number` #### Returns -`Promise`\<`any`\> +`Promise`\<`ChatItemInfo`\> *** @@ -662,7 +862,7 @@ Network usage: no. > **apiGetChats**(`userId`, `pagination`, `query?`, `pendingConnections?`): `Promise`\<`AChat`[]\> -Defined in: [src/api.ts:817](../api.ts#L817) +Defined in: [src/api.ts:894](../api.ts#L894) Get chat previews (paginated). Network usage: no. @@ -699,7 +899,7 @@ on large databases. > **apiGetGroupLink**(`groupId`): `Promise`\<`GroupLink`\> -Defined in: [src/api.ts:704](../api.ts#L704) +Defined in: [src/api.ts:709](../api.ts#L709) Get group link. Network usage: no. @@ -720,7 +920,7 @@ Network usage: no. > **apiGetGroupLinkStr**(`groupId`): `Promise`\<`string`\> -Defined in: [src/api.ts:710](../api.ts#L710) +Defined in: [src/api.ts:715](../api.ts#L715) #### Parameters @@ -734,11 +934,26 @@ Defined in: [src/api.ts:710](../api.ts#L710) *** +### apiGetNetworkConfig() + +> **apiGetNetworkConfig**(): `Promise`\<`NetworkConfig`\> + +Defined in: [src/api.ts:997](../api.ts#L997) + +Get network configuration. +Network usage: no. + +#### Returns + +`Promise`\<`NetworkConfig`\> + +*** + ### apiGetUserAddress() > **apiGetUserAddress**(`userId`): `Promise`\<`UserContactLink` \| `undefined`\> -Defined in: [src/api.ts:382](../api.ts#L382) +Defined in: [src/api.ts:385](../api.ts#L385) Get bot address and settings. Network usage: no. @@ -755,11 +970,32 @@ Network usage: no. *** +### apiGetUserServers() + +> **apiGetUserServers**(`userId`): `Promise`\<`UserOperatorServers`[]\> + +Defined in: [src/api.ts:1017](../api.ts#L1017) + +Get user servers and chat relays, grouped by operator. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +#### Returns + +`Promise`\<`UserOperatorServers`[]\> + +*** + ### apiJoinGroup() > **apiJoinGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:583](../api.ts#L583) +Defined in: [src/api.ts:588](../api.ts#L588) Join group. Network usage: interactive. @@ -780,7 +1016,7 @@ Network usage: interactive. > **apiLeaveGroup**(`groupId`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:633](../api.ts#L633) +Defined in: [src/api.ts:638](../api.ts#L638) Leave group. Network usage: background. @@ -801,7 +1037,7 @@ Network usage: background. > **apiListContacts**(`userId`): `Promise`\<`Contact`[]\> -Defined in: [src/api.ts:793](../api.ts#L793) +Defined in: [src/api.ts:870](../api.ts#L870) Get contacts. Network usage: no. @@ -822,7 +1058,7 @@ Network usage: no. > **apiListGroups**(`userId`, `contactId?`, `search?`): `Promise`\<`GroupInfo`[]\> -Defined in: [src/api.ts:803](../api.ts#L803) +Defined in: [src/api.ts:880](../api.ts#L880) Get groups. Network usage: no. @@ -851,7 +1087,7 @@ Network usage: no. > **apiListMembers**(`groupId`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:643](../api.ts#L643) +Defined in: [src/api.ts:648](../api.ts#L648) Get group members. Network usage: no. @@ -872,7 +1108,7 @@ Network usage: no. > **apiListUsers**(): `Promise`\<`UserInfo`[]\> -Defined in: [src/api.ts:920](../api.ts#L920) +Defined in: [src/api.ts:1057](../api.ts#L1057) Get all user profiles Network usage: no. @@ -887,7 +1123,7 @@ Network usage: no. > **apiNewGroup**(`userId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:653](../api.ts#L653) +Defined in: [src/api.ts:658](../api.ts#L658) Create group. Network usage: no. @@ -908,13 +1144,84 @@ Network usage: no. *** +### apiPrepareContact() + +> **apiPrepareContact**(`userId`, `connLink`, `contactSLinkData`, `verifiedDomain?`): `Promise`\<`AChat`\> + +Defined in: [src/api.ts:773](../api.ts#L773) + +Prepare contact chat from link data before connecting. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### connLink + +`CreatedConnLink` + +##### contactSLinkData + +`ContactShortLinkData` + +##### verifiedDomain? + +`string` + +#### Returns + +`Promise`\<`AChat`\> + +*** + +### apiPrepareGroup() + +> **apiPrepareGroup**(`userId`, `connLinkContact`, `direct`, `groupSLinkData`, `verifiedDomain?`): `Promise`\<`AChat`\> + +Defined in: [src/api.ts:783](../api.ts#L783) + +Prepare group chat from link data before connecting. +Network usage: no. + +#### Parameters + +##### userId + +`number` + +##### connLinkContact + +`CreatedConnLink` + +##### direct + +`boolean` + +##### groupSLinkData + +`GroupShortLinkData` + +##### verifiedDomain? + +`string` + +#### Returns + +`Promise`\<`AChat`\> + +*** + ### apiReceiveFile() -> **apiReceiveFile**(`fileId`): `Promise`\<`AChatItem`\> +> **apiReceiveFile**(`fileId`, `filePath?`, `approvedRelays?`): `Promise`\<`AChatItem`\> -Defined in: [src/api.ts:542](../api.ts#L542) +Defined in: [src/api.ts:547](../api.ts#L547) Receive file. +With `approvedRelays = false`, a file on unknown XFTP relays is received only when the IP address is protected; otherwise `fileNotApproved` is thrown. Network usage: no. #### Parameters @@ -923,6 +1230,14 @@ Network usage: no. `number` +##### filePath? + +`string` + +##### approvedRelays? + +`boolean` = `true` + #### Returns `Promise`\<`AChatItem`\> @@ -933,7 +1248,7 @@ Network usage: no. > **apiRejectContactRequest**(`contactReqId`): `Promise`\<`void`\> -Defined in: [src/api.ts:783](../api.ts#L783) +Defined in: [src/api.ts:860](../api.ts#L860) Reject contact request. The user who sent the request is **not notified**. Network usage: no. @@ -954,7 +1269,7 @@ Network usage: no. > **apiRemoveMembers**(`groupId`, `memberIds`, `withMessages?`): `Promise`\<`GroupMember`[]\> -Defined in: [src/api.ts:623](../api.ts#L623) +Defined in: [src/api.ts:628](../api.ts#L628) Remove members. Requires Admin role. Network usage: background. @@ -983,11 +1298,11 @@ Network usage: background. > **apiSendMemberContactInvitation**(`contactId`, `message?`): `Promise`\<`Contact`\> -Defined in: [src/api.ts:987](../api.ts#L987) +Defined in: [src/api.ts:1124](../api.ts#L1124) Send a direct message invitation to a group member contact. The contact must have been created with [apiCreateMemberContact](#apicreatemembercontact). -Network usage: interactive. +Network usage: background. #### Parameters @@ -1009,7 +1324,7 @@ Network usage: interactive. > **apiSendMessages**(`chat`, `messages`, `liveMessage?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:444](../api.ts#L444) +Defined in: [src/api.ts:448](../api.ts#L448) Send messages. Network usage: background. @@ -1038,7 +1353,7 @@ Network usage: background. > **apiSendTextMessage**(`chat`, `text`, `inReplyTo?`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:467](../api.ts#L467) +Defined in: [src/api.ts:471](../api.ts#L471) Send text message. Network usage: background. @@ -1067,7 +1382,7 @@ Network usage: background. > **apiSendTextReply**(`chatItem`, `text`): `Promise`\<`AChatItem`[]\> -Defined in: [src/api.ts:475](../api.ts#L475) +Defined in: [src/api.ts:479](../api.ts#L479) Send text message in reply to received message. Network usage: background. @@ -1092,7 +1407,7 @@ Network usage: background. > **apiSetActiveUser**(`userId`, `viewPwd?`): `Promise`\<`User`\> -Defined in: [src/api.ts:930](../api.ts#L930) +Defined in: [src/api.ts:1067](../api.ts#L1067) Set active user profile Network usage: no. @@ -1117,7 +1432,7 @@ Network usage: no. > **apiSetAddressSettings**(`userId`, `__namedParameters`): `Promise`\<`void`\> -Defined in: [src/api.ts:416](../api.ts#L416) +Defined in: [src/api.ts:419](../api.ts#L419) Set bot address settings. Network usage: interactive. @@ -1142,7 +1457,7 @@ Network usage: interactive. > **apiSetAutoAcceptMemberContacts**(`userId`, `onOff`): `Promise`\<`void`\> -Defined in: [src/api.ts:869](../api.ts#L869) +Defined in: [src/api.ts:946](../api.ts#L946) Set auto-accept member contacts. Network usage: no. @@ -1167,7 +1482,7 @@ Network usage: no. > **apiSetContactCustomData**(`contactId`, `customData?`): `Promise`\<`void`\> -Defined in: [src/api.ts:859](../api.ts#L859) +Defined in: [src/api.ts:936](../api.ts#L936) Set contact custom data. Network usage: no. @@ -1192,7 +1507,7 @@ Network usage: no. > **apiSetContactPrefs**(`contactId`, `preferences`): `Promise`\<`void`\> -Defined in: [src/api.ts:966](../api.ts#L966) +Defined in: [src/api.ts:1103](../api.ts#L1103) Configure chat preference overrides for the contact. Network usage: background. @@ -1217,7 +1532,7 @@ Network usage: background. > **apiSetGroupCustomData**(`groupId`, `customData?`): `Promise`\<`void`\> -Defined in: [src/api.ts:849](../api.ts#L849) +Defined in: [src/api.ts:926](../api.ts#L926) Set group custom data. Network usage: no. @@ -1242,7 +1557,7 @@ Network usage: no. > **apiSetGroupLinkMemberRole**(`groupId`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:686](../api.ts#L686) +Defined in: [src/api.ts:691](../api.ts#L691) Set member role for group link. Network usage: no. @@ -1267,7 +1582,7 @@ Network usage: no. > **apiSetMembersRole**(`groupId`, `groupMemberIds`, `memberRole`): `Promise`\<`void`\> -Defined in: [src/api.ts:603](../api.ts#L603) +Defined in: [src/api.ts:608](../api.ts#L608) Set members role. Requires Admin role. Network usage: background. @@ -1292,11 +1607,32 @@ Network usage: background. *** +### apiSetNetworkConfig() + +> **apiSetNetworkConfig**(`networkConfig`): `Promise`\<`void`\> + +Defined in: [src/api.ts:1007](../api.ts#L1007) + +Set network configuration. +Network usage: background. + +#### Parameters + +##### networkConfig + +`NetworkConfig` + +#### Returns + +`Promise`\<`void`\> + +*** + ### apiSetProfileAddress() > **apiSetProfileAddress**(`userId`, `enable`): `Promise`\<`UserProfileUpdateSummary`\> -Defined in: [src/api.ts:400](../api.ts#L400) +Defined in: [src/api.ts:403](../api.ts#L403) Add address to bot profile. Network usage: interactive. @@ -1321,7 +1657,7 @@ Network usage: interactive. > **apiSetUserDomain**(`userId`, `simplexDomain?`): `Promise`\<`User`\> -Defined in: [src/api.ts:429](../api.ts#L429) +Defined in: [src/api.ts:433](../api.ts#L433) #### Parameters @@ -1343,7 +1679,7 @@ Defined in: [src/api.ts:429](../api.ts#L429) > **apiUpdateChatItem**(`chatType`, `chatId`, `chatItemId`, `msgContent`, `liveMessage?`): `Promise`\<`ChatItem`\> -Defined in: [src/api.ts:484](../api.ts#L484) +Defined in: [src/api.ts:488](../api.ts#L488) Update message. A live message stays live with `liveMessage = true` and is finalized with `liveMessage = false`. @@ -1381,7 +1717,7 @@ Network usage: background. > **apiUpdateGroupProfile**(`groupId`, `groupProfile`): `Promise`\<`GroupInfo`\> -Defined in: [src/api.ts:663](../api.ts#L663) +Defined in: [src/api.ts:668](../api.ts#L668) Update group profile. Network usage: background. @@ -1406,7 +1742,7 @@ Network usage: background. > **apiUpdateProfile**(`userId`, `profile`): `Promise`\<`UserProfileUpdateSummary` \| `undefined`\> -Defined in: [src/api.ts:950](../api.ts#L950) +Defined in: [src/api.ts:1087](../api.ts#L1087) Update user profile. Network usage: background. @@ -1431,7 +1767,7 @@ Network usage: background. > **close**(): `Promise`\<`void`\> -Defined in: [src/api.ts:162](../api.ts#L162) +Defined in: [src/api.ts:165](../api.ts#L165) Stop chat controller and close chat database. The database is not closed if stopping fails. @@ -1447,7 +1783,7 @@ Usually doesn't need to be called in chat bots. > **off**\<`K`\>(`event`, `subscriber?`): `void` -Defined in: [src/api.ts:303](../api.ts#L303) +Defined in: [src/api.ts:306](../api.ts#L306) Unsubscribe all or a specific handler from a specific event. @@ -1481,7 +1817,7 @@ An optional subscriber function for the event. > **offAny**(`receiver?`): `void` -Defined in: [src/api.ts:319](../api.ts#L319) +Defined in: [src/api.ts:322](../api.ts#L322) Unsubscribe all or a specific handler from any events. @@ -1505,7 +1841,7 @@ An optional subscriber function for the event. > **on**\<`K`\>(`subscribers`): `void` -Defined in: [src/api.ts:211](../api.ts#L211) +Defined in: [src/api.ts:214](../api.ts#L214) Subscribe multiple event handlers at once. @@ -1535,7 +1871,7 @@ If the same function is subscribed to event. > **on**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:219](../api.ts#L219) +Defined in: [src/api.ts:222](../api.ts#L222) Subscribe a handler to a specific event. @@ -1573,7 +1909,7 @@ If the same function is subscribed to event. > **onAny**(`receiver`): `void` -Defined in: [src/api.ts:242](../api.ts#L242) +Defined in: [src/api.ts:245](../api.ts#L245) Subscribe a handler to any event. @@ -1599,7 +1935,7 @@ If the same function is subscribed to event. > **once**\<`K`\>(`event`, `subscriber`): `void` -Defined in: [src/api.ts:253](../api.ts#L253) +Defined in: [src/api.ts:256](../api.ts#L256) Subscribe a handler to a specific event to be delivered one time. @@ -1637,7 +1973,7 @@ If the same function is subscribed to event. > **recvChatEvent**(`wait?`): `Promise`\<`ChatEvent` \| `undefined`\> -Defined in: [src/api.ts:354](../api.ts#L354) +Defined in: [src/api.ts:357](../api.ts#L357) #### Parameters @@ -1655,7 +1991,7 @@ Defined in: [src/api.ts:354](../api.ts#L354) > **sendChatCmd**(`cmd`): `Promise`\<`ChatResponse`\> -Defined in: [src/api.ts:350](../api.ts#L350) +Defined in: [src/api.ts:353](../api.ts#L353) #### Parameters @@ -1673,7 +2009,7 @@ Defined in: [src/api.ts:350](../api.ts#L350) > **startChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:128](../api.ts#L128) +Defined in: [src/api.ts:131](../api.ts#L131) Start chat controller. Must be called with the existing user profile. @@ -1687,7 +2023,7 @@ Start chat controller. Must be called with the existing user profile. > **stopChat**(): `Promise`\<`void`\> -Defined in: [src/api.ts:151](../api.ts#L151) +Defined in: [src/api.ts:154](../api.ts#L154) Stop chat controller. `close` calls it before closing the database. @@ -1705,7 +2041,7 @@ Usually doesn't need to be called in chat bots. > **wait**\<`K`\>(`event`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:261](../api.ts#L261) +Defined in: [src/api.ts:264](../api.ts#L264) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1730,7 +2066,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `predicate`): `Promise`\<`ChatEvent` & `object`\> -Defined in: [src/api.ts:262](../api.ts#L262) +Defined in: [src/api.ts:265](../api.ts#L265) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1759,7 +2095,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `timeout`): `Promise`\ -Defined in: [src/api.ts:263](../api.ts#L263) +Defined in: [src/api.ts:266](../api.ts#L266) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1788,7 +2124,7 @@ Returns `undefined` on timeout if specified. > **wait**\<`K`\>(`event`, `predicate`, `timeout`): `Promise`\ -Defined in: [src/api.ts:264](../api.ts#L264) +Defined in: [src/api.ts:267](../api.ts#L267) Waits for specific event, with an optional predicate. Returns `undefined` on timeout if specified. @@ -1823,7 +2159,7 @@ Returns `undefined` on timeout if specified. > `static` **init**(`db`, `confirm?`, `queueSize?`): `Promise`\<`ChatApi`\> -Defined in: [src/api.ts:114](../api.ts#L114) +Defined in: [src/api.ts:117](../api.ts#L117) Initializes the ChatApi, loading libsimplex for `db.type` (downloaded on first use). diff --git a/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md b/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md index 9f7f57dd5c..792ff1eef9 100644 --- a/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md +++ b/packages/simplex-chat-nodejs/docs/api.Class.LiveMessage.md @@ -6,7 +6,7 @@ # Class: LiveMessage -Defined in: [src/api.ts:1005](../api.ts#L1005) +Defined in: [src/api.ts:1134](../api.ts#L1134) Text shown as a live message while it is written, e.g. a streamed reply. @@ -16,7 +16,7 @@ Text shown as a live message while it is written, e.g. a streamed reply. > **new LiveMessage**(`chat`, `chatType`, `chatId`, `interval?`, `onError?`): `LiveMessage` -Defined in: [src/api.ts:1019](../api.ts#L1019) +Defined in: [src/api.ts:1148](../api.ts#L1148) #### Parameters @@ -60,7 +60,7 @@ Error handler for sending, updating and deleting messages. Errors are logged whe > **end**(`text?`): `Promise`\<`void`\> -Defined in: [src/api.ts:1049](../api.ts#L1049) +Defined in: [src/api.ts:1178](../api.ts#L1178) Finalize the live message with the text, or with the last text when omitted. A message without text is deleted. The next update starts a new live message. @@ -82,7 +82,7 @@ The returned promise resolves after all queued commands complete. > **update**(`text`): `void` -Defined in: [src/api.ts:1031](../api.ts#L1031) +Defined in: [src/api.ts:1160](../api.ts#L1160) Set the text of the live message. The first text is sent at once; later texts update the message at most once per interval. diff --git a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md index f0c9521c6b..00912ad68c 100644 --- a/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/api.Interface.BotAddressSettings.md @@ -45,6 +45,14 @@ false *** +### chatLayout? + +> `optional` **chatLayout?**: `ChatLayout` + +Defined in: [src/api.ts:43](../api.ts#L43) + +*** + ### welcomeMessage? > `optional` **welcomeMessage?**: `string` \| `MsgContent` diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md index 114392d8ba..265cf3780d 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.DbConfig.md @@ -8,7 +8,7 @@ > **DbConfig** = \{ `encryptionKey?`: `string`; `filePrefix`: `string`; `type`: `"sqlite"`; \} \| \{ `connectionString`: `string`; `schemaPrefix?`: `string`; `type`: `"postgres"`; \} -Defined in: [src/api.ts:63](../api.ts#L63) +Defined in: [src/api.ts:66](../api.ts#L66) Database configuration. `type` selects the libsimplex backend loaded by `ChatApi.init`; one backend per process. diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md index 103ee471e0..40160e1444 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscriberFunc.md @@ -8,7 +8,7 @@ > **EventSubscriberFunc**\<`K`\> = (`event`, `chat`) => `void` \| `Promise`\<`void`\> -Defined in: [src/api.ts:50](../api.ts#L50) +Defined in: [src/api.ts:53](../api.ts#L53) ## Type Parameters diff --git a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md index 6f694c79aa..211defa96d 100644 --- a/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md +++ b/packages/simplex-chat-nodejs/docs/api.TypeAlias.EventSubscribers.md @@ -8,4 +8,4 @@ > **EventSubscribers** = `{ [K in CEvt.Tag]?: EventSubscriberFunc }` -Defined in: [src/api.ts:52](../api.ts#L52) +Defined in: [src/api.ts:55](../api.ts#L55) diff --git a/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md b/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md index 29352adcfc..2cba288415 100644 --- a/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md +++ b/packages/simplex-chat-nodejs/docs/api.Variable.defaultBotAddressSettings.md @@ -8,4 +8,4 @@ > `const` **defaultBotAddressSettings**: [`BotAddressSettings`](api.Interface.BotAddressSettings.md) -Defined in: [src/api.ts:44](../api.ts#L44) +Defined in: [src/api.ts:46](../api.ts#L46) diff --git a/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md b/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md index c66aa388ac..1c476f0827 100644 --- a/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md +++ b/packages/simplex-chat-nodejs/docs/bot.Function.subscribeChatItems.md @@ -8,7 +8,7 @@ > **subscribeChatItems**(`bot`, `onMessage`, `commands`): `void` -Defined in: [src/bot.ts:110](../bot.ts#L110) +Defined in: [src/bot.ts:111](../bot.ts#L111) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md b/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md index 4e880ea329..54a658b780 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.ciBotCommand.md @@ -8,7 +8,7 @@ > **ciBotCommand**(`chatItem`): [`BotCommand`](util.Interface.BotCommand.md) \| `undefined` -Defined in: [src/util.ts:78](../util.ts#L78) +Defined in: [src/util.ts:79](../util.ts#L79) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md b/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md index 624f88f45a..506629cbe9 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.ciContentText.md @@ -8,7 +8,7 @@ > **ciContentText**(`__namedParameters`): `string` \| `undefined` -Defined in: [src/util.ts:64](../util.ts#L64) +Defined in: [src/util.ts:65](../util.ts#L65) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md b/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md index ed9640b927..154421d5d2 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.fromLocalProfile.md @@ -8,7 +8,7 @@ > **fromLocalProfile**(`__namedParameters`): `Profile` -Defined in: [src/util.ts:56](../util.ts#L56) +Defined in: [src/util.ts:57](../util.ts#L57) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md b/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md index 48090064c3..784e6c99c4 100644 --- a/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md +++ b/packages/simplex-chat-nodejs/docs/util.Function.reactionText.md @@ -8,7 +8,7 @@ > **reactionText**(`reaction`): `string` -Defined in: [src/util.ts:89](../util.ts#L89) +Defined in: [src/util.ts:90](../util.ts#L90) ## Parameters diff --git a/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md b/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md index f50e88a1d1..f64ce87017 100644 --- a/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md +++ b/packages/simplex-chat-nodejs/docs/util.Interface.BotCommand.md @@ -6,7 +6,7 @@ # Interface: BotCommand -Defined in: [src/util.ts:72](../util.ts#L72) +Defined in: [src/util.ts:73](../util.ts#L73) ## Properties @@ -14,7 +14,7 @@ Defined in: [src/util.ts:72](../util.ts#L72) > **keyword**: `string` -Defined in: [src/util.ts:73](../util.ts#L73) +Defined in: [src/util.ts:74](../util.ts#L74) *** @@ -22,4 +22,4 @@ Defined in: [src/util.ts:73](../util.ts#L73) > **params**: `string` -Defined in: [src/util.ts:74](../util.ts#L74) +Defined in: [src/util.ts:75](../util.ts#L75) diff --git a/packages/simplex-chat-nodejs/src/api.ts b/packages/simplex-chat-nodejs/src/api.ts index f01219ef9b..80c9602d2b 100644 --- a/packages/simplex-chat-nodejs/src/api.ts +++ b/packages/simplex-chat-nodejs/src/api.ts @@ -1,4 +1,4 @@ -import {CC, CEvt, ChatEvent, ChatResponse, T} from "@simplex-chat/types" +import {CC, CEvt, CR, ChatEvent, ChatResponse, T} from "@simplex-chat/types" import * as core from "./core" import * as util from "./util" @@ -152,7 +152,7 @@ export class ChatApi { * Usually doesn't need to be called in chat bots. */ async stopChat(): Promise { - const r = await this.sendChatCmd("/_stop") + const r = await this.sendChatCmd(CC.APIStopChat.cmdString({})) if (r.type !== "chatStopped") throw new ChatCommandError("error stopping chat", r) await this.stopEventsLoop() } @@ -541,10 +541,11 @@ export class ChatApi { /** * Receive file. + * With `approvedRelays = false`, a file on unknown XFTP relays is received only when the IP address is protected; otherwise `fileNotApproved` is thrown. * Network usage: no. */ - async apiReceiveFile(fileId: number): Promise { - const r = await this.sendChatCmd(CC.ReceiveFile.cmdString({fileId, userApprovedRelays: true})) + async apiReceiveFile(fileId: number, filePath?: string, approvedRelays = true): Promise { + const r = await this.sendChatCmd(CC.ReceiveFile.cmdString({fileId, userApprovedRelays: approvedRelays, filePath})) if (r.type === "rcvFileAccepted") return r.chatItem if (r.type === "rcvFileAcceptedSndCancelled") throw new ChatCommandError("file cancelled by sender", r) throw new ChatCommandError("error receiving file", r) @@ -733,9 +734,9 @@ export class ChatApi { * Determine SimpleX link type and if the bot is already connected via this link. * Network usage: interactive. */ - async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> { + async apiConnectPlan(userId: number, connectionLink: string): Promise<[plan: T.ConnectionPlan, link: T.CreatedConnLink, planSimplexName?: T.SimplexNameInfo, otherSimplexName?: T.SimplexNameInfo]> { const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectTarget: connectionLink, resolveMode: T.PlanResolveMode.Unknown})) - if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink] + if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink, r.planSimplexName, r.otherSimplexName] throw new ChatCommandError("error getting connect plan", r) } @@ -748,6 +749,78 @@ export class ChatApi { return this.handleConnectResult(r) } + /** + * Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link. + * Returns the response with the pending connection, or with the contact when it already exists. + * Network usage: interactive. + */ + async apiConnectPreparedLink(userId: number, incognito: boolean, preparedLink: T.CreatedConnLink): Promise { + const r = await this.sendChatCmd(CC.APIConnect.cmdString({userId, incognito, preparedLink_: preparedLink})) + switch (r.type) { + case "sentConfirmation": + case "sentInvitation": + case "contactAlreadyExists": + return r + default: + throw new ChatCommandError("connection error", r) + } + } + + /** + * Prepare contact chat from link data before connecting. + * Network usage: no. + */ + async apiPrepareContact(userId: number, connLink: T.CreatedConnLink, contactSLinkData: T.ContactShortLinkData, verifiedDomain?: string): Promise { + const r = await this.sendChatCmd(CC.APIPrepareContact.cmdString({userId, connLink, verifiedDomain, contactSLinkData})) + if (r.type === "newPreparedChat") return r.chat + throw new ChatCommandError("error preparing contact", r) + } + + /** + * Prepare group chat from link data before connecting. + * Network usage: no. + */ + async apiPrepareGroup(userId: number, connLinkContact: T.CreatedConnLink, direct: boolean, groupSLinkData: T.GroupShortLinkData, verifiedDomain?: string): Promise { + const r = await this.sendChatCmd(CC.APIPrepareGroup.cmdString({userId, connLinkContact, direct, verifiedDomain, groupSLinkData})) + if (r.type === "newPreparedChat") return r.chat + throw new ChatCommandError("error preparing group", r) + } + + /** + * Connect to prepared contact. + * Network usage: interactive. + */ + async apiConnectPreparedContact(contactId: number, incognito: boolean, msgContent?: T.MsgContent): Promise { + const r = await this.sendChatCmd(CC.APIConnectPreparedContact.cmdString({contactId, incognito, msgContent_: msgContent})) + switch (r.type) { + case "startedConnectionToContact": + case "contactAlreadyExists": + return r + default: + throw new ChatCommandError("error connecting to prepared contact", r) + } + } + + /** + * Connect to prepared group. + * Network usage: interactive. + */ + async apiConnectPreparedGroup(groupId: number, incognito: boolean, msgContent?: T.MsgContent, ownerContact?: T.GroupOwnerContact): Promise { + const r = await this.sendChatCmd(CC.APIConnectPreparedGroup.cmdString({groupId, incognito, ownerContact, msgContent_: msgContent})) + if (r.type === "startedConnectionToGroup") return r + throw new ChatCommandError("error connecting to prepared group", r) + } + + /** + * Connect to existing contact via address in its profile. + * Network usage: interactive. + */ + async apiConnectContactViaAddress(userId: number, incognito: boolean, contactId: number): Promise { + const r = await this.sendChatCmd(CC.APIConnectContactViaAddress.cmdString({userId, incognito, contactId})) + if (r.type === "sentInvitationToContact") return r + throw new ChatCommandError("error connecting via contact address", r) + } + /** * Connect via SimpleX link as string in the active user profile. * Network usage: interactive. @@ -880,13 +953,73 @@ export class ChatApi { * 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}`) + async apiGetChat(chatType: T.ChatType, chatId: number, pagination: T.ChatPagination | number): Promise { + const chatPagination: T.ChatPagination = typeof pagination === "number" ? {type: "last", count: pagination} : pagination + const r = await this.sendChatCmd(CC.APIGetChat.cmdString({chatRef: {chatType, chatId}, chatPagination})) if (r.type === "apiChat") return r.chat throw new ChatCommandError("error getting chat", r) } + /** + * Mark messages as read. + * Network usage: no. + */ + async apiChatItemsRead(chatType: T.ChatType, chatId: number, chatItemIds: number[]): Promise { + const r = await this.sendChatCmd(CC.APIChatItemsRead.cmdString({chatRef: {chatType, chatId}, chatItemIds})) + if (r.type === "itemsReadForChat") return r.chatInfo + throw new ChatCommandError("error marking chat items read", r) + } + + /** + * Get message information. + * Network usage: no. + */ + async apiGetChatItemInfo(chatType: T.ChatType, chatId: number, chatItemId: number): Promise { + const r = await this.sendChatCmd(CC.APIGetChatItemInfo.cmdString({chatRef: {chatType, chatId}, chatItemId})) + if (r.type === "chatItemInfo") return r + throw new ChatCommandError("error getting chat item info", r) + } + + /** + * Get contact information. + * Network usage: no. + */ + async apiContactInfo(contactId: number): Promise { + const r = await this.sendChatCmd(CC.APIContactInfo.cmdString({contactId})) + if (r.type === "contactInfo") return r + throw new ChatCommandError("error getting contact info", r) + } + + /** + * Get network configuration. + * Network usage: no. + */ + async apiGetNetworkConfig(): Promise { + const r = await this.sendChatCmd(CC.APIGetNetworkConfig.cmdString({})) + if (r.type === "networkConfig") return r.networkConfig + throw new ChatCommandError("error getting network config", r) + } + + /** + * Set network configuration. + * Network usage: background. + */ + async apiSetNetworkConfig(networkConfig: T.NetworkConfig): Promise { + const r = await this.sendChatCmd(CC.APISetNetworkConfig.cmdString({networkConfig})) + if (r.type === "cmdOk") return + throw new ChatCommandError("error setting network config", r) + } + + /** + * Get user servers and chat relays, grouped by operator. + * Network usage: no. + */ + async apiGetUserServers(userId: number): Promise { + const r = await this.sendChatCmd(CC.APIGetUserServers.cmdString({userId})) + if (r.type === "userServers") return r.userServers + throw new ChatCommandError("error getting user servers", r) + } + /** * Get active user profile * Network usage: no. @@ -978,7 +1111,7 @@ export class ChatApi { * Network usage: interactive. */ async apiCreateMemberContact(groupId: number, groupMemberId: number): Promise { - const r: any = await this.sendChatCmd(`/_create member contact #${groupId} ${groupMemberId}`) + const r = await this.sendChatCmd(CC.APICreateMemberContact.cmdString({groupId, groupMemberId})) if (r.type === "newMemberContact") return r.contact throw new ChatCommandError("error creating member contact", r) } @@ -986,18 +1119,10 @@ export class ChatApi { /** * Send a direct message invitation to a group member contact. * The contact must have been created with {@link apiCreateMemberContact}. - * Network usage: interactive. + * Network usage: background. */ async apiSendMemberContactInvitation(contactId: number, message?: T.MsgContent | string): Promise { - let cmd = `/_invite member contact @${contactId}` - if (message !== undefined) { - if (typeof message === "string") { - cmd += ` text ${message}` - } else { - cmd += ` json ${JSON.stringify(message)}` - } - } - const r: any = await this.sendChatCmd(cmd) + const r = await this.sendChatCmd(CC.APISendMemberContactInvitation.cmdString({contactId, msgContent_: typeof message === "string" ? {type: "text", text: message} : message})) if (r.type === "newMemberContactSentInv") return r.contact throw new ChatCommandError("error sending member contact invitation", r) } diff --git a/packages/simplex-chat-nodejs/tests/api.test.ts b/packages/simplex-chat-nodejs/tests/api.test.ts index 5ff298d33f..92164fc2e5 100644 --- a/packages/simplex-chat-nodejs/tests/api.test.ts +++ b/packages/simplex-chat-nodejs/tests/api.test.ts @@ -1,7 +1,8 @@ import * as path from "path" import * as fs from "fs" +import * as assert from "assert" import {CEvt, T} from "@simplex-chat/types" -import {api} from ".." +import {api, util} from ".." import {SmpServer, describeIfSmpServerAvailable, startSmpServer} from "./smpServer" const CT = T.ChatType @@ -159,4 +160,69 @@ describeIfSmpServerAvailable("API tests (use local SMP server)", () => { await bob.close() await carol.close() }, 90000) + + it("should connect via prepared contact and read messages", async () => { + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const bob = await api.ChatApi.init({type: "sqlite", filePrefix: bobPath}) + const aliceUser = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) + const bobUser = await bob.apiCreateActiveUser({displayName: "bob", fullName: ""}) + await alice.startChat() + await bob.startChat() + await useSmpServer(alice) + await useSmpServer(bob) + const address = await alice.apiCreateUserAddress(aliceUser.userId) + const [plan, addressLink, planSimplexName] = await bob.apiConnectPlan(bobUser.userId, util.contactAddressStr(address)) + assert(plan.type === "contactAddress" && plan.contactAddressPlan.type === "ok" && plan.contactAddressPlan.contactSLinkData_) + expect(planSimplexName).toBeUndefined() + const prepared = await bob.apiPrepareContact(bobUser.userId, addressLink, plan.contactAddressPlan.contactSLinkData_) + assert(prepared.chatInfo.type === CT.Direct) + const request = alice.wait("receivedContactRequest") + await expect(bob.apiConnectPreparedContact(prepared.chatInfo.contact.contactId, false)).resolves.toMatchObject({type: "startedConnectionToContact"}) + const connected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")]) + await alice.apiAcceptContactRequest((await request).contactRequest.contactRequestId) + const [{contact: bobContact}, {contact: aliceContact}] = await connected + const hello = alice.wait("newChatItems", ({chatItems}) => chatItems.some(ci => ci.chatItem.meta.itemText === "hello")) + await bob.apiSendTextMessage([CT.Direct, aliceContact.contactId], "hello") + await hello + const initial = await alice.apiGetChat(CT.Direct, bobContact.contactId, {type: "initial", count: 10}) + const item = initial.chatItems.find(ci => ci.meta.itemText === "hello") + assert(item?.meta.itemStatus.type === "rcvNew") + const {itemId} = item.meta + await expect(alice.apiChatItemsRead(CT.Direct, bobContact.contactId, [itemId])).resolves.toMatchObject({type: CT.Direct}) + const around = await alice.apiGetChat(CT.Direct, bobContact.contactId, {type: "around", chatItemId: itemId, count: 0}) + expect(around.chatItems.find(ci => ci.meta.itemId === itemId)?.meta.itemStatus.type).toBe("rcvRead") + const {chatItem, chatItemInfo} = await alice.apiGetChatItemInfo(CT.Direct, bobContact.contactId, itemId) + expect(chatItem.chatItem.meta.itemId).toBe(itemId) + expect(chatItemInfo.fileXftpServers).toEqual([]) + const info = await bob.apiContactInfo(aliceContact.contactId) + expect(info.contact.contactId).toBe(aliceContact.contactId) + expect(info.customUserProfile).toBeUndefined() + const [, invitationLink] = await bob.apiConnectPlan(bobUser.userId, await alice.apiCreateLink(aliceUser.userId)) + const reconnected = Promise.all([alice.wait("contactConnected"), bob.wait("contactConnected")]) + await expect(bob.apiConnectPreparedLink(bobUser.userId, false, invitationLink)).resolves.toMatchObject({type: "sentConfirmation"}) + await reconnected + await alice.close() + await bob.close() + }, 60000) + + it("should get and set network config", async () => { + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) + const networkConfig = await alice.apiGetNetworkConfig() + const updated = {...networkConfig, smpProxyFallback: T.SMPProxyFallback.AllowProtected, logTLSErrors: true} + await alice.apiSetNetworkConfig(updated) + await expect(alice.apiGetNetworkConfig()).resolves.toEqual(updated) + await alice.close() + }) + + it("should get user servers", async () => { + const alice = await api.ChatApi.init({type: "sqlite", filePrefix: alicePath}) + const {userId} = await alice.apiCreateActiveUser({displayName: "alice", fullName: ""}) + await alice.startChat() + await useSmpServer(alice) + const servers = await alice.apiGetUserServers(userId) + const custom = servers.find(({operator}) => operator === undefined) + expect(custom?.smpServers.map(({server, enabled}) => ({server, enabled}))).toEqual([{server: smpServer.address, enabled: true}]) + await alice.close() + }) }) diff --git a/packages/simplex-chat-nodejs/tests/api.unit.test.ts b/packages/simplex-chat-nodejs/tests/api.unit.test.ts index 14be90f5bf..84f536a0aa 100644 --- a/packages/simplex-chat-nodejs/tests/api.unit.test.ts +++ b/packages/simplex-chat-nodejs/tests/api.unit.test.ts @@ -11,6 +11,14 @@ async function chatWithResponse(response: object): Promise { return api.ChatApi.init({type: "sqlite", filePrefix: "unused"}) } +function sentCommands(): string[] { + return jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd) +} + +function nextResponse(response: object): void { + jest.mocked(core.chatSendCmd).mockResolvedValue(response as ChatResponse) +} + afterEach(() => jest.restoreAllMocks()) describe("documented success responses", () => { @@ -31,7 +39,7 @@ describe("documented success responses", () => { const chat = await chatWithResponse({type: "chatItemUpdated", user, chatItem: {chatItem}}) await expect(chat.apiUpdateChatItem(T.ChatType.Group, 1, 2, {type: "text", text: "partial"}, true)).resolves.toEqual(chatItem) await expect(chat.apiUpdateChatItem(T.ChatType.Group, 1, 2, {type: "text", text: "final"})).resolves.toEqual(chatItem) - expect(jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd)).toEqual([ + expect(sentCommands()).toEqual([ '/_update item #1 2 live=on json {"msgContent":{"type":"text","text":"partial"},"mentions":{}}', '/_update item #1 2 json {"msgContent":{"type":"text","text":"final"},"mentions":{}}', ]) @@ -46,13 +54,131 @@ describe("documented success responses", () => { const chat = await chatWithResponse({type: "userProfileNoChange", user}) await expect(chat.apiSetUserDomain(1, "calc.simplex")).resolves.toEqual(user) await expect(chat.apiSetUserDomain(1)).resolves.toEqual(user) - expect(jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd)).toEqual(["/_set domain 1 calc.simplex", "/_set domain 1"]) + expect(sentCommands()).toEqual(["/_set domain 1 calc.simplex", "/_set domain 1"]) }) it("apiReceiveFile reports a file cancelled by sender", async () => { const chat = await chatWithResponse({type: "rcvFileAcceptedSndCancelled", user, rcvFileTransfer: {}}) await expect(chat.apiReceiveFile(3)).rejects.toThrow("file cancelled by sender") }) + + it("apiReceiveFile receives to the file path", async () => { + const chatItem = {chatItem: {meta: {itemId: 2}}} + const chat = await chatWithResponse({type: "rcvFileAccepted", user, chatItem}) + await expect(chat.apiReceiveFile(3, "./f.txt")).resolves.toEqual(chatItem) + expect(sentCommands()).toEqual(["/freceive 3 approved_relays=on ./f.txt"]) + }) + + it("apiReceiveFile approves relays by default", async () => { + const chatItem = {chatItem: {meta: {itemId: 2}}} + const chat = await chatWithResponse({type: "rcvFileAccepted", user, chatItem}) + await expect(chat.apiReceiveFile(3)).resolves.toEqual(chatItem) + await expect(chat.apiReceiveFile(3, undefined, false)).resolves.toEqual(chatItem) + await expect(chat.apiReceiveFile(3, "./f.txt", false)).resolves.toEqual(chatItem) + expect(sentCommands()).toEqual(["/freceive 3 approved_relays=on", "/freceive 3", "/freceive 3 ./f.txt"]) + }) + + it("apiGetChat accepts a count or a pagination", async () => { + const aChat = {chatInfo: {type: "direct"}, chatItems: [], chatStats: {}} + const chat = await chatWithResponse({type: "apiChat", user, chat: aChat}) + await expect(chat.apiGetChat(T.ChatType.Group, 1, 0)).resolves.toEqual(aChat) + await expect(chat.apiGetChat(T.ChatType.Direct, 2, {type: "around", chatItemId: 5, count: 0})).resolves.toEqual(aChat) + expect(sentCommands()).toEqual(["/_get chat #1 count=0", "/_get chat @2 around=5 count=0"]) + }) + + it("apiChatItemsRead returns the chat info", async () => { + const chatInfo = {type: "direct", contact: {contactId: 2}} + const chat = await chatWithResponse({type: "itemsReadForChat", user, chatInfo}) + await expect(chat.apiChatItemsRead(T.ChatType.Direct, 2, [3, 4])).resolves.toEqual(chatInfo) + }) + + it("apiContactInfo returns the response", async () => { + const info = {type: "contactInfo", user, contact: {contactId: 2}, customUserProfile: {displayName: "random", fullName: ""}} + const chat = await chatWithResponse(info) + await expect(chat.apiContactInfo(2)).resolves.toEqual(info) + }) + + it("apiPrepareContact and apiPrepareGroup return the prepared chat", async () => { + const aChat = {chatInfo: {type: "direct"}, chatItems: [], chatStats: {}} + const chat = await chatWithResponse({type: "newPreparedChat", user, chat: aChat}) + const connLink = {connFullLink: "F", connShortLink: "S"} + await expect(chat.apiPrepareContact(1, connLink, {profile: {displayName: "bob", fullName: ""}, business: false}, "biz.simplex")).resolves.toEqual(aChat) + await expect(chat.apiPrepareGroup(1, connLink, false, {groupProfile: {displayName: "team", fullName: ""}})).resolves.toEqual(aChat) + }) + + it("apiConnectPreparedContact returns the started connection or the existing contact", async () => { + const started = {type: "startedConnectionToContact", user, contact: {contactId: 2}} + const chat = await chatWithResponse(started) + await expect(chat.apiConnectPreparedContact(2, false)).resolves.toEqual(started) + const existing = {type: "contactAlreadyExists", user, contact: {contactId: 2}} + nextResponse(existing) + await expect(chat.apiConnectPreparedContact(2, true, {type: "text", text: "hi"})).resolves.toEqual(existing) + }) + + it("apiConnectPreparedGroup and apiConnectContactViaAddress return the response", async () => { + const started = {type: "startedConnectionToGroup", user, groupInfo: {groupId: 3}, relayResults: []} + const chat = await chatWithResponse(started) + await expect(chat.apiConnectPreparedGroup(3, false)).resolves.toEqual(started) + const sent = {type: "sentInvitationToContact", user, contact: {contactId: 2}} + nextResponse(sent) + await expect(chat.apiConnectContactViaAddress(1, false, 2)).resolves.toEqual(sent) + }) + + it("apiConnectPreparedLink returns the response, apiConnect the link type", async () => { + const connLink = {connFullLink: "L"} + const confirmation = {type: "sentConfirmation", user, connection: {pccConnId: 1}} + const chat = await chatWithResponse(confirmation) + await expect(chat.apiConnectPreparedLink(1, false, connLink)).resolves.toEqual(confirmation) + await expect(chat.apiConnect(1, false, connLink)).resolves.toBe(api.ConnReqType.Invitation) + const invitation = {type: "sentInvitation", user, connection: {pccConnId: 2}} + nextResponse(invitation) + await expect(chat.apiConnectPreparedLink(1, false, connLink)).resolves.toEqual(invitation) + await expect(chat.apiConnect(1, false, connLink)).resolves.toBe(api.ConnReqType.Contact) + const existing = {type: "contactAlreadyExists", user, contact: {contactId: 2}} + nextResponse(existing) + await expect(chat.apiConnectPreparedLink(1, false, connLink)).resolves.toEqual(existing) + await expect(chat.apiConnect(1, false, connLink)).rejects.toThrow("contact already exists") + }) + + it("apiConnectPlan returns the SimpleX names", async () => { + const connectionPlan = {type: "contactAddress", contactAddressPlan: {type: "ok"}} + const connLink = {connFullLink: "L"} + const planSimplexName = {nameType: "contact", nameDomain: {nameTLD: "simplex", domain: "biz", subDomain: []}} + const otherSimplexName = {nameType: "publicGroup", nameDomain: {nameTLD: "simplex", domain: "news", subDomain: []}} + const chat = await chatWithResponse({type: "connectionPlan", user, connLink, planSimplexName, otherSimplexName, connectionPlan}) + await expect(chat.apiConnectPlan(1, "@biz.simplex")).resolves.toEqual([connectionPlan, connLink, planSimplexName, otherSimplexName]) + }) + + it("apiGetNetworkConfig returns the config, apiSetNetworkConfig accepts cmdOk", async () => { + const networkConfig = {socksMode: T.SocksMode.Always, logTLSErrors: false} as T.NetworkConfig + const chat = await chatWithResponse({type: "networkConfig", networkConfig}) + await expect(chat.apiGetNetworkConfig()).resolves.toEqual(networkConfig) + nextResponse({type: "cmdOk"}) + await expect(chat.apiSetNetworkConfig(networkConfig)).resolves.toBeUndefined() + }) + + it("apiCreateMemberContact returns the contact, apiSendMemberContactInvitation sends a text message as JSON", async () => { + const contact = {contactId: 2} + const chat = await chatWithResponse({type: "newMemberContact", user, contact, groupInfo: {}, member: {}}) + await expect(chat.apiCreateMemberContact(1, 2)).resolves.toEqual(contact) + nextResponse({type: "newMemberContactSentInv", user, contact, groupInfo: {}, member: {}}) + await expect(chat.apiSendMemberContactInvitation(2, "hi")).resolves.toEqual(contact) + expect(sentCommands()).toEqual(["/_create member contact #1 2", '/_invite member contact @2 json {"type":"text","text":"hi"}']) + }) + + it("apiGetChatItemInfo returns the response", async () => { + const info = {type: "chatItemInfo", user, chatItem: {chatItem: {meta: {itemId: 5}}}, chatItemInfo: {itemVersions: [], fileXftpServers: ["xftp://abc@localhost:7002"]}} + const chat = await chatWithResponse(info) + await expect(chat.apiGetChatItemInfo(T.ChatType.Direct, 2, 5)).resolves.toEqual(info) + expect(sentCommands()).toEqual(["/_get item info @2 5"]) + }) + + it("apiGetUserServers returns the servers", async () => { + const userServers = [{xftpServers: [{serverId: 1, server: "xftp://abc@localhost:7002", preset: false, enabled: true, roles: {}, deleted: false}], smpServers: [], chatRelays: []}] + const chat = await chatWithResponse({type: "userServers", user, userServers}) + await expect(chat.apiGetUserServers(1)).resolves.toEqual(userServers) + expect(sentCommands()).toEqual(["/_servers 1"]) + }) }) describe("startChat lifecycle", () => { diff --git a/packages/simplex-chat-nodejs/tests/commands.test.ts b/packages/simplex-chat-nodejs/tests/commands.test.ts index 61ce43bd63..00286a6a47 100644 --- a/packages/simplex-chat-nodejs/tests/commands.test.ts +++ b/packages/simplex-chat-nodejs/tests/commands.test.ts @@ -1,4 +1,4 @@ -import {CC} from "@simplex-chat/types" +import {CC, T} from "@simplex-chat/types" describe("APIConnect.cmdString", () => { const preparedLink_ = {connFullLink: "L"} @@ -11,3 +11,117 @@ describe("APIConnect.cmdString", () => { expect(CC.APIConnect.cmdString({userId: 1, incognito: false, preparedLink_})).toBe("/_connect 1 L") }) }) + +describe("APIConnectPlan.cmdString", () => { + const plan = {userId: 1, connectTarget: "L"} + + it("omits the default resolve mode", () => { + expect(CC.APIConnectPlan.cmdString({...plan, resolveMode: T.PlanResolveMode.Unknown})).toBe("/_connect plan 1 L") + }) + + it("renders other resolve modes", () => { + expect(CC.APIConnectPlan.cmdString({...plan, resolveMode: T.PlanResolveMode.AllGroups})).toBe("/_connect plan 1 L resolve=allGroups") + expect(CC.APIConnectPlan.cmdString({...plan, resolveMode: T.PlanResolveMode.Never})).toBe("/_connect plan 1 L resolve=never") + }) + + it("renders link owner signature", () => { + const linkOwnerSig = {chatBinding: "B", ownerSig: "S"} + expect(CC.APIConnectPlan.cmdString({...plan, resolveMode: T.PlanResolveMode.Unknown, linkOwnerSig})).toBe(`/_connect plan 1 L sig=${JSON.stringify(linkOwnerSig)}`) + }) +}) + +describe("APIGetChat.cmdString", () => { + const chatRef = {chatType: T.ChatType.Direct, chatId: 2} + + it("renders each pagination", () => { + expect(CC.APIGetChat.cmdString({chatRef: {chatType: T.ChatType.Group, chatId: 1}, chatPagination: {type: "last", count: 0}})).toBe("/_get chat #1 count=0") + expect(CC.APIGetChat.cmdString({chatRef, chatPagination: {type: "initial", count: 1}})).toBe("/_get chat @2 initial=1") + expect(CC.APIGetChat.cmdString({chatRef, chatPagination: {type: "around", chatItemId: 5, count: 0}})).toBe("/_get chat @2 around=5 count=0") + expect(CC.APIGetChat.cmdString({chatRef, chatPagination: {type: "after", chatItemId: 5, count: 100}})).toBe("/_get chat @2 after=5 count=100") + expect(CC.APIGetChat.cmdString({chatRef, chatPagination: {type: "before", chatItemId: 5, count: 10}})).toBe("/_get chat @2 before=5 count=10") + }) + + it("renders content tag and search", () => { + expect(CC.APIGetChat.cmdString({chatRef, contentTag: T.MsgContentTag.Image, chatPagination: {type: "last", count: 10}, search: "k"})).toBe("/_get chat @2 content=image count=10 search=k") + }) +}) + +describe("APIChatItemsRead.cmdString", () => { + it("renders item IDs", () => { + expect(CC.APIChatItemsRead.cmdString({chatRef: {chatType: T.ChatType.Direct, chatId: 2}, chatItemIds: [3, 4]})).toBe("/_read chat items @2 3,4") + }) +}) + +describe("APIPrepareContact.cmdString", () => { + const connLink = {connFullLink: "F", connShortLink: "S"} + const contactSLinkData = {profile: {displayName: "bob", fullName: ""}, business: false} + + it("renders the verified domain", () => { + expect(CC.APIPrepareContact.cmdString({userId: 1, connLink, verifiedDomain: "biz.simplex", contactSLinkData})).toBe(`/_prepare contact 1 F S domain=biz.simplex ${JSON.stringify(contactSLinkData)}`) + }) + + it("omits the domain", () => { + expect(CC.APIPrepareContact.cmdString({userId: 1, connLink, contactSLinkData})).toBe(`/_prepare contact 1 F S ${JSON.stringify(contactSLinkData)}`) + }) +}) + +describe("APIPrepareGroup.cmdString", () => { + const connLinkContact = {connFullLink: "F", connShortLink: "S"} + const groupSLinkData = {groupProfile: {displayName: "team", fullName: ""}} + + it("renders direct=off for a channel", () => { + expect(CC.APIPrepareGroup.cmdString({userId: 1, connLinkContact, direct: false, verifiedDomain: "news.simplex", groupSLinkData})).toBe(`/_prepare group 1 F S direct=off domain=news.simplex ${JSON.stringify(groupSLinkData)}`) + }) + + it("omits direct for a group", () => { + expect(CC.APIPrepareGroup.cmdString({userId: 1, connLinkContact, direct: true, groupSLinkData})).toBe(`/_prepare group 1 F S ${JSON.stringify(groupSLinkData)}`) + }) +}) + +describe("prepared chat connection cmdString", () => { + const msgContent_: T.MsgContent = {type: "text", text: "hi"} + + it("renders APIConnectPreparedContact with a message", () => { + expect(CC.APIConnectPreparedContact.cmdString({contactId: 2, incognito: true, msgContent_})).toBe('/_connect contact @2 incognito=on json {"type":"text","text":"hi"}') + }) + + it("renders APIConnectPreparedGroup with owner contact", () => { + expect(CC.APIConnectPreparedGroup.cmdString({groupId: 3, incognito: false, ownerContact: {contactId: 1, memberId: "abc"}})).toBe("/_connect group #3 contact=1 owner=abc") + expect(CC.APIConnectPreparedGroup.cmdString({groupId: 3, incognito: false, msgContent_})).toBe('/_connect group #3 json {"type":"text","text":"hi"}') + }) + + it("renders APIConnectContactViaAddress", () => { + expect(CC.APIConnectContactViaAddress.cmdString({userId: 1, incognito: true, contactId: 2})).toBe("/_connect contact 1 incognito=on 2") + }) +}) + +describe("member contact cmdString", () => { + it("renders APICreateMemberContact", () => { + expect(CC.APICreateMemberContact.cmdString({groupId: 1, groupMemberId: 2})).toBe("/_create member contact #1 2") + }) + + it("renders APISendMemberContactInvitation", () => { + expect(CC.APISendMemberContactInvitation.cmdString({contactId: 2, msgContent_: {type: "text", text: "hi"}})).toBe('/_invite member contact @2 json {"type":"text","text":"hi"}') + expect(CC.APISendMemberContactInvitation.cmdString({contactId: 2})).toBe("/_invite member contact @2") + }) +}) + +describe("network config cmdString", () => { + it("renders APIGetNetworkConfig and APISetNetworkConfig", () => { + const networkConfig = {socksMode: T.SocksMode.Always, logTLSErrors: true} as T.NetworkConfig + expect(CC.APIGetNetworkConfig.cmdString({})).toBe("/network") + expect(CC.APISetNetworkConfig.cmdString({networkConfig})).toBe(`/_network ${JSON.stringify(networkConfig)}`) + }) +}) + +describe("APIGetChatItemInfo.cmdString", () => { + it("renders chat reference and item ID", () => { + expect(CC.APIGetChatItemInfo.cmdString({chatRef: {chatType: T.ChatType.Direct, chatId: 2}, chatItemId: 5})).toBe("/_get item info @2 5") + }) +}) + +describe("APIGetUserServers.cmdString", () => { + it("renders user ID", () => { + expect(CC.APIGetUserServers.cmdString({userId: 1})).toBe("/_servers 1") + }) +}) diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_commands.py b/packages/simplex-chat-python/src/simplex_chat/types/_commands.py index 9dc3a05e8d..6c50c1bae6 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_commands.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_commands.py @@ -342,6 +342,32 @@ def APIListMembers_cmd_string(self: APIListMembers) -> str: APIListMembers_Response = CR.GroupMembers | CR.ChatCmdError +# Create a direct message contact with a group member. +# Network usage: interactive. +class APICreateMemberContact(TypedDict): + groupId: int # int64 + groupMemberId: int # int64 + + +def APICreateMemberContact_cmd_string(self: APICreateMemberContact) -> str: + return '/_create member contact #' + str(self['groupId']) + ' ' + str(self['groupMemberId']) + +APICreateMemberContact_Response = CR.NewMemberContact | CR.ChatCmdError + + +# Send a direct message invitation to a group member contact. +# Network usage: background. +class APISendMemberContactInvitation(TypedDict): + contactId: int # int64 + msgContent_: NotRequired["T.MsgContent"] + + +def APISendMemberContactInvitation_cmd_string(self: APISendMemberContactInvitation) -> str: + return '/_invite member contact @' + str(self['contactId']) + ((' json ' + json.dumps(self.get('msgContent_'))) if self.get('msgContent_') is not None else '') + +APISendMemberContactInvitation_Response = CR.NewMemberContactSentInv | CR.ChatCmdError + + # Create group. # Network usage: no. class APINewGroup(TypedDict): @@ -512,7 +538,7 @@ class APIConnectPlan(TypedDict): def APIConnectPlan_cmd_string(self: APIConnectPlan) -> str: - return '/_connect plan ' + str(self['userId']) + ' ' + self.get('connectTarget') + return '/_connect plan ' + str(self['userId']) + ' ' + self.get('connectTarget') + T.PlanResolveMode_cmd_string(self['resolveMode']) + ((' sig=' + json.dumps(self.get('linkOwnerSig'))) if self.get('linkOwnerSig') is not None else '') APIConnectPlan_Response = CR.ConnectionPlan | CR.ChatCmdError @@ -553,6 +579,80 @@ Connect_Response = ( ) +# Prepare contact chat from link data before connecting. +# Network usage: no. +class APIPrepareContact(TypedDict): + userId: int # int64 + connLink: "T.CreatedConnLink" + verifiedDomain: NotRequired[str] + contactSLinkData: "T.ContactShortLinkData" + + +def APIPrepareContact_cmd_string(self: APIPrepareContact) -> str: + return '/_prepare contact ' + str(self['userId']) + ' ' + T.CreatedConnLink_cmd_string(self['connLink']) + ((' domain=' + self.get('verifiedDomain')) if self.get('verifiedDomain') is not None else '') + ' ' + json.dumps(self['contactSLinkData']) + +APIPrepareContact_Response = CR.NewPreparedChat | CR.ChatCmdError + + +# Prepare group chat from link data before connecting. +# Network usage: no. +class APIPrepareGroup(TypedDict): + userId: int # int64 + connLinkContact: "T.CreatedConnLink" + direct: bool + verifiedDomain: NotRequired[str] + groupSLinkData: "T.GroupShortLinkData" + + +def APIPrepareGroup_cmd_string(self: APIPrepareGroup) -> str: + return '/_prepare group ' + str(self['userId']) + ' ' + T.CreatedConnLink_cmd_string(self['connLinkContact']) + (' direct=off' if not self['direct'] else '') + ((' domain=' + self.get('verifiedDomain')) if self.get('verifiedDomain') is not None else '') + ' ' + json.dumps(self['groupSLinkData']) + +APIPrepareGroup_Response = CR.NewPreparedChat | CR.ChatCmdError + + +# Connect to prepared contact. +# Network usage: interactive. +class APIConnectPreparedContact(TypedDict): + contactId: int # int64 + incognito: bool + msgContent_: NotRequired["T.MsgContent"] + + +def APIConnectPreparedContact_cmd_string(self: APIConnectPreparedContact) -> str: + return '/_connect contact @' + str(self['contactId']) + (' incognito=on' if self['incognito'] else '') + ((' json ' + json.dumps(self.get('msgContent_'))) if self.get('msgContent_') is not None else '') + +APIConnectPreparedContact_Response = CR.StartedConnectionToContact | CR.ContactAlreadyExists | CR.ChatCmdError + + +# Connect to prepared group. +# Network usage: interactive. +class APIConnectPreparedGroup(TypedDict): + groupId: int # int64 + incognito: bool + ownerContact: NotRequired["T.GroupOwnerContact"] + msgContent_: NotRequired["T.MsgContent"] + + +def APIConnectPreparedGroup_cmd_string(self: APIConnectPreparedGroup) -> str: + return '/_connect group #' + str(self['groupId']) + (' incognito=on' if self['incognito'] else '') + ((' ' + T.GroupOwnerContact_cmd_string(self.get('ownerContact'))) if self.get('ownerContact') is not None else '') + ((' json ' + json.dumps(self.get('msgContent_'))) if self.get('msgContent_') is not None else '') + +APIConnectPreparedGroup_Response = CR.StartedConnectionToGroup | CR.ChatCmdError + + +# Connect to existing contact via address in its profile. +# Network usage: interactive. +class APIConnectContactViaAddress(TypedDict): + userId: int # int64 + incognito: bool + contactId: int # int64 + + +def APIConnectContactViaAddress_cmd_string(self: APIConnectContactViaAddress) -> str: + return '/_connect contact ' + str(self['userId']) + (' incognito=on' if self['incognito'] else '') + ' ' + str(self['contactId']) + +APIConnectContactViaAddress_Response = CR.SentInvitationToContact | CR.ChatCmdError + + # Accept contact request. # Network usage: interactive. class APIAcceptContact(TypedDict): @@ -622,6 +722,59 @@ def APIGetChats_cmd_string(self: APIGetChats) -> str: APIGetChats_Response = CR.ApiChats | CR.ChatCmdError +# Get chat messages. +# Network usage: no. +class APIGetChat(TypedDict): + chatRef: "T.ChatRef" + contentTag: NotRequired["T.MsgContentTag"] + chatPagination: "T.ChatPagination" + search: NotRequired[str] + + +def APIGetChat_cmd_string(self: APIGetChat) -> str: + return '/_get chat ' + T.ChatRef_cmd_string(self['chatRef']) + ((' content=' + str(self.get('contentTag'))) if self.get('contentTag') is not None else '') + ' ' + T.ChatPagination_cmd_string(self['chatPagination']) + ((' search=' + self.get('search')) if self.get('search') is not None else '') + +APIGetChat_Response = CR.ApiChat | CR.ChatCmdError + + +# Mark messages as read. +# Network usage: no. +class APIChatItemsRead(TypedDict): + chatRef: "T.ChatRef" + chatItemIds: list[int] # int64, non-empty + + +def APIChatItemsRead_cmd_string(self: APIChatItemsRead) -> str: + return '/_read chat items ' + T.ChatRef_cmd_string(self['chatRef']) + ' ' + ','.join(map(str, self['chatItemIds'])) + +APIChatItemsRead_Response = CR.ItemsReadForChat | CR.ChatCmdError + + +# Get contact information. +# Network usage: no. +class APIContactInfo(TypedDict): + contactId: int # int64 + + +def APIContactInfo_cmd_string(self: APIContactInfo) -> str: + return '/_info @' + str(self['contactId']) + +APIContactInfo_Response = CR.ContactInfo | CR.ChatCmdError + + +# Get message information. +# Network usage: no. +class APIGetChatItemInfo(TypedDict): + chatRef: "T.ChatRef" + chatItemId: int # int64 + + +def APIGetChatItemInfo_cmd_string(self: APIGetChatItemInfo) -> str: + return '/_get item info ' + T.ChatRef_cmd_string(self['chatRef']) + ' ' + str(self['chatItemId']) + +APIGetChatItemInfo_Response = CR.ChatItemInfo | CR.ChatCmdError + + # Delete chat. # Network usage: background. class APIDeleteChat(TypedDict): @@ -825,6 +978,42 @@ def APIStopChat_cmd_string(self: APIStopChat) -> str: APIStopChat_Response = CR.ChatStopped +# Get network configuration. +# Network usage: no. +class APIGetNetworkConfig(TypedDict): + pass + + +def APIGetNetworkConfig_cmd_string(self: APIGetNetworkConfig) -> str: + return '/network' + +APIGetNetworkConfig_Response = CR.NetworkConfig | CR.ChatCmdError + + +# Set network configuration. +# Network usage: background. +class APISetNetworkConfig(TypedDict): + networkConfig: "T.NetworkConfig" + + +def APISetNetworkConfig_cmd_string(self: APISetNetworkConfig) -> str: + return '/_network ' + json.dumps(self['networkConfig']) + +APISetNetworkConfig_Response = CR.CmdOk | CR.ChatCmdError + + +# Get user servers and chat relays, grouped by operator. +# Network usage: no. +class APIGetUserServers(TypedDict): + userId: int # int64 + + +def APIGetUserServers_cmd_string(self: APIGetUserServers) -> str: + return '/_servers ' + str(self['userId']) + +APIGetUserServers_Response = CR.UserServers | CR.ChatCmdError + + # Remote control commands # Allows a bot to accept an incoming remote control session from a SimpleX Desktop client, giving the desktop live access to the bot's SimpleX instance. diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_responses.py b/packages/simplex-chat-python/src/simplex_chat/types/_responses.py index 07c7c04f49..f76c6c5342 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_responses.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_responses.py @@ -13,6 +13,18 @@ class ActiveUser(TypedDict): type: Literal["activeUser"] user: "T.User" +class ApiChat(TypedDict): + type: Literal["apiChat"] + user: "T.User" + chat: "T.AChat" + navInfo: NotRequired["T.NavigationInfo"] + +class ChatItemInfo(TypedDict): + type: Literal["chatItemInfo"] + user: "T.User" + chatItem: "T.AChatItem" + chatItemInfo: "T.ChatItemInfo" + class ChatItemNotChanged(TypedDict): type: Literal["chatItemNotChanged"] user: "T.User" @@ -81,6 +93,13 @@ class ContactDeleted(TypedDict): user: "T.User" contact: "T.Contact" +class ContactInfo(TypedDict): + type: Literal["contactInfo"] + user: "T.User" + contact: "T.Contact" + connectionStats_: NotRequired["T.ConnectionStats"] + customUserProfile: NotRequired["T.Profile"] + class ContactPrefsUpdated(TypedDict): type: Literal["contactPrefsUpdated"] user: "T.User" @@ -192,6 +211,11 @@ class Invitation(TypedDict): connLinkInvitation: "T.CreatedConnLink" connection: "T.PendingContactConnection" +class ItemsReadForChat(TypedDict): + type: Literal["itemsReadForChat"] + user: "T.User" + chatInfo: "T.ChatInfo" + class LeftMemberUser(TypedDict): type: Literal["leftMemberUser"] user: "T.User" @@ -219,11 +243,34 @@ class MembersRoleUser(TypedDict): toRole: "T.GroupMemberRole" msgSigned: bool +class NetworkConfig(TypedDict): + type: Literal["networkConfig"] + networkConfig: "T.NetworkConfig" + class NewChatItems(TypedDict): type: Literal["newChatItems"] user: "T.User" chatItems: list["T.AChatItem"] +class NewMemberContact(TypedDict): + type: Literal["newMemberContact"] + user: "T.User" + contact: "T.Contact" + groupInfo: "T.GroupInfo" + member: "T.GroupMember" + +class NewMemberContactSentInv(TypedDict): + type: Literal["newMemberContactSentInv"] + user: "T.User" + contact: "T.Contact" + groupInfo: "T.GroupInfo" + member: "T.GroupMember" + +class NewPreparedChat(TypedDict): + type: Literal["newPreparedChat"] + user: "T.User" + chat: "T.AChat" + class RcvFileAccepted(TypedDict): type: Literal["rcvFileAccepted"] user: "T.User" @@ -353,6 +400,11 @@ class UserProfileNoChange(TypedDict): type: Literal["userProfileNoChange"] user: "T.User" +class UserServers(TypedDict): + type: Literal["userServers"] + user: "T.User" + userServers: list["T.UserOperatorServers"] + class UsersList(TypedDict): type: Literal["usersList"] users: list["T.UserInfo"] @@ -365,6 +417,8 @@ class ApiChats(TypedDict): ChatResponse = ( AcceptingContactRequest | ActiveUser + | ApiChat + | ChatItemInfo | ChatItemNotChanged | ChatItemReaction | ChatItemUpdated @@ -379,6 +433,7 @@ ChatResponse = ( | ContactAlreadyExists | ContactConnectionDeleted | ContactDeleted + | ContactInfo | ContactPrefsUpdated | ContactRequestRejected | ContactsList @@ -398,11 +453,16 @@ ChatResponse = ( | GroupsList | GroupDomainVerified | Invitation + | ItemsReadForChat | LeftMemberUser | MemberAccepted | MembersBlockedForAllUser | MembersRoleUser + | NetworkConfig | NewChatItems + | NewMemberContact + | NewMemberContactSentInv + | NewPreparedChat | RcvFileAccepted | RcvFileAcceptedSndCancelled | RcvFileCancelled @@ -425,8 +485,9 @@ ChatResponse = ( | UserDeletedMembers | UserProfileUpdated | UserProfileNoChange + | UserServers | UsersList | ApiChats ) -ChatResponse_Tag = Literal["acceptingContactRequest", "activeUser", "chatItemNotChanged", "chatItemReaction", "chatItemUpdated", "chatItemsDeleted", "chatMsgContent", "chatRunning", "chatStarted", "chatStopped", "cmdOk", "chatCmdError", "connectionPlan", "contactAlreadyExists", "contactConnectionDeleted", "contactDeleted", "contactPrefsUpdated", "contactRequestRejected", "contactsList", "groupDeletedUser", "groupLink", "groupLinkCreated", "groupLinkDeleted", "groupCreated", "publicGroupCreated", "publicGroupCreationFailed", "groupRelays", "groupRelaysAdded", "groupRelaysAddFailed", "relayGroupAllowed", "groupMembers", "groupUpdated", "groupsList", "groupDomainVerified", "invitation", "leftMemberUser", "memberAccepted", "membersBlockedForAllUser", "membersRoleUser", "newChatItems", "rcvFileAccepted", "rcvFileAcceptedSndCancelled", "rcvFileCancelled", "remoteCtrlConnected", "remoteCtrlConnecting", "sentConfirmation", "sentGroupInvitation", "sentGroupInvitationViaGroup", "sentInvitation", "sentInvitationToContact", "serviceReplyAccepted", "sndFileCancelled", "startedConnectionToContact", "startedConnectionToGroup", "userAcceptedGroupSent", "userContactLink", "userContactLinkCreated", "userContactLinkDeleted", "userContactLinkUpdated", "userDeletedMembers", "userProfileUpdated", "userProfileNoChange", "usersList", "apiChats"] +ChatResponse_Tag = Literal["acceptingContactRequest", "activeUser", "apiChat", "chatItemInfo", "chatItemNotChanged", "chatItemReaction", "chatItemUpdated", "chatItemsDeleted", "chatMsgContent", "chatRunning", "chatStarted", "chatStopped", "cmdOk", "chatCmdError", "connectionPlan", "contactAlreadyExists", "contactConnectionDeleted", "contactDeleted", "contactInfo", "contactPrefsUpdated", "contactRequestRejected", "contactsList", "groupDeletedUser", "groupLink", "groupLinkCreated", "groupLinkDeleted", "groupCreated", "publicGroupCreated", "publicGroupCreationFailed", "groupRelays", "groupRelaysAdded", "groupRelaysAddFailed", "relayGroupAllowed", "groupMembers", "groupUpdated", "groupsList", "groupDomainVerified", "invitation", "itemsReadForChat", "leftMemberUser", "memberAccepted", "membersBlockedForAllUser", "membersRoleUser", "networkConfig", "newChatItems", "newMemberContact", "newMemberContactSentInv", "newPreparedChat", "rcvFileAccepted", "rcvFileAcceptedSndCancelled", "rcvFileCancelled", "remoteCtrlConnected", "remoteCtrlConnecting", "sentConfirmation", "sentGroupInvitation", "sentGroupInvitationViaGroup", "sentInvitation", "sentInvitationToContact", "serviceReplyAccepted", "sndFileCancelled", "startedConnectionToContact", "startedConnectionToGroup", "userAcceptedGroupSent", "userContactLink", "userContactLinkCreated", "userContactLinkDeleted", "userContactLinkUpdated", "userDeletedMembers", "userProfileUpdated", "userProfileNoChange", "userServers", "usersList", "apiChats"] diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py index af9a501d81..255be16753 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py @@ -1312,6 +1312,20 @@ class ChatItemDeletion(TypedDict): deletedChatItem: "AChatItem" toChatItem: NotRequired["AChatItem"] +class ChatItemInfo(TypedDict): + itemVersions: list["ChatItemVersion"] + memberDeliveryStatuses: NotRequired[list["MemberDeliveryStatus"]] # non-empty + forwardedFromChatItem: NotRequired["AChatItem"] + fileXftpServers: list[str] + +class ChatItemVersion(TypedDict): + chatItemVersionId: int # int64 + msgContent: "MsgContent" + formattedText: NotRequired[list["FormattedText"]] + markdownBlocks: NotRequired[list["MarkdownBlock"]] # non-empty + itemVersionTs: str # ISO-8601 timestamp + createdAt: str # ISO-8601 timestamp + ChatLayout = Literal["classic", "terminal"] class ChatListQuery_filters(TypedDict): @@ -1327,6 +1341,43 @@ ChatListQuery = ChatListQuery_filters | ChatListQuery_search ChatListQuery_Tag = Literal["filters", "search"] +class ChatPagination_last(TypedDict): + type: Literal["last"] + count: int # int + +class ChatPagination_after(TypedDict): + type: Literal["after"] + chatItemId: int # int64 + count: int # int + +class ChatPagination_before(TypedDict): + type: Literal["before"] + chatItemId: int # int64 + count: int # int + +class ChatPagination_around(TypedDict): + type: Literal["around"] + chatItemId: int # int64 + count: int # int + +class ChatPagination_initial(TypedDict): + type: Literal["initial"] + count: int # int + +ChatPagination = ( + ChatPagination_last + | ChatPagination_after + | ChatPagination_before + | ChatPagination_around + | ChatPagination_initial +) + +ChatPagination_Tag = Literal["last", "after", "before", "around", "initial"] + + +def ChatPagination_cmd_string(self: ChatPagination) -> str: + return 'count=' + str(self['count']) if str(self['type']) == 'last' else 'after=' + str(self['chatItemId']) + ' count=' + str(self['count']) if str(self['type']) == 'after' else 'before=' + str(self['chatItemId']) + ' count=' + str(self['count']) if str(self['type']) == 'before' else 'around=' + str(self['chatItemId']) + ' count=' + str(self['count']) if str(self['type']) == 'around' else 'initial=' + str(self['count']) # type: ignore[typeddict-item] + ChatPeerType = Literal["human", "bot", "business", "agent"] # Used in API commands. Chat scope can only be passed with groups. @@ -1437,6 +1488,19 @@ class ComposedMessage(TypedDict): msgContent: "MsgContent" mentions: dict[str, int] # str : int64 +class ConditionsAcceptance_accepted(TypedDict): + type: Literal["accepted"] + acceptedAt: NotRequired[str] # ISO-8601 timestamp + autoAccepted: bool + +class ConditionsAcceptance_required(TypedDict): + type: Literal["required"] + deadline: NotRequired[str] # ISO-8601 timestamp + +ConditionsAcceptance = ConditionsAcceptance_accepted | ConditionsAcceptance_required + +ConditionsAcceptance_Tag = Literal["accepted", "required"] + class ConnStatus_new(TypedDict): type: Literal["new"] @@ -1583,6 +1647,14 @@ ConnectionPlan = ( ConnectionPlan_Tag = Literal["invitationLink", "contactAddress", "groupLink", "error"] +class ConnectionStats(TypedDict): + connAgentVersion: int # int + rcvQueuesInfo: list["RcvQueueInfo"] + sndQueuesInfo: list["SndQueueInfo"] + ratchetSyncState: "RatchetSyncState" + ratchetSyncSupported: bool + subStatus: NotRequired["SubscriptionStatus"] + class Contact(TypedDict): contactId: int # int64 localDisplayName: str @@ -2152,6 +2224,14 @@ class GroupMemberSettings(TypedDict): GroupMemberStatus = Literal["rejected", "removed", "left", "deleted", "unknown", "invited", "pending_approval", "pending_review", "introduced", "intro-inv", "accepted", "announced", "connected", "complete", "creator"] +class GroupOwnerContact(TypedDict): + contactId: int # int64 + memberId: str + + +def GroupOwnerContact_cmd_string(self: GroupOwnerContact) -> str: + return 'contact=' + str(self['contactId']) + ' owner=' + self['memberId'] + class GroupPreference(TypedDict): enable: "GroupFeatureEnabled" @@ -2199,6 +2279,47 @@ class GroupShortLinkInfo(TypedDict): groupRelays: list[str] publicGroupId: NotRequired[str] +class GroupSndStatus_new(TypedDict): + type: Literal["new"] + +class GroupSndStatus_forwarded(TypedDict): + type: Literal["forwarded"] + +class GroupSndStatus_inactive(TypedDict): + type: Literal["inactive"] + +class GroupSndStatus_sent(TypedDict): + type: Literal["sent"] + +class GroupSndStatus_rcvd(TypedDict): + type: Literal["rcvd"] + msgRcptStatus: "MsgReceiptStatus" + +class GroupSndStatus_error(TypedDict): + type: Literal["error"] + agentError: "SndError" + +class GroupSndStatus_warning(TypedDict): + type: Literal["warning"] + agentError: "SndError" + +class GroupSndStatus_invalid(TypedDict): + type: Literal["invalid"] + text: str + +GroupSndStatus = ( + GroupSndStatus_new + | GroupSndStatus_forwarded + | GroupSndStatus_inactive + | GroupSndStatus_sent + | GroupSndStatus_rcvd + | GroupSndStatus_error + | GroupSndStatus_warning + | GroupSndStatus_invalid +) + +GroupSndStatus_Tag = Literal["new", "forwarded", "inactive", "sent", "rcvd", "error", "warning", "invalid"] + class GroupSummary(TypedDict): currentMembers: int # int64 publicMemberCount: NotRequired[int] # int64 @@ -2214,6 +2335,8 @@ GroupType = Literal["channel", "group"] HandshakeError = Literal["PARSE", "IDENTITY", "BAD_AUTH", "BAD_SERVICE"] +HostMode = Literal["onionViaSocks", "onion", "public"] + InlineFileMode = Literal["offer", "sent"] class InvitationLinkPlan_ok(TypedDict): @@ -2255,6 +2378,11 @@ InvitedBy = InvitedBy_contact | InvitedBy_user | InvitedBy_unknown InvitedBy_Tag = Literal["contact", "user", "unknown"] +class KeepAliveOpts(TypedDict): + keepIdle: int # int + keepIntvl: int # int + keepCnt: int # int + class LinkContent_page(TypedDict): type: Literal["page"] @@ -2356,6 +2484,11 @@ MarkdownBlock_Tag = Literal["paragraph", "heading", "codeBlock", "quote", "list" MemberCriteria = Literal["all"] +class MemberDeliveryStatus(TypedDict): + groupMemberId: int # int64 + memberDeliveryStatus: "GroupSndStatus" + sentViaProxy: NotRequired[bool] + # Connection link sent in a message - only short links are allowed. class MsgChatLink_contact(TypedDict): @@ -2438,6 +2571,8 @@ MsgContent = ( MsgContent_Tag = Literal["text", "link", "image", "video", "voice", "file", "report", "chat", "unknown"] +MsgContentTag = Literal["text", "link", "image", "video", "voice", "file", "report", "chat"] + MsgDecryptError = Literal["ratchetHeader", "tooManySkipped", "ratchetEarlier", "other", "ratchetSync"] MsgDirection = Literal["rcv", "snd"] @@ -2510,6 +2645,28 @@ NameErrorType = NameErrorType_NO_RESOLVER | NameErrorType_NOT_FOUND | NameErrorT NameErrorType_Tag = Literal["NO_RESOLVER", "NOT_FOUND", "RESOLVER"] +class NavigationInfo(TypedDict): + afterUnread: int # int + afterTotal: int # int + +class NetworkConfig(TypedDict): + socksProxy: NotRequired[str] + socksMode: "SocksMode" + hostMode: "HostMode" + requiredHostMode: bool + sessionMode: "TransportSessionMode" + smpProxyMode: "SMPProxyMode" + smpProxyFallback: "SMPProxyFallback" + smpWebPortServers: "SMPWebPortServers" + tcpConnectTimeout: "NetworkTimeout" + tcpTimeout: "NetworkTimeout" + tcpTimeoutPerKb: int # int64 + rcvConcurrency: int # int + tcpKeepAlive: NotRequired["KeepAliveOpts"] + smpPingInterval: int # int64 + smpPingCount: int # int + logTLSErrors: bool + class NetworkError_connectError(TypedDict): type: Literal["connectError"] connectError: str @@ -2542,6 +2699,10 @@ NetworkError = ( NetworkError_Tag = Literal["connectError", "tLSError", "unknownCAError", "failedError", "timeoutError", "subscribeError"] +class NetworkTimeout(TypedDict): + backgroundTimeout: int # int + interactiveTimeout: int # int + class NewUser(TypedDict): profile: NotRequired["Profile"] pastTimestamp: bool @@ -2557,6 +2718,8 @@ class NoteFolder(TypedDict): favorite: bool unread: bool +OperatorTag = Literal["simplex", "flux"] + class OwnerVerification_verified(TypedDict): type: Literal["verified"] @@ -2595,6 +2758,10 @@ class PendingContactConnection(TypedDict): PlanResolveMode = Literal["allGroups", "unknown", "never"] + +def PlanResolveMode_cmd_string(self: PlanResolveMode) -> str: + return ' resolve=allGroups' if str(self) == 'allGroups' else ' resolve=never' if str(self) == 'never' else '' + class PrefEnabled(TypedDict): forUser: bool forContact: bool @@ -2687,6 +2854,8 @@ class PublicGroupProfile(TypedDict): publicGroupId: str publicGroupAccess: NotRequired["PublicGroupAccess"] +QueueStatus = Literal["new", "confirmed", "secured", "active", "disabled"] + class RCErrorType_internal(TypedDict): type: Literal["internal"] internalErr: str @@ -2956,6 +3125,15 @@ RcvMsgError = RcvMsgError_dropped | RcvMsgError_parseError RcvMsgError_Tag = Literal["dropped", "parseError"] +class RcvQueueInfo(TypedDict): + rcvServer: str + status: "QueueStatus" + rcvSwitchStatus: NotRequired["RcvSwitchStatus"] + canAbortSwitch: bool + subStatus: "SubscriptionStatus" + +RcvSwitchStatus = Literal["switch_started", "sending_qadd", "sending_quse", "received_message", "received_qend"] + class RelayCapabilities(TypedDict): webDomain: NotRequired[str] @@ -3077,6 +3255,12 @@ SMPAgentError = ( SMPAgentError_Tag = Literal["A_MESSAGE", "A_PROHIBITED", "A_VERSION", "A_LINK", "A_CRYPTO", "A_DUPLICATE", "A_QUEUE", "A_SERVICE"] +SMPProxyFallback = Literal["allow", "allowProtected", "prohibit"] + +SMPProxyMode = Literal["always", "unknown", "unprotected", "never"] + +SMPWebPortServers = Literal["all", "preset", "off"] + class SecurityCode(TypedDict): securityCode: str verifiedAt: str # ISO-8601 timestamp @@ -3086,6 +3270,27 @@ SegmentRange = TypedDict("SegmentRange", { "to": int, # int }) +class ServerOperator(TypedDict): + operatorId: int # int64 + operatorTag: NotRequired["OperatorTag"] + tradeName: str + legalName: NotRequired[str] + serverDomains: list[str] + conditionsAcceptance: "ConditionsAcceptance" + enabled: bool + smpRoles: "ServerRoles" + xftpRoles: "ServerRoles" + +class ServerRoles(TypedDict): + storage: bool + proxy: bool + names: bool + +class ServerRolesOverride(TypedDict): + storage: NotRequired[bool] + proxy: NotRequired[bool] + names: NotRequired[bool] + class SimplePreference(TypedDict): allow: "FeatureAllowed" @@ -3245,6 +3450,15 @@ SndGroupEvent = ( SndGroupEvent_Tag = Literal["memberRole", "memberBlocked", "userRole", "memberDeleted", "userLeft", "groupUpdated", "memberAccepted", "userPendingReview"] +class SndQueueInfo(TypedDict): + sndServer: str + status: "QueueStatus" + sndSwitchStatus: NotRequired["SndSwitchStatus"] + +SndSwitchStatus = Literal["sending_qkey", "sending_qtest", "securing_queue", "sending_qend"] + +SocksMode = Literal["always", "onion"] + class SrvError_host(TypedDict): type: Literal["host"] @@ -3762,6 +3976,8 @@ TransportError = ( TransportError_Tag = Literal["badBlock", "version", "largeMsg", "badSession", "noServerAuth", "handshake"] +TransportSessionMode = Literal["user", "session", "server", "entity"] + UIColorMode = Literal["light", "dark"] class UIColors(TypedDict): @@ -3860,6 +4076,12 @@ class UserInfo(TypedDict): user: "User" unreadCount: int # int +class UserOperatorServers(TypedDict): + operator: NotRequired["ServerOperator"] + smpServers: list["UserServer"] + xftpServers: list["UserServer"] + chatRelays: list["UserChatRelay"] + class UserProfileUpdateSummary(TypedDict): updateSuccesses: int # int updateFailures: int # int @@ -3869,6 +4091,15 @@ class UserPwdHash(TypedDict): hash: str salt: str +class UserServer(TypedDict): + serverId: int # int64 + server: str + preset: bool + tested: NotRequired[bool] + enabled: bool + roles: "ServerRolesOverride" + deleted: bool + class VersionRange(TypedDict): minVersion: int # int maxVersion: int # int diff --git a/packages/simplex-chat-python/tests/test_codegen.py b/packages/simplex-chat-python/tests/test_codegen.py index 20a9400d18..15aede4871 100644 --- a/packages/simplex-chat-python/tests/test_codegen.py +++ b/packages/simplex-chat-python/tests/test_codegen.py @@ -45,3 +45,102 @@ def test_api_connect_cmd_string_renders_incognito(): link = {"connFullLink": "L"} assert CC.APIConnect_cmd_string({"userId": 1, "incognito": True, "preparedLink_": link}) == "/_connect 1 incognito=on L" assert CC.APIConnect_cmd_string({"userId": 1, "incognito": False, "preparedLink_": link}) == "/_connect 1 L" + + +def test_chat_pagination_cmd_string(): + assert T.ChatPagination_cmd_string({"type": "last", "count": 0}) == "count=0" + assert T.ChatPagination_cmd_string({"type": "initial", "count": 1}) == "initial=1" + assert ( + T.ChatPagination_cmd_string({"type": "around", "chatItemId": 5, "count": 0}) + == "around=5 count=0" + ) + assert ( + T.ChatPagination_cmd_string({"type": "after", "chatItemId": 5, "count": 100}) + == "after=5 count=100" + ) + assert ( + T.ChatPagination_cmd_string({"type": "before", "chatItemId": 5, "count": 10}) + == "before=5 count=10" + ) + + +def test_api_get_chat_cmd_string(): + ref = {"chatType": "direct", "chatId": 2} + last = {"type": "last", "count": 10} + assert ( + CC.APIGetChat_cmd_string( + {"chatRef": ref, "contentTag": "image", "chatPagination": last, "search": "k"} + ) + == "/_get chat @2 content=image count=10 search=k" + ) + assert ( + CC.APIGetChat_cmd_string( + {"chatRef": ref, "chatPagination": {"type": "initial", "count": 1}} + ) + == "/_get chat @2 initial=1" + ) + + +def test_api_chat_items_read_cmd_string(): + ref = {"chatType": "direct", "chatId": 2} + assert ( + CC.APIChatItemsRead_cmd_string({"chatRef": ref, "chatItemIds": [3, 4]}) + == "/_read chat items @2 3,4" + ) + + +def test_api_connect_plan_cmd_string_omits_default_resolve_mode(): + assert T.PlanResolveMode_cmd_string("unknown") == "" + assert T.PlanResolveMode_cmd_string("allGroups") == " resolve=allGroups" + assert T.PlanResolveMode_cmd_string("never") == " resolve=never" + assert ( + CC.APIConnectPlan_cmd_string({"userId": 1, "connectTarget": "L", "resolveMode": "unknown"}) + == "/_connect plan 1 L" + ) + assert ( + CC.APIConnectPlan_cmd_string({"userId": 1, "connectTarget": "L", "resolveMode": "never"}) + == "/_connect plan 1 L resolve=never" + ) + + +def test_api_connect_prepared_group_cmd_string(): + owner = {"contactId": 1, "memberId": "abc"} + msg = {"type": "text", "text": "hi"} + assert T.GroupOwnerContact_cmd_string(owner) == "contact=1 owner=abc" + assert ( + CC.APIConnectPreparedGroup_cmd_string( + {"groupId": 3, "incognito": False, "ownerContact": owner} + ) + == "/_connect group #3 contact=1 owner=abc" + ) + assert ( + CC.APIConnectPreparedGroup_cmd_string({"groupId": 3, "incognito": True, "msgContent_": msg}) + == '/_connect group #3 incognito=on json {"type": "text", "text": "hi"}' + ) + + +def test_member_contact_cmd_string(): + assert ( + CC.APICreateMemberContact_cmd_string({"groupId": 1, "groupMemberId": 2}) + == "/_create member contact #1 2" + ) + assert ( + CC.APISendMemberContactInvitation_cmd_string({"contactId": 2}) + == "/_invite member contact @2" + ) + + +def test_api_get_network_config_cmd_string(): + assert CC.APIGetNetworkConfig_cmd_string({}) == "/network" + + +def test_api_get_chat_item_info_cmd_string(): + ref = {"chatType": "direct", "chatId": 2} + assert ( + CC.APIGetChatItemInfo_cmd_string({"chatRef": ref, "chatItemId": 5}) + == "/_get item info @2 5" + ) + + +def test_api_get_user_servers_cmd_string(): + assert CC.APIGetUserServers_cmd_string({"userId": 1}) == "/_servers 1" diff --git a/plans/2026-10-23-bot-api-commands.md b/plans/2026-10-23-bot-api-commands.md new file mode 100644 index 0000000000..2a2a5526f1 --- /dev/null +++ b/plans/2026-10-23-bot-api-commands.md @@ -0,0 +1,427 @@ +# Bot API: chat, connection, member contact and network commands + +Fourteen commands were undocumented, and their strings were built by hand: in bots, and in the Node and Python libraries for `APIGetChat`, `APICreateMemberContact` and `APISendMemberContactInvitation`. +The commands are documented; in the core, seven command constructors and `ChatPagination` are changed to record syntax, and `verifiedDomain` is typed as `StrJSON`. +TypeScript and Python types are generated, with `cmdString` and `_cmd_string` functions; each command of the Node library is sent through its `cmdString`, and the methods of the Python library (`api.py`) are changed in a following pull request. + +References are to branch `ep/bot-api` at `d53829b0b`. Haskell paths without a directory are in `src/Simplex/Chat/`. `Library/` is `src/Simplex/Chat/Library/`. `API/` is `bots/src/API/`. `TS/` is `packages/simplex-chat-client/types/typescript/src/`. `node/` is `packages/simplex-chat-nodejs/`. `SMQ/` is simplexmq `src/Simplex/Messaging/` at `8bc4d3601` (`cabal.project:24`). + +## Decisions + +Documented commands; strings in each syntax are parsed by `chatCommandP` at the `parser` line of `Library/Commands.hs`: +``` +category / command network usage parser syntax +Group commands + APICreateMemberContact interactive :6552 /_create member contact # + APISendMemberContactInvitation background :6553 /_invite member contact @[ json ] +Connection commands + APIPrepareContact no :6560 /_prepare contact [ domain=] + APIPrepareGroup no :6561 /_prepare group [ direct=off][ domain=] + APIConnectPreparedContact interactive :6564 /_connect contact @[ incognito=on][ json ] + APIConnectPreparedGroup interactive :6565 /_connect group #[ incognito=on][ ][ json ] + APIConnectContactViaAddress interactive :6607 /_connect contact [ incognito=on] +Chat commands + APIGetChat no :6349 /_get chat [ content=text|link|image|video|voice|file|report|chat] [ search=] + APIChatItemsRead no :6376 /_read chat items [,...] + APIContactInfo no :6469 /_info @ + APIGetChatItemInfo no :6352 /_get item info +Chat management + APIGetNetworkConfig no :6461 /network + APISetNetworkConfig background :6459 /_network + APIGetUserServers no :6445 /_servers +``` +Changed documented syntax: +- `APIConnectPlan`: `/_connect plan [ sig=]` (parser `Library/Commands.hs:6559`). +- `PlanResolveMode`: ` resolve=allGroups` and ` resolve=never`; `unknown`, the parser default (`Library/Commands.hs:6559`), as an empty string. + +Documented errors: +``` +APICreateMemberContact CEGroupMemberNotActive + CEPeerChatVRangeIncompatible +APISendMemberContactInvitation CEGroupMemberNotActive +``` + +Documented responses: +``` +response command +CRApiChat APIGetChat +CRChatItemInfo APIGetChatItemInfo +CRContactInfo APIContactInfo +CRItemsReadForChat APIChatItemsRead +CRNetworkConfig APIGetNetworkConfig +CRNewMemberContact APICreateMemberContact +CRNewMemberContactSentInv APISendMemberContactInvitation +CRNewPreparedChat APIPrepareContact + APIPrepareGroup +CRUserServers APIGetUserServers +``` + +Documented types: +``` +with syntax + ChatPagination count=|after= count=|before= count=|around= count=|initial= + GroupOwnerContact contact= owner= +records + ChatItemInfo + ChatItemVersion + ConnectionStats + KeepAliveOpts + MemberDeliveryStatus + NavigationInfo + NetworkConfig + NetworkTimeout + RcvQueueInfo + ServerOperator + ServerRoles + ServerRolesOverride + SndQueueInfo + UserOperatorServers + UserServer +unions + ConditionsAcceptance + GroupSndStatus +enums + HostMode + MsgContentTag MCUnknown_ excluded + OperatorTag + QueueStatus + RcvSwitchStatus + SMPProxyFallback + SMPProxyMode + SMPWebPortServers + SocksMode + SndSwitchStatus + TransportSessionMode +``` +Types generated as their JSON encoding, not as their Haskell type: +``` +type generated as JSON +AChatInfo ChatInfo JSON of ChatInfo (Messages.hs:254-256) +ProtoServerWithAuth string strEncode (SMQ/Protocol.hs:1370-1372, :1381-1383) +ServerOperator' ServerOperator JSON of ServerOperator (Operators.hs:603-605) +UserServer' UserServer JSON of UserServer (Operators.hs:616-618) +SocksProxyWithAuth string strEncode (SMQ/Transport/Client.hs:265-274) +RcvSwitchStatus strEncode values strEncode (SMQ/Agent/Protocol.hs:617-623, :637-642) +SndSwitchStatus strEncode values strEncode (SMQ/Agent/Protocol.hs:651-656, :669-674) +``` + +Core: +- Documented commands have named fields (`tests/APIDocs.hs:84-88`): seven constructors are changed to record syntax. +- `ChatPagination` constructors have fields `count` and `chatItemId`, the fields of the generated union. +- `verifiedDomain` of `APIPrepareContact` and `APIPrepareGroup` has type `Maybe (StrJSON "SimplexDomain" SimplexDomain)`, as `simplexDomain` of `APISetUserDomain` (`Controller.hs:455`); it is generated as a string. +- Accepted command strings, handler results and responses are unchanged. + +Node library: +- Every command is sent as `CC..cmdString(...)`. +- Result of a new method, by the first matching row: +``` +accepted responses result +CR.CmdOk only void +one type, with one field other than user that field +other the response +``` +- For any other response, a `ChatCommandError` is thrown. +``` +new method command result +apiConnectPreparedLink APIConnect CR.SentConfirmation | CR.SentInvitation | CR.ContactAlreadyExists +apiPrepareContact APIPrepareContact chat +apiPrepareGroup APIPrepareGroup chat +apiConnectPreparedContact APIConnectPreparedContact CR.StartedConnectionToContact | CR.ContactAlreadyExists +apiConnectPreparedGroup APIConnectPreparedGroup CR.StartedConnectionToGroup +apiConnectContactViaAddress APIConnectContactViaAddress CR.SentInvitationToContact +apiChatItemsRead APIChatItemsRead chatInfo +apiGetChatItemInfo APIGetChatItemInfo CR.ChatItemInfo +apiContactInfo APIContactInfo CR.ContactInfo +apiGetNetworkConfig APIGetNetworkConfig networkConfig +apiSetNetworkConfig APISetNetworkConfig void, on CR.CmdOk +apiGetUserServers APIGetUserServers userServers +``` +``` +changed method change +stopChat APIStopChat; string unchanged, /_stop +apiReceiveFile optional filePath, passed to ReceiveFile; third parameter approvedRelays = true, passed as userApprovedRelays +apiConnectPlan result: [plan, link, planSimplexName?, otherSimplexName?] +apiGetChat third parameter: a count or T.ChatPagination; a count is converted to {type: "last", count}, sent as count=; result: T.AChat +apiCreateMemberContact APICreateMemberContact; signature unchanged +apiSendMemberContactInvitation APISendMemberContactInvitation; a string message is sent as {type: "text", text}; JSDoc network usage: background +``` +- `apiConnect` is unchanged: `ConnReqType` is returned; for `contactAlreadyExists`, a `ChatCommandError` "contact already exists" is thrown. +- Package versions: as on master. Before `simplex-chat` is released, `@simplex-chat/types` is published above 7.1.0-beta.6.2 with these commands, and the dependency at `node/package.json:27` is raised to that version. + +Delivery, decided by the user on 2026-10-24: +- The pull request in simplex-chat/simplex-chat is opened after the fork pull request is approved by the user. +- MCP server, in a following pull request, after the release of `@simplex-chat/types` and `simplex-chat`: the strings of the commands whose Node library methods are added or changed here are replaced by these methods. +``` +APIGetChat packages/simplex-chat-mcp/src/messages.ts:179 +APIChatItemsRead packages/simplex-chat-mcp/src/messages.ts:183 +APIConnectPlan packages/simplex-chat-mcp/src/tools.ts:471 +prepare and connect commands packages/simplex-chat-mcp/src/tools.ts:520-535 +APIConnect packages/simplex-chat-mcp/src/tools.ts:539 +APIContactInfo packages/simplex-chat-mcp/src/tools.ts:771 +``` +- Python library, in a following pull request: the strings built by hand are replaced by the generated `_cmd_string` functions; a string message is then sent as JSON, and `import json` (`api.py:6`) is removed. +``` +APIStopChat packages/simplex-chat-python/src/simplex_chat/api.py:116 +APIGetChat packages/simplex-chat-python/src/simplex_chat/api.py:686 +APICreateMemberContact packages/simplex-chat-python/src/simplex_chat/api.py:764 +APISendMemberContactInvitation packages/simplex-chat-python/src/simplex_chat/api.py:774-779 +``` + +## Mechanics + +`Controller.hs`: +``` +APICreateMemberContact :488 + groupId :: GroupId + groupMemberId :: GroupMemberId +APISetNetworkConfig :516 + networkConfig :: NetworkConfig +APIContactInfo :524 + contactId :: ContactId +APIPrepareContact :569 + userId :: UserId + connLink :: ACreatedConnLink + verifiedDomain :: Maybe (StrJSON "SimplexDomain" SimplexDomain) + contactSLinkData :: ContactShortLinkData +APIPrepareGroup :570 + userId :: UserId + connLinkContact :: CreatedLinkContact + direct :: DirectLink + verifiedDomain :: Maybe (StrJSON "SimplexDomain" SimplexDomain) + groupSLinkData :: GroupShortLinkData +APIConnectContactViaAddress :579 + userId :: UserId + incognito :: IncognitoEnabled + contactId :: ContactId +ChatPagination :1139-1144 + CPLast {count :: Int} + CPAfter {chatItemId :: ChatItemId, count :: Int} + CPBefore {chatItemId :: ChatItemId, count :: Int} + CPAround {chatItemId :: ChatItemId, count :: Int} + CPInitial {count :: Int} +``` +`APIGetUserServers`, added later: `{userId :: UserId}`. +`Library/Commands.hs`: +``` +APIPrepareContact :2208 unStrJSON <$> verifiedDomain, passed to createPreparedGroup +APIPrepareGroup :2236 unStrJSON <$> verifiedDomain, passed to preparedGroupFromLink +connectWithPlan :4694 vName = StrJSON . nameDomain <$> planSimplexName, passed at :4704 and :4713 +chatCommandP :6560 " domain=" *> (StrJSON <$> strP) +chatCommandP :6561 " domain=" *> (StrJSON <$> strP) +``` +`API/Docs/Commands.hs` entries: +``` +Group commands :130-131 +Connection commands :156-160 +Chat commands :170-172 +Chat management :221-222 +APIConnectPlan :153, syntax +removed + the twelve commands, from undocumentedCommands (:359) + the placeholder comments of APIChatItemsRead and APIGetChat +``` +`APIGetChatItemInfo`, added later: the entry after `APIContactInfo` in "Chat commands"; its name in `undocumentedCommands` and its placeholder comment removed. +`APIGetUserServers`, added later: the entry after `APISetNetworkConfig` in "Chat management"; its name in `undocumentedCommands` removed. +`API/Docs/Responses.hs` entries: +``` +CRApiChat :50 +CRContactInfo :65 +CRItemsReadForChat :85 +CRNetworkConfig :90 +CRNewMemberContact :92 +CRNewMemberContactSentInv :93 +CRNewPreparedChat :94 +removed + the seven responses, from undocumentedResponses (:130) + the placeholder comments of CRApiChat and CRItemsReadForChat +``` +`CRChatItemInfo`, added later: the entry after `CRApiChat`; its name in `undocumentedResponses` and its placeholder comment removed. +`CRUserServers`, added later: the entry after `CRUserProfileNoChange`; its name in `undocumentedResponses` removed. +`API/Docs/Types.hs`: +- Imports: `isLower` (`:19`); `KeepAliveOpts (..)` (`:53`). +- `consSnake` (`:164-165`), new: the constructor name without the prefix, in lowercase, with `_` after each lowercase letter followed by an uppercase one. +- `PlanResolveMode` syntax: `:355`. +- `deriving instance Generic` of the nineteen types: eleven added, eight enabled from comments. + +Entries: +``` +new, in alphabetical order + GroupOwnerContact :313 + HostMode :324 + KeepAliveOpts :328 + NetworkConfig :348 + NetworkTimeout :350 + QueueStatus :367 + SMPProxyFallback :398 + SMPProxyMode :399 + SMPWebPortServers :400 + SocksMode :406 + TransportSessionMode :416 +enabled from comments + ChatPagination :438 STUnion, with syntax + ConnectionStats :439 + MsgContentTag :443 suffix "_" + NavigationInfo :444 + RcvQueueInfo :449 + RcvSwitchStatus :450 STEnum' (consSnake "RS") + SndQueueInfo :452 + SndSwitchStatus :453 STEnum' (consSnake "SS") +``` +Added later, entries and `deriving instance Generic` enabled from comments: +``` +ChatItemInfo +ChatItemVersion +GroupSndStatus +MemberDeliveryStatus +``` +Added later, entries and `deriving instance Generic`, new: +``` +ConditionsAcceptance STUnion "CA" +OperatorTag STEnum "OT" +ServerOperator sti @ServerOperator, typeName "ServerOperator"; Generic (ServerOperator' 'DBStored) +ServerRoles import of ServerRoles (..) from SMQ/Agent/Env/SQLite.hs +ServerRolesOverride +UserOperatorServers +UserServer sti @(UserServer 'PSMP), typeName "UserServer"; Generic (UserServer' 'DBStored p); import of ProtocolType (..) +``` +`API/TypeInfo.hs`: +- `AChatInfo` → `ChatInfo`: `:177`. +- `SocksProxyWithAuth` in `stringTypes`: `:228`. +- Added later: `ServerOperator'` → `ServerOperator` and `UserServer'` → `UserServer`, after `UserChatRelay'`; `ProtoServerWithAuth` in `stringTypes`. + +`node/src/api.ts`: +``` +import of CR :1 +stopChat :151 +apiReceiveFile :542 +apiConnectPlan :732 +apiConnectPreparedLink :752 +apiPrepareContact :768 +apiPrepareGroup :778 +apiConnectPreparedContact :788 +apiConnectPreparedGroup :803 +apiConnectContactViaAddress :813 +apiGetChat :951 +apiChatItemsRead :962 +apiContactInfo :972 +apiGetNetworkConfig :982 +apiSetNetworkConfig :992 +apiCreateMemberContact :1088 +apiSendMemberContactInvitation :1099 +``` +`apiGetChatItemInfo`, added later, after `apiChatItemsRead`. +`apiGetUserServers`, added later, after `apiSetNetworkConfig`. +Generated by the "Bot API docs" tests (`tests/APIDocs.hs:32-50`): +``` +bots/api/COMMANDS.md +bots/api/TYPES.md +TS/commands.ts +TS/responses.ts +TS/types.ts +packages/simplex-chat-python/src/simplex_chat/types/_commands.py +packages/simplex-chat-python/src/simplex_chat/types/_responses.py +packages/simplex-chat-python/src/simplex_chat/types/_types.py +``` +Generated by `npm run docs` (typedoc, `node/package.json:24`): +``` +node/docs/api.Class.ChatApi.md +node/docs/api.Class.LiveMessage.md line links only +``` + +## Tests + +`node/tests/commands.test.ts`, new tests of generated `cmdString`: +``` +APIConnectPlan :15 unknown: /_connect plan 1 L; allGroups and never: " resolve="; linkOwnerSig: " sig=" +APIGetChat :33 count=0; initial=1; around=5 count=0; after=5 count=100; before=5 count=10; content=image count=10 search=k +APIChatItemsRead :49 /_read chat items @2 3,4 +APIPrepareContact :55 with and without " domain=biz.simplex" +APIPrepareGroup :68 direct false: " direct=off domain=news.simplex"; direct true: both omitted +prepared chat connection :81 /_connect contact @2 incognito=on json ; /_connect group #3 contact=1 owner=abc; /_connect group #3 json ; /_connect contact 1 incognito=on 2 +member contact :98 /_create member contact #1 2; /_invite member contact @2 with and without json +network config :109 /network; /_network +APIGetChatItemInfo, added later /_get item info @2 5 +APIGetUserServers, added later /_servers 1 +``` +`node/tests/api.unit.test.ts`, `core.chatSendCmd` mocked. + +New helpers: +``` +sentCommands :14 strings passed to the mocked core.chatSendCmd, in call order +nextResponse :18 the response of later calls of the mocked core.chatSendCmd +``` +Present tests, changed: `jest.mocked(core.chatSendCmd).mock.calls.map(([, cmd]) => cmd)` is replaced by `sentCommands()`: +``` +apiUpdateChatItem updates and finalizes a live message :37 +apiSetUserDomain sets and removes the name :53 +``` +New tests: +``` +line call sent string result +:65 apiReceiveFile(3, "./f.txt") /freceive 3 approved_relays=on ./f.txt chatItem +:72 apiGetChat, count 0 /_get chat #1 count=0 chat + apiGetChat, around pagination /_get chat @2 around=5 count=0 chat +:80 apiChatItemsRead chatInfo +:86 apiContactInfo response +:92 apiPrepareContact, apiPrepareGroup chat +:100 apiConnectPreparedContact startedConnectionToContact; contactAlreadyExists +:109 apiConnectPreparedGroup, apiConnectContactViaAddress response +:118 apiConnectPreparedLink sentConfirmation; sentInvitation; contactAlreadyExists + apiConnect Invitation; Contact; error "contact already exists" +:134 apiConnectPlan [plan, link, planSimplexName, otherSimplexName] +:143 apiGetNetworkConfig networkConfig + apiSetNetworkConfig undefined, on cmdOk +:151 apiCreateMemberContact /_create member contact #1 2 contact + apiSendMemberContactInvitation(2, "hi") /_invite member contact @2 json {"type":"text","text":"hi"} contact +added apiGetChatItemInfo(Direct, 2, 5) /_get item info @2 5 response +added apiReceiveFile(3) /freceive 3 approved_relays=on chatItem + apiReceiveFile(3, undefined, false) /freceive 3 chatItem + apiReceiveFile(3, "./f.txt", false) /freceive 3 ./f.txt chatItem +added apiGetUserServers(1) /_servers 1 userServers +``` +- Present test, unchanged: "stops the chat before closing the store" (`:191`): `send /_stop` is expected before `closeStore`. + +`node/tests/api.test.ts`, new tests with a local SMP server: +``` +:164 should connect via prepared contact and read messages + plan of alice's address: contactAddress, ok, with link data; planSimplexName undefined + apiPrepareContact: direct chat; apiConnectPreparedContact: startedConnectionToContact + after alice accepts the contact request and both clients receive contactConnected: apiGetChat initial: "hello" rcvNew; apiChatItemsRead: direct chat info + apiGetChat around the item: rcvRead; apiContactInfo: contact, customUserProfile undefined + apiGetChatItemInfo of the item: the same item, fileXftpServers empty + apiConnectPreparedLink with alice's invitation: sentConfirmation, then both connected +:205 should get and set network config + smpProxyFallback allowProtected and logTLSErrors true are set; the result of the next apiGetNetworkConfig is equal to the set configuration +added should get user servers + after /smp with the local SMP server: in the group without operator, smpServers is that server, enabled +``` +- Present test, unchanged: "should create member contact and send invitation" (`:81`): both member contact methods are called against the core, with a string message (`:149`). + +`packages/simplex-chat-python/tests/test_codegen.py`, new tests of generated `_cmd_string`: +``` +test_chat_pagination_cmd_string count=0; initial=1; around=5 count=0; after=5 count=100; before=5 count=10 +test_api_get_chat_cmd_string /_get chat @2 content=image count=10 search=k; /_get chat @2 initial=1 +test_api_chat_items_read_cmd_string /_read chat items @2 3,4 +test_api_connect_plan_cmd_string_omits_default_resolve_mode PlanResolveMode: unknown: ""; allGroups and never: " resolve="; /_connect plan 1 L; /_connect plan 1 L resolve=never +test_api_connect_prepared_group_cmd_string contact=1 owner=abc; /_connect group #3 contact=1 owner=abc; /_connect group #3 incognito=on json +test_member_contact_cmd_string /_create member contact #1 2; /_invite member contact @2 +test_api_get_network_config_cmd_string /network +test_api_get_chat_item_info_cmd_string /_get item info @2 5 +test_api_get_user_servers_cmd_string /_servers 1 +``` + +`tests/ChatTests/Names.hs`, new test: +``` +testPrepareChannelWithName :328 /_prepare group ... direct=off domain=team.simplex (:343), parsed at Library/Commands.hs:6561 + plan of the prepared channel: "known prepared group #team", "SimpleX name: #team (verified)" +``` + +Haskell, present tests, unchanged: +- "Bot API docs" (`tests/APIDocs.hs`): coverage of commands, their field names, responses, events and types, and of the responses of each command (`:73-151`); each generated file is equal to its regenerated text (`:153-157`). +- Tests of the changed lines of `Library/Commands.hs`, in `tests/ChatTests/Names.hs`: +``` +testConnectByName :34 /c @alice.simplex: connectContactViaName, APIPrepareContact with the name +testConnectByChannelName :297 /c #team.simplex: joinChannelViaRelays, APIPrepareGroup with the name +testConnectByNameBusinessAndChannel :412 /_prepare contact ... domain=biz.simplex (:431): business chat with the domain +``` +- `enableNamesRole` (`tests/ChatClient.hs:488`): `APIGetUserServers 1` is executed, and its SMP servers are set with the names role. diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 3731b112a1..cb5c34061a 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -490,7 +490,7 @@ data ChatCommand | APIDeleteGroupLink {groupId :: GroupId} | APIGetGroupLink {groupId :: GroupId} | APIAddGroupShortLink GroupId - | APICreateMemberContact GroupId GroupMemberId + | APICreateMemberContact {groupId :: GroupId, groupMemberId :: GroupMemberId} | APISendMemberContactInvitation {contactId :: ContactId, msgContent_ :: Maybe MsgContent} | APIAcceptMemberContact ContactId | GetUserProtoServers AProtocolType @@ -505,7 +505,7 @@ data ChatCommand | APIGetServerOperators | APISetServerOperators (NonEmpty ServerOperator) | SetServerOperators (NonEmpty ServerOperatorRoles) - | APIGetUserServers UserId + | APIGetUserServers {userId :: UserId} | APISetUserServers UserId (NonEmpty UpdatedUserOperatorServers) | APIValidateServers UserId [UpdatedUserOperatorServers] -- response is CRUserServersValidation | APIGetUsageConditions @@ -518,7 +518,7 @@ data ChatCommand | APISetChatTTL {userId :: UserId, chatRef :: ChatRef, seconds :: Maybe Int64} | SetChatTTL ChatName (Maybe Int64) | GetChatTTL ChatName - | APISetNetworkConfig NetworkConfig + | APISetNetworkConfig {networkConfig :: NetworkConfig} | APIGetNetworkConfig | SetNetworkConfig SimpleNetCfg | APISetNetworkInfo UserNetworkInfo @@ -526,7 +526,7 @@ data ChatCommand | ReconnectServer UserId SMPServer | APISetChatSettings {chatRef :: ChatRef, chatSettings :: ChatSettings} | APISetMemberSettings GroupId GroupMemberId GroupMemberSettings - | APIContactInfo ContactId + | APIContactInfo {contactId :: ContactId} | APIGroupInfo GroupId | APIGetUpdatedGroupLinkData {groupId :: GroupId} | APIGroupMemberInfo GroupId GroupMemberId @@ -571,8 +571,8 @@ data ChatCommand | APISetConnectionIncognito Int64 IncognitoEnabled | APIChangeConnectionUser Int64 UserId -- new user id to switch connection to | APIConnectPlan {userId :: UserId, connectTarget :: Maybe AConnectTarget, resolveMode :: PlanResolveMode, linkOwnerSig :: Maybe LinkOwnerSig} -- Maybe AConnectTarget is used to report parsing failure as special error - | APIPrepareContact UserId ACreatedConnLink (Maybe SimplexDomain) ContactShortLinkData - | APIPrepareGroup UserId CreatedLinkContact DirectLink (Maybe SimplexDomain) GroupShortLinkData + | APIPrepareContact {userId :: UserId, connLink :: ACreatedConnLink, verifiedDomain :: Maybe (StrJSON "SimplexDomain" SimplexDomain), contactSLinkData :: ContactShortLinkData} + | APIPrepareGroup {userId :: UserId, connLinkContact :: CreatedLinkContact, direct :: DirectLink, verifiedDomain :: Maybe (StrJSON "SimplexDomain" SimplexDomain), groupSLinkData :: GroupShortLinkData} | APIChangePreparedContactUser ContactId UserId | APIChangePreparedGroupUser GroupId UserId | APIConnectPreparedContact {contactId :: ContactId, incognito :: IncognitoEnabled, msgContent_ :: Maybe MsgContent} @@ -581,7 +581,7 @@ data ChatCommand | Connect {incognito :: IncognitoEnabled, connTarget_ :: Maybe AConnectTarget} | APIVerifyContactDomain {contactId :: ContactId} | APIVerifyGroupDomain {groupId :: GroupId} - | APIConnectContactViaAddress UserId IncognitoEnabled ContactId + | APIConnectContactViaAddress {userId :: UserId, incognito :: IncognitoEnabled, contactId :: ContactId} | ConnectSimplex IncognitoEnabled -- UserId (not used in UI) | DeleteContact ContactName ChatDeleteMode | ClearContact ContactName @@ -1147,11 +1147,11 @@ data SendRef deriving (Eq, Show) data ChatPagination - = CPLast Int - | CPAfter ChatItemId Int - | CPBefore ChatItemId Int - | CPAround ChatItemId Int - | CPInitial Int + = CPLast {count :: Int} + | CPAfter {chatItemId :: ChatItemId, count :: Int} + | CPBefore {chatItemId :: ChatItemId, count :: Int} + | CPAround {chatItemId :: ChatItemId, count :: Int} + | CPInitial {count :: Int} deriving (Show) data PaginationByTime diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index e073440488..3f2d56568b 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -2221,7 +2221,7 @@ processChatCommand cxt nm = \case groupPreferences = maybe defaultBusinessGroupPrefs businessGroupPrefs preferences groupProfile = businessGroupProfile profile groupPreferences chatLayout gVar <- asks random - (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing verifiedDomain + (gInfo, hostMember_) <- withStore $ \db -> createPreparedGroup db gVar cxt user groupProfile True ccLink welcomeSharedMsgId False GRMember Nothing (unStrJSON <$> verifiedDomain) hostMember <- maybe (throwCmdError "no host member") pure hostMember_ void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing Nothing (Just epochStart) let cd = CDGroupRcv gInfo Nothing hostMember @@ -2250,7 +2250,7 @@ processChatCommand cxt nm = \case APIPrepareGroup userId ccLink direct verifiedDomain groupSLinkData -> withUserId userId $ \user -> do let GroupShortLinkData {groupProfile = GroupProfile {description}} = groupSLinkData welcomeSharedMsgId <- forM description $ \_ -> getSharedMsgId - (gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId verifiedDomain + (gInfo, hostMember_) <- preparedGroupFromLink user ccLink direct groupSLinkData welcomeSharedMsgId (unStrJSON <$> verifiedDomain) void $ createChatItem user (CDGroupSnd gInfo Nothing) False CIChatBanner Nothing Nothing (Just epochStart) let cd = maybe (CDChannelRcv gInfo Nothing) (CDGroupRcv gInfo Nothing) hostMember_ cInfo = GroupChat gInfo Nothing @@ -4714,7 +4714,7 @@ processChatCommand cxt nm = \case _ -> processChatCommand cxt nm $ APIConnect userId incognito $ Just ccLink | otherwise = pure $ CRConnectionPlan user ccLink planSimplexName otherSimplexName plan where - vName = nameDomain <$> planSimplexName + vName = StrJSON . nameDomain <$> planSimplexName joinChannelViaRelays :: CreatedLinkContact -> GroupShortLinkData -> CM ChatResponse joinChannelViaRelays ccl gld = do GroupInfo {groupId} <- prepareChannelGroup @@ -6965,8 +6965,8 @@ chatCommandP = "/_contacts " *> (APIListContacts <$> A.decimal), "/contacts" $> ListContacts, "/_connect plan " *> (APIConnectPlan <$> A.decimal <* A.space <*> ((Just <$> strP) <|> A.takeTill (== ' ') $> Nothing) <*> ((" resolve=" *> planResolveModeP) <|> pure PRMUnknown) <*> optional (" sig=" *> jsonP)), - "/_prepare contact " *> (APIPrepareContact <$> A.decimal <* A.space <*> connLinkP <*> optional (" domain=" *> strP) <* A.space <*> jsonP), - "/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP' <*> (" direct=" *> onOffP <|> pure True) <*> optional (" domain=" *> strP) <* A.space <*> jsonP), + "/_prepare contact " *> (APIPrepareContact <$> A.decimal <* A.space <*> connLinkP <*> optional (" domain=" *> (StrJSON <$> strP)) <* A.space <*> jsonP), + "/_prepare group " *> (APIPrepareGroup <$> A.decimal <* A.space <*> connLinkP' <*> (" direct=" *> onOffP <|> pure True) <*> optional (" domain=" *> (StrJSON <$> strP)) <* A.space <*> jsonP), "/_set contact user @" *> (APIChangePreparedContactUser <$> A.decimal <* A.space <*> A.decimal), "/_set group user #" *> (APIChangePreparedGroupUser <$> A.decimal <* A.space <*> A.decimal), "/_connect contact @" *> (APIConnectPreparedContact <$> A.decimal <*> incognitoOnOffP <*> optional (A.space *> msgContentP)), diff --git a/tests/ChatTests/Names.hs b/tests/ChatTests/Names.hs index 29a8c00c4f..13c1228b4e 100644 --- a/tests/ChatTests/Names.hs +++ b/tests/ChatTests/Names.hs @@ -26,6 +26,7 @@ chatNamesTests = do it "channel name is not verified just by joining via link" testChannelDomainLinkJoinUnverified it "verify channel name, fail on re-point, retain status on refresh" testChannelDomainVerify it "connect by channel name" testConnectByChannelName + it "channel name is verified when the channel is prepared with it" testPrepareChannelWithName it "connect by name resolving to channel (primary) and direct contact" testConnectByNameChannelAndContact it "connect by name resolving to direct contact (primary) and channel" testConnectByNameContactAndChannel it "connect by name resolving to business (primary) and channel" testConnectByNameBusinessAndChannel @@ -324,6 +325,29 @@ testConnectByChannelName ps = withSmpServerAndNames ps $ \reg -> where teamName = SimplexNameInfo NTPublicGroup (SimplexDomain TLDSimplex "team" []) +testPrepareChannelWithName :: HasCallStack => TestParams -> IO () +testPrepareChannelWithName ps = withSmpServerAndNames ps $ \reg -> + withNewTestChat ps "alice" aliceProfile $ \alice -> + withNewTestChatOpts ps relayTestOpts "cath" cathProfile $ \cath -> + withNewTestChat ps "bob" bobProfile $ \bob -> do + mapM_ enableNamesRole [alice, cath, bob] + (shortLink, fullLink) <- prepareChannel1Relay "team" alice cath + registerName reg teamName (channelNameRecord "team.simplex" (T.pack shortLink)) + alice ##> "/public group access #team domain=team.simplex" + alice <## "updated public group access: domain=team.simplex" + cath <## "alice updated group #team: (signed)" + cath <## "updated public group access: domain=team.simplex" + bob ##> ("/_connect plan 1 " <> shortLink) + bob <## "group link: ok to connect via relays" + groupSLinkData <- getTermLine bob + bob ##> ("/_prepare group 1 " <> fullLink <> " " <> shortLink <> " direct=off domain=team.simplex " <> groupSLinkData) + bob <## "#team: group is prepared" + bob ##> ("/_connect plan 1 " <> shortLink) + bob <## "group link: known prepared group #team" + bob <## "SimpleX name: #team (verified)" + where + teamName = SimplexNameInfo NTPublicGroup (SimplexDomain TLDSimplex "team" []) + -- The bare name "team.simplex" resolves to both a channel and a direct contact. The channel is tried -- first and succeeds (bob has joined #team), so it is the primary (planSimplexName); otherSimplexName -- is the direct contact @team.simplex, shown as "You can also connect to @team.simplex in direct chat".