bots, nodejs: document chat, connection, member contact and network commands, typed commands in library (#7808)

* bots, nodejs: document chat, connection, member contact and network commands, typed commands in library

* bots, nodejs: restore apiConnect, wrap domain once, test all connect responses

* nodejs: JSDoc for new methods

* docs: plan for bot API commands and typed Node library methods

* tests: channel prepared with a domain has a verified SimpleX name

* docs: apply review fixes to the plan for bot API commands

* docs: apply second review pass to the plan for bot API commands

* docs: refer the bot API plan to the rewritten test commit

* docs: record decisions in the plan for bot API commands

* docs: plan tests of the generated Python command strings

* python: test the generated strings of the documented commands

* docs: plan the message information command for bots

* bots, nodejs: message information with the file servers for bots

* docs: plan the relay approval option of receiving files in Node

* nodejs: receive files without approving unknown relays

* docs: plan the user servers command for bots

* bots, nodejs: user servers and relays by operator for bots

* nodejs: document the message information method

* docs: plan the file upload progress event for bots

* bots: file upload progress event for bots

* bots: drop the file upload progress event

* docs: drop the upload progress event plan

* nodejs: document user servers and relay approval of received files
This commit is contained in:
Evgeny @ SimpleX Chat authored and GitHub committed 2026-10-31 22:30:16 +00:00
1 parent d1b3da8410
commit 99b91696f7
34 files changed
+3716 -204

No files matched your search

@@ -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.
@@ -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"]
@@ -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
@@ -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"