simplex-chat 0.3.0 → 6.5.0-beta.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +67 -22
  2. package/binding.gyp +35 -0
  3. package/dist/api.d.ts +371 -0
  4. package/dist/api.js +774 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/bot.d.ts +31 -0
  7. package/dist/bot.js +189 -0
  8. package/dist/bot.js.map +1 -0
  9. package/dist/core.d.ts +128 -0
  10. package/dist/core.js +121 -0
  11. package/dist/core.js.map +1 -0
  12. package/dist/index.d.ts +4 -2
  13. package/dist/index.js +5 -4
  14. package/dist/index.js.map +1 -1
  15. package/dist/simplex.d.ts +10 -0
  16. package/dist/simplex.js +1 -0
  17. package/dist/util.d.ts +15 -0
  18. package/dist/util.js +91 -0
  19. package/dist/util.js.map +1 -0
  20. package/package.json +32 -48
  21. package/src/api.ts +834 -0
  22. package/src/bot.ts +216 -0
  23. package/src/core.ts +210 -0
  24. package/src/download-libs.js +224 -0
  25. package/src/index.ts +22 -0
  26. package/src/simplex.d.ts +10 -0
  27. package/src/simplex.js +1 -0
  28. package/src/util.ts +92 -0
  29. package/dist/client.d.ts +0 -61
  30. package/dist/client.js +0 -271
  31. package/dist/client.js.map +0 -1
  32. package/dist/command.d.ts +0 -408
  33. package/dist/command.js +0 -184
  34. package/dist/command.js.map +0 -1
  35. package/dist/index-web.d.ts +0 -1
  36. package/dist/index-web.js +0 -6
  37. package/dist/index-web.js.map +0 -1
  38. package/dist/index.bundle.js +0 -11
  39. package/dist/queue.d.ts +0 -24
  40. package/dist/queue.js +0 -77
  41. package/dist/queue.js.map +0 -1
  42. package/dist/response.d.ts +0 -754
  43. package/dist/response.js +0 -21
  44. package/dist/response.js.map +0 -1
  45. package/dist/test.html +0 -7
  46. package/dist/transport.d.ts +0 -54
  47. package/dist/transport.js +0 -131
  48. package/dist/transport.js.map +0 -1
  49. package/dist/websocket.d.ts +0 -8
  50. package/dist/websocket.js +0 -4
  51. package/dist/websocket.js.map +0 -1
package/src/api.ts ADDED
@@ -0,0 +1,834 @@
1
+ import {CC, CEvt, ChatEvent, ChatResponse, T} from "@simplex-chat/types"
2
+ import * as core from "./core"
3
+ import * as util from "./util"
4
+
5
+ export class ChatCommandError extends Error {
6
+ constructor(public message: string, public response: ChatResponse) {
7
+ super(message)
8
+ }
9
+ }
10
+
11
+ /**
12
+ * Connection request types.
13
+ * @enum {string}
14
+ */
15
+ export enum ConnReqType {
16
+ Invitation = "invitation",
17
+ Contact = "contact",
18
+ }
19
+
20
+ /**
21
+ * Bot address settings.
22
+ */
23
+ export interface BotAddressSettings {
24
+ /**
25
+ * Automatically accept contact requests.
26
+ * @default true
27
+ */
28
+ autoAccept?: boolean
29
+
30
+ /**
31
+ * Optional welcome message to show before connection to the users.
32
+ * @default undefined (no welcome message)
33
+ */
34
+ welcomeMessage?: T.MsgContent | string | undefined
35
+
36
+ /**
37
+ * Business contact address.
38
+ * For all requests business chats will be created where other participants can be added.
39
+ * @default false
40
+ */
41
+ businessAddress?: boolean
42
+ }
43
+
44
+ export const defaultBotAddressSettings: BotAddressSettings = {
45
+ autoAccept: true,
46
+ welcomeMessage: undefined,
47
+ businessAddress: false
48
+ }
49
+
50
+ export type EventSubscriberFunc<K extends CEvt.Tag> = (event: ChatEvent & {type: K}) => void | Promise<void>
51
+
52
+ export type EventSubscribers = {[K in CEvt.Tag]?: EventSubscriberFunc<K>}
53
+
54
+ interface EventSubscriber<K extends CEvt.Tag> {
55
+ subscriber: EventSubscriberFunc<K>
56
+ once: boolean
57
+ }
58
+
59
+ /**
60
+ * Main API class for interacting with the chat core library.
61
+ */
62
+ export class ChatApi {
63
+ private receiveEvents = false
64
+ private eventsLoop: Promise<void> | undefined = undefined
65
+ private subscribers: {[K in CEvt.Tag]?: EventSubscriber<K>[]} = {}
66
+ private receivers: EventSubscriberFunc<CEvt.Tag>[] = []
67
+
68
+ private constructor(protected ctrl_: bigint | undefined) {}
69
+
70
+ /**
71
+ * Initializes the ChatApi.
72
+ * @param {string} dbFilePrefix - File prefix for the database files.
73
+ * @param {string} [dbKey=""] - Database encryption key.
74
+ * @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode.
75
+ */
76
+ static async init(
77
+ dbFilePrefix: string,
78
+ dbKey: string = "",
79
+ confirm = core.MigrationConfirmation.YesUp
80
+ ): Promise<ChatApi> {
81
+ const ctrl = await core.chatMigrateInit(dbFilePrefix, dbKey, confirm)
82
+ return new ChatApi(ctrl)
83
+ }
84
+
85
+ /**
86
+ * Start chat controller. Must be called with the existing user profile.
87
+ */
88
+ async startChat(): Promise<void> {
89
+ this.receiveEvents = true
90
+ this.eventsLoop = this.runEventsLoop()
91
+ const r = await this.sendChatCmd(CC.StartChat.cmdString({mainApp: true, enableSndFiles: true}))
92
+ if (r.type !== "chatStarted" && r.type !== "chatRunning") {
93
+ throw new ChatCommandError("error starting chat", r)
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Stop chat controller.
99
+ * Must be called before closing the database.
100
+ * Usually doesn't need to be called in chat bots.
101
+ */
102
+ async stopChat(): Promise<void> {
103
+ const r = await this.sendChatCmd("/_stop")
104
+ if (r.type !== "chatStopped") throw new ChatCommandError("error starting chat", r)
105
+ this.receiveEvents = false
106
+ if (this.eventsLoop) await this.eventsLoop
107
+ this.eventsLoop = undefined
108
+ }
109
+
110
+ /**
111
+ * Close chat database.
112
+ * Usually doesn't need to be called in chat bots.
113
+ */
114
+ async close(): Promise<void> {
115
+ this.receiveEvents = false
116
+ if (this.eventsLoop) await this.eventsLoop
117
+ this.eventsLoop = undefined
118
+ await core.chatCloseStore(this.ctrl)
119
+ this.ctrl_ = undefined
120
+ }
121
+
122
+ private async runEventsLoop(): Promise<void> {
123
+ while (this.receiveEvents) {
124
+ try {
125
+ const event = await this.recvChatEvent()
126
+ if (!event) continue
127
+ const subs = this.subscribers[event.type]
128
+ if (subs) {
129
+ for (const {subscriber, once} of [...subs]) {
130
+ try {
131
+ const p = (subscriber as EventSubscriberFunc<typeof event.type>)(event)
132
+ if (p instanceof Promise) await p
133
+ } catch(e) {
134
+ console.log(`${event.type} event processing error`, e)
135
+ }
136
+ if (once) this.off(event.type, subscriber as EventSubscriberFunc<typeof event.type>)
137
+ }
138
+ }
139
+ for (const r of [...this.receivers]) {
140
+ try {
141
+ const p = r(event)
142
+ if (p instanceof Promise) await p
143
+ } catch(e) {
144
+ console.log(`${event.type} event processing error`, e)
145
+ }
146
+ }
147
+ } catch(err) {
148
+ const e = err as core.ChatAPIError
149
+ if ("chatError" in e) {
150
+ console.log("Chat error", e.chatError)
151
+ } else {
152
+ console.log("Invalid event", e)
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Subscribe multiple event handlers at once.
160
+ * @param subscribers - An object mapping event types (CEvt.Tag) to their subscriber functions.
161
+ * @throws {Error} If the same function is subscribed to event.
162
+ */
163
+ on<K extends CEvt.Tag>(subscribers: EventSubscribers): void
164
+
165
+ /**
166
+ * Subscribe a handler to a specific event.
167
+ * @param {CEvt.Tag} event - The event type to subscribe to.
168
+ * @param subscriber - The subscriber function for the event.
169
+ * @throws {Error} If the same function is subscribed to event.
170
+ */
171
+ on<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>): void
172
+ on<K extends CEvt.Tag>(events: K | EventSubscribers, subscriber?: EventSubscriberFunc<K>): void {
173
+ if (typeof events === "string" && subscriber) {
174
+ this.on_(events, subscriber)
175
+ } else {
176
+ const eventEntries = Object.entries(events) as [CEvt.Tag, EventSubscriberFunc<CEvt.Tag> | undefined][]
177
+ for (const [event, subscriber] of eventEntries) {
178
+ if (subscriber) this.on_(event, subscriber)
179
+ }
180
+ }
181
+ }
182
+
183
+ private on_<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>, once: boolean = false): void {
184
+ const subs: EventSubscriber<K>[] = this.subscribers[event] || (this.subscribers[event] = [])
185
+ if (subs.some(s => s.subscriber === subscriber)) throw Error(`this function is already subscribed to ${event}`)
186
+ subs.push({subscriber, once})
187
+ }
188
+
189
+ /**
190
+ * Subscribe a handler to any event.
191
+ * @param receiver - The receiver function for any event.
192
+ * @throws {Error} If the same function is subscribed to event.
193
+ */
194
+ onAny(receiver: EventSubscriberFunc<CEvt.Tag>): void {
195
+ if (this.receivers.some(s => s === receiver)) throw Error("this function is already subscribed")
196
+ this.receivers.push(receiver)
197
+ }
198
+
199
+ /**
200
+ * Subscribe a handler to a specific event to be delivered one time.
201
+ * @param {CEvt.Tag} event - The event type to subscribe to.
202
+ * @param subscriber - The subscriber function for the event.
203
+ * @throws {Error} If the same function is subscribed to event.
204
+ */
205
+ once<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>): void {
206
+ this.on_(event, subscriber, true)
207
+ }
208
+
209
+ /**
210
+ * Waits for specific event, with an optional predicate.
211
+ * Returns `undefined` on timeout if specified.
212
+ */
213
+ wait<K extends CEvt.Tag>(event: K): Promise<ChatEvent & {type: K}>
214
+ wait<K extends CEvt.Tag>(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined): Promise<ChatEvent & {type: K}>
215
+ wait<K extends CEvt.Tag>(event: K, timeout: number): Promise<ChatEvent & {type: K} | undefined>
216
+ wait<K extends CEvt.Tag>(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined, timeout: number): Promise<ChatEvent & {type: K} | undefined>
217
+ wait<K extends CEvt.Tag>(
218
+ event: K,
219
+ predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined | number = undefined, // number for timeout
220
+ timeout: number = 0 // milliseconds, default - indefinite
221
+ ): Promise<ChatEvent & {type: K} | undefined> {
222
+ if (typeof predicate === "number") {
223
+ timeout = predicate
224
+ predicate = undefined
225
+ }
226
+ return new Promise((resolve, reject) => {
227
+ let done = false
228
+ const cleanup = () => {
229
+ done = true
230
+ this.off(event, subscriber)
231
+ }
232
+ const subscriber: EventSubscriberFunc<K> = async (evt: ChatEvent & {type: K}) => {
233
+ if (done) return
234
+ if (predicate) {
235
+ try { if (!predicate(evt)) return }
236
+ catch(e) { cleanup(); reject(e); return }
237
+ }
238
+ cleanup()
239
+ resolve(evt)
240
+ }
241
+ this.on(event, subscriber)
242
+ if (timeout > 0) {
243
+ setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout)
244
+ }
245
+ })
246
+ }
247
+
248
+ /**
249
+ * Unsubscribe all or a specific handler from a specific event.
250
+ * @param {CEvt.Tag} event - The event type to unsubscribe from.
251
+ * @param subscriber - An optional subscriber function for the event.
252
+ */
253
+ off<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K> | undefined = undefined): void {
254
+ if (subscriber) {
255
+ const subs = this.subscribers[event]
256
+ if (subs) {
257
+ const i = subs.findIndex(s => s.subscriber === subscriber)
258
+ if (i >= 0) subs.splice(i, 1)
259
+ }
260
+ } else {
261
+ delete this.subscribers[event]
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Unsubscribe all or a specific handler from any events.
267
+ * @param receiver - An optional subscriber function for the event.
268
+ */
269
+ offAny(receiver: EventSubscriberFunc<CEvt.Tag> | undefined = undefined): void {
270
+ if (receiver) {
271
+ const i = this.receivers.findIndex(r => r === receiver)
272
+ if (i >= 0) this.receivers.splice(i, 1)
273
+ } else {
274
+ this.receivers = []
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Chat controller is initialized
280
+ */
281
+ get initialized(): boolean {
282
+ return typeof this.ctrl_ === "bigint"
283
+ }
284
+
285
+ /**
286
+ * Chat controller is started
287
+ */
288
+ get started(): boolean {
289
+ return this.receiveEvents && this.eventsLoop !== undefined
290
+ }
291
+
292
+ /**
293
+ * Chat controller reference
294
+ */
295
+ get ctrl(): bigint {
296
+ if (typeof this.ctrl_ === "bigint") return this.ctrl_
297
+ else throw Error("chat api controller not initialized")
298
+ }
299
+
300
+ async sendChatCmd(cmd: string): Promise<ChatResponse> {
301
+ return await core.chatSendCmd(this.ctrl, cmd)
302
+ }
303
+
304
+ async recvChatEvent(wait: number = 5_000_000): Promise<ChatEvent | undefined> {
305
+ return await core.chatRecvMsgWait(this.ctrl, wait)
306
+ }
307
+
308
+ /**
309
+ * Create bot address.
310
+ * Network usage: interactive.
311
+ */
312
+ async apiCreateUserAddress(userId: number): Promise<T.CreatedConnLink> {
313
+ const r = await this.sendChatCmd(CC.APICreateMyAddress.cmdString({userId}))
314
+ if (r.type === "userContactLinkCreated") return r.connLinkContact
315
+ throw new ChatCommandError("error creating user address", r)
316
+ }
317
+
318
+ /**
319
+ * Deletes a user address.
320
+ * Network usage: background.
321
+ */
322
+ async apiDeleteUserAddress(userId: number): Promise<void> {
323
+ const r = await this.sendChatCmd(CC.APIDeleteMyAddress.cmdString({userId}))
324
+ if (r.type === "userContactLinkDeleted") return
325
+ throw new ChatCommandError("error deleting user address", r)
326
+ }
327
+
328
+ /**
329
+ * Get bot address and settings.
330
+ * Network usage: no.
331
+ */
332
+ async apiGetUserAddress(userId: number): Promise<T.UserContactLink | undefined> {
333
+ try {
334
+ const r = await this.sendChatCmd(CC.APIShowMyAddress.cmdString({userId}))
335
+ switch (r.type) {
336
+ case "userContactLink": return r.contactLink
337
+ default: throw new ChatCommandError("error loading user address", r)
338
+ }
339
+ } catch (err) {
340
+ const e = err as any
341
+ if (e.chatError?.type === "errorStore" && e.chatError.storeError?.type === "userContactLinkNotFound") return undefined
342
+ throw e
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Add address to bot profile.
348
+ * Network usage: interactive.
349
+ */
350
+ async apiSetProfileAddress(userId: number, enable: boolean): Promise<T.UserProfileUpdateSummary> {
351
+ const r = await this.sendChatCmd(CC.APISetProfileAddress.cmdString({userId, enable}))
352
+ switch (r.type) {
353
+ case "userProfileUpdated":
354
+ return r.updateSummary
355
+ default:
356
+ throw new ChatCommandError("error loading user address", r)
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Set bot address settings.
362
+ * Network usage: interactive.
363
+ */
364
+ async apiSetAddressSettings(userId: number, {autoAccept, welcomeMessage, businessAddress}: BotAddressSettings): Promise<void> {
365
+ const autoReply = welcomeMessage || defaultBotAddressSettings.welcomeMessage
366
+ const settings: T.AddressSettings = {
367
+ autoAccept: (autoAccept === undefined ? defaultBotAddressSettings.autoAccept : autoAccept) ? {acceptIncognito: false} : undefined,
368
+ autoReply: typeof autoReply === "string" ? {type: "text", text: autoReply} : autoReply,
369
+ businessAddress: businessAddress || defaultBotAddressSettings.businessAddress || false
370
+ }
371
+ const r = await this.sendChatCmd(CC.APISetAddressSettings.cmdString({userId, settings}))
372
+ if (r.type !== "userContactLinkUpdated") {
373
+ throw new ChatCommandError("error changing user contact address settings", r)
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Send messages.
379
+ * Network usage: background.
380
+ */
381
+ async apiSendMessages(chat: [T.ChatType, number] | T.ChatRef | T.ChatInfo, messages: T.ComposedMessage[], liveMessage = false): Promise<T.AChatItem[]> {
382
+ const sendRef = Array.isArray(chat)
383
+ ? {chatType: chat[0], chatId: chat[1]}
384
+ : "chatType" in chat
385
+ ? chat
386
+ : util.chatInfoRef(chat)
387
+ if (!sendRef) throw Error("apiSendMessages: can't send messages to this chat")
388
+ const r = await this.sendChatCmd(
389
+ CC.APISendMessages.cmdString({
390
+ sendRef,
391
+ composedMessages: messages,
392
+ liveMessage
393
+ })
394
+ )
395
+ if (r.type === "newChatItems") return r.chatItems
396
+ throw new ChatCommandError("unexpected response", r)
397
+ }
398
+
399
+ /**
400
+ * Send text message.
401
+ * Network usage: background.
402
+ */
403
+ async apiSendTextMessage(chat: [T.ChatType, number] | T.ChatRef | T.ChatInfo, text: string, inReplyTo?: number): Promise<T.AChatItem[]> {
404
+ return this.apiSendMessages(chat, [{msgContent: {type: "text", text}, mentions: {}, quotedItemId: inReplyTo}])
405
+ }
406
+
407
+ /**
408
+ * Send text message in reply to received message.
409
+ * Network usage: background.
410
+ */
411
+ async apiSendTextReply(chatItem: T.AChatItem, text: string): Promise<T.AChatItem[]> {
412
+ return this.apiSendTextMessage(chatItem.chatInfo, text, chatItem.chatItem.meta.itemId)
413
+ }
414
+
415
+ /**
416
+ * Update message.
417
+ * Network usage: background.
418
+ */
419
+ async apiUpdateChatItem(chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, liveMessage: false): Promise<T.ChatItem> {
420
+ const r = await this.sendChatCmd(
421
+ CC.APIUpdateChatItem.cmdString({
422
+ chatRef: {chatType, chatId},
423
+ chatItemId,
424
+ liveMessage,
425
+ updatedMessage: {msgContent, mentions: {}},
426
+ })
427
+ )
428
+ if (r.type === "chatItemUpdated") return r.chatItem.chatItem
429
+ throw new ChatCommandError("error updating chat item", r)
430
+ }
431
+
432
+ /**
433
+ * Delete message.
434
+ * Network usage: background.
435
+ */
436
+ async apiDeleteChatItems(
437
+ chatType: T.ChatType,
438
+ chatId: number,
439
+ chatItemIds: number[],
440
+ deleteMode: T.CIDeleteMode
441
+ ): Promise<T.ChatItemDeletion[]> {
442
+ const r = await this.sendChatCmd(CC.APIDeleteChatItem.cmdString({chatRef: {chatType, chatId}, chatItemIds, deleteMode}))
443
+ if (r.type === "chatItemsDeleted") return r.chatItemDeletions
444
+ throw new ChatCommandError("error deleting chat item", r)
445
+ }
446
+
447
+ /**
448
+ * Moderate message. Requires Moderator role (and higher than message author's).
449
+ * Network usage: background.
450
+ */
451
+ async apiDeleteMemberChatItem(groupId: number, chatItemIds: number[]): Promise<T.ChatItemDeletion[]> {
452
+ const r = await this.sendChatCmd(CC.APIDeleteMemberChatItem.cmdString({groupId, chatItemIds}))
453
+ if (r.type === "chatItemsDeleted") return r.chatItemDeletions
454
+ throw new ChatCommandError("error deleting member chat item", r)
455
+ }
456
+
457
+ /**
458
+ * Add/remove message reaction.
459
+ * Network usage: background.
460
+ */
461
+ async apiChatItemReaction(
462
+ chatType: T.ChatType,
463
+ chatId: number,
464
+ chatItemId: number,
465
+ add: boolean,
466
+ reaction: T.MsgReaction
467
+ ) {
468
+ const r = await this.sendChatCmd(CC.APIChatItemReaction.cmdString({chatRef: {chatType, chatId}, chatItemId, add, reaction}))
469
+ if (r.type === "chatItemsDeleted") return r.chatItemDeletions
470
+ throw new ChatCommandError("error setting item reaction", r)
471
+ }
472
+
473
+ /**
474
+ * Receive file.
475
+ * Network usage: no.
476
+ */
477
+ async apiReceiveFile(fileId: number): Promise<T.AChatItem> {
478
+ const r = await this.sendChatCmd(CC.ReceiveFile.cmdString({fileId, userApprovedRelays: true}))
479
+ if (r.type === "rcvFileAccepted") return r.chatItem
480
+ throw new ChatCommandError("error receiving file", r)
481
+ }
482
+
483
+ /**
484
+ * Cancel file.
485
+ * Network usage: background.
486
+ */
487
+ async apiCancelFile(fileId: number): Promise<void> {
488
+ const r = await this.sendChatCmd(CC.CancelFile.cmdString({fileId}))
489
+ if (r.type === "sndFileCancelled" || r.type === "rcvFileCancelled") return
490
+ throw new ChatCommandError("error canceling file", r)
491
+ }
492
+
493
+ /**
494
+ * Add contact to group. Requires bot to have Admin role.
495
+ * Network usage: interactive.
496
+ */
497
+ async apiAddMember(groupId: number, contactId: number, memberRole: T.GroupMemberRole): Promise<T.GroupMember> {
498
+ const r = await this.sendChatCmd(CC.APIAddMember.cmdString({groupId, contactId, memberRole}))
499
+ if (r.type === "sentGroupInvitation") return r.member
500
+ throw new ChatCommandError("error adding member", r)
501
+ }
502
+
503
+ /**
504
+ * Join group.
505
+ * Network usage: interactive.
506
+ */
507
+ async apiJoinGroup(groupId: number): Promise<T.GroupInfo> {
508
+ const r = await this.sendChatCmd(CC.APIJoinGroup.cmdString({groupId}))
509
+ if (r.type === "userAcceptedGroupSent") return r.groupInfo
510
+ throw new ChatCommandError("error joining group", r)
511
+ }
512
+
513
+ /**
514
+ * Accept group member. Requires Admin role.
515
+ * Network usage: background.
516
+ */
517
+ async apiAcceptMember(groupId: number, groupMemberId: number, memberRole: T.GroupMemberRole): Promise<T.GroupMember> {
518
+ const r = await this.sendChatCmd(CC.APIAcceptMember.cmdString({groupId, groupMemberId, memberRole}))
519
+ if (r.type === "memberAccepted") return r.member
520
+ throw new ChatCommandError("error accepting member", r)
521
+ }
522
+
523
+ /**
524
+ * Set members role. Requires Admin role.
525
+ * Network usage: background.
526
+ */
527
+ async apiSetMembersRole(groupId: number, groupMemberIds: number[], memberRole: T.GroupMemberRole): Promise<void> {
528
+ const r = await this.sendChatCmd(CC.APIMembersRole.cmdString({groupId, groupMemberIds, memberRole}))
529
+ if (r.type === "membersRoleUser") return
530
+ throw new ChatCommandError("error setting members role", r)
531
+ }
532
+
533
+ /**
534
+ * Block members. Requires Moderator role.
535
+ * Network usage: background.
536
+ */
537
+ async apiBlockMembersForAll(groupId: number, groupMemberIds: number[], blocked: boolean): Promise<void> {
538
+ const r = await this.sendChatCmd(CC.APIBlockMembersForAll.cmdString({groupId, groupMemberIds, blocked}))
539
+ if (r.type === "membersBlockedForAllUser") return
540
+ throw new ChatCommandError("error blocking members", r)
541
+ }
542
+
543
+ /**
544
+ * Remove members. Requires Admin role.
545
+ * Network usage: background.
546
+ */
547
+ async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise<T.GroupMember[]> {
548
+ const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages}))
549
+ if (r.type === "userDeletedMembers") return r.members
550
+ throw new ChatCommandError("error removing member", r)
551
+ }
552
+
553
+ /**
554
+ * Leave group.
555
+ * Network usage: background.
556
+ */
557
+ async apiLeaveGroup(groupId: number): Promise<T.GroupInfo> {
558
+ const r = await this.sendChatCmd(CC.APILeaveGroup.cmdString({groupId}))
559
+ if (r.type === "leftMemberUser") return r.groupInfo
560
+ throw new ChatCommandError("error leaving group", r)
561
+ }
562
+
563
+ /**
564
+ * Get group members.
565
+ * Network usage: no.
566
+ */
567
+ async apiListMembers(groupId: number): Promise<T.GroupMember[]> {
568
+ const r = await this.sendChatCmd(CC.APIListMembers.cmdString({groupId}))
569
+ if (r.type === "groupMembers") return r.group.members
570
+ throw new ChatCommandError("error getting group members", r)
571
+ }
572
+
573
+ /**
574
+ * Create group.
575
+ * Network usage: no.
576
+ */
577
+ async apiNewGroup(userId: number, groupProfile: T.GroupProfile): Promise<T.GroupInfo> {
578
+ const r = await this.sendChatCmd(CC.APINewGroup.cmdString({userId, groupProfile, incognito: false}))
579
+ if (r.type === "groupCreated") return r.groupInfo
580
+ throw new ChatCommandError("error creating group", r)
581
+ }
582
+
583
+ /**
584
+ * Update group profile.
585
+ * Network usage: background.
586
+ */
587
+ async apiUpdateGroupProfile(groupId: number, groupProfile: T.GroupProfile): Promise<T.GroupInfo> {
588
+ const r = await this.sendChatCmd(CC.APIUpdateGroupProfile.cmdString({groupId, groupProfile}))
589
+ if (r.type === "groupUpdated") return r.toGroup
590
+ throw new ChatCommandError("error updating group", r)
591
+ }
592
+
593
+ /**
594
+ * Create group link.
595
+ * Network usage: interactive.
596
+ */
597
+ async apiCreateGroupLink(groupId: number, memberRole: T.GroupMemberRole): Promise<string> {
598
+ const r = await this.sendChatCmd(CC.APICreateGroupLink.cmdString({groupId, memberRole}))
599
+ if (r.type === "groupLinkCreated") {
600
+ const link = r.groupLink.connLinkContact
601
+ return link.connShortLink || link.connFullLink
602
+ }
603
+ throw new ChatCommandError("error creating group link", r)
604
+ }
605
+
606
+ /**
607
+ * Set member role for group link.
608
+ * Network usage: no.
609
+ */
610
+ async apiSetGroupLinkMemberRole(groupId: number, memberRole: T.GroupMemberRole): Promise<void> {
611
+ const r = await this.sendChatCmd(CC.APIGroupLinkMemberRole.cmdString({groupId, memberRole}))
612
+ if (r.type !== "groupLink") throw new ChatCommandError("error setting group link member role", r)
613
+ }
614
+
615
+ /**
616
+ * Delete group link.
617
+ * Network usage: background.
618
+ */
619
+ async apiDeleteGroupLink(groupId: number): Promise<void> {
620
+ const r = await this.sendChatCmd(CC.APIDeleteGroupLink.cmdString({groupId}))
621
+ if (r.type !== "groupLinkDeleted") throw new ChatCommandError("error deleting group link", r)
622
+ }
623
+
624
+ /**
625
+ * Get group link.
626
+ * Network usage: no.
627
+ */
628
+ async apiGetGroupLink(groupId: number): Promise<T.GroupLink> {
629
+ const r = await this.sendChatCmd(CC.APIGetGroupLink.cmdString({groupId}))
630
+ if (r.type === "groupLink") return r.groupLink
631
+ throw new ChatCommandError("error getting group link", r)
632
+ }
633
+
634
+ async apiGetGroupLinkStr(groupId: number): Promise<string> {
635
+ const link = (await this.apiGetGroupLink(groupId)).connLinkContact
636
+ return link.connShortLink || link.connFullLink
637
+ }
638
+
639
+ /**
640
+ * Create 1-time invitation link.
641
+ * Network usage: interactive.
642
+ */
643
+ async apiCreateLink(userId: number): Promise<string> {
644
+ const r = await this.sendChatCmd(CC.APIAddContact.cmdString({userId, incognito: false}))
645
+ if (r.type === "invitation") {
646
+ const link = r.connLinkInvitation
647
+ return link.connShortLink || link.connFullLink
648
+ }
649
+ throw new ChatCommandError("error creating link", r)
650
+ }
651
+
652
+ /**
653
+ * Determine SimpleX link type and if the bot is already connected via this link.
654
+ * Network usage: interactive.
655
+ */
656
+ async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> {
657
+ const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectionLink}))
658
+ if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink]
659
+ throw new ChatCommandError("error getting connect plan", r)
660
+ }
661
+
662
+ /**
663
+ * Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link
664
+ * Network usage: interactive.
665
+ */
666
+ async apiConnect(userId: number, incognito: boolean, preparedLink?: T.CreatedConnLink): Promise<ConnReqType> {
667
+ const r = await this.sendChatCmd(CC.APIConnect.cmdString({userId, incognito, preparedLink_: preparedLink}))
668
+ return this.handleConnectResult(r)
669
+ }
670
+
671
+ /**
672
+ * Connect via SimpleX link as string in the active user profile.
673
+ * Network usage: interactive.
674
+ */
675
+ async apiConnectActiveUser(connLink: string): Promise<ConnReqType> {
676
+ const r = await this.sendChatCmd(CC.Connect.cmdString({incognito: false, connLink_: connLink}))
677
+ return this.handleConnectResult(r)
678
+ }
679
+
680
+ private handleConnectResult(r: ChatResponse): ConnReqType {
681
+ switch (r.type) {
682
+ case "sentConfirmation":
683
+ return ConnReqType.Invitation
684
+ case "sentInvitation":
685
+ return ConnReqType.Contact
686
+ case "contactAlreadyExists":
687
+ throw new ChatCommandError("contact already exists", r)
688
+ default:
689
+ throw new ChatCommandError("connection error", r)
690
+ }
691
+ }
692
+
693
+ /**
694
+ * Accept contact request.
695
+ * Network usage: interactive.
696
+ */
697
+ async apiAcceptContactRequest(contactReqId: number): Promise<T.Contact> {
698
+ const r = await this.sendChatCmd(CC.APIAcceptContact.cmdString({contactReqId}))
699
+ if (r.type === "acceptingContactRequest") return r.contact
700
+ throw new ChatCommandError("error accepting contact request", r)
701
+ }
702
+
703
+ /**
704
+ * Reject contact request. The user who sent the request is **not notified**.
705
+ * Network usage: no.
706
+ */
707
+ async apiRejectContactRequest(contactReqId: number): Promise<void> {
708
+ const r = await this.sendChatCmd(CC.APIRejectContact.cmdString({contactReqId}))
709
+ if (r.type === "contactRequestRejected") return
710
+ throw new ChatCommandError("error rejecting contact request", r)
711
+ }
712
+
713
+ /**
714
+ * Get contacts.
715
+ * Network usage: no.
716
+ */
717
+ async apiListContacts(userId: number): Promise<T.Contact[]> {
718
+ const r = await this.sendChatCmd(CC.APIListContacts.cmdString({userId}))
719
+ if (r.type === "contactsList") return r.contacts
720
+ throw new ChatCommandError("error listing contacts", r)
721
+ }
722
+
723
+ /**
724
+ * Get groups.
725
+ * Network usage: no.
726
+ */
727
+ async apiListGroups(userId: number, contactId?: number, search?: string): Promise<T.GroupInfo[]> {
728
+ const r = await this.sendChatCmd(CC.APIListGroups.cmdString({userId, contactId_: contactId, search}))
729
+ if (r.type === "groupsList") return r.groups
730
+ throw new ChatCommandError("error listing groups", r)
731
+ }
732
+
733
+ /**
734
+ * Delete chat.
735
+ * Network usage: background.
736
+ */
737
+ async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}): Promise<void> {
738
+ const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode}))
739
+ switch (chatType) {
740
+ case T.ChatType.Direct:
741
+ if (r.type === "contactDeleted") return
742
+ break
743
+ case T.ChatType.Group:
744
+ if (r.type === "groupDeletedUser") return
745
+ break
746
+ }
747
+ throw new ChatCommandError("error deleting chat", r)
748
+ }
749
+
750
+ /**
751
+ * Get active user profile
752
+ * Network usage: no.
753
+ */
754
+ async apiGetActiveUser(): Promise<T.User | undefined> {
755
+ try {
756
+ const r = await this.sendChatCmd(CC.ShowActiveUser.cmdString({}))
757
+ switch (r.type) {
758
+ case "activeUser":
759
+ return r.user
760
+ default:
761
+ throw new ChatCommandError("unexpected response", r)
762
+ }
763
+ } catch (err) {
764
+ const e = err as core.ChatAPIError
765
+ if (e.chatError?.type === "error" && e.chatError.errorType.type === "noActiveUser") return undefined
766
+ throw err
767
+ }
768
+ }
769
+
770
+ /**
771
+ * Create new user profile
772
+ * Network usage: no.
773
+ */
774
+ async apiCreateActiveUser(profile?: T.Profile): Promise<T.User> {
775
+ const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false}}))
776
+ if (r.type === "activeUser") return r.user
777
+ throw new ChatCommandError("unexpected response", r)
778
+ }
779
+
780
+ /**
781
+ * Get all user profiles
782
+ * Network usage: no.
783
+ */
784
+ async apiListUsers(): Promise<T.UserInfo[]> {
785
+ const r = await this.sendChatCmd(CC.ListUsers.cmdString({}))
786
+ if (r.type === "usersList") return r.users
787
+ throw new ChatCommandError("error listing users", r)
788
+ }
789
+
790
+ /**
791
+ * Set active user profile
792
+ * Network usage: no.
793
+ */
794
+ async apiSetActiveUser(userId: number, viewPwd?: string): Promise<T.User> {
795
+ const r = await this.sendChatCmd(CC.APISetActiveUser.cmdString({userId, viewPwd}))
796
+ if (r.type === "activeUser") return r.user
797
+ throw new ChatCommandError("error setting active user", r)
798
+ }
799
+
800
+ /**
801
+ * Delete user profile.
802
+ * Network usage: background.
803
+ */
804
+ async apiDeleteUser(userId: number, delSMPQueues: boolean, viewPwd?: string): Promise<void> {
805
+ const r = await this.sendChatCmd(CC.APIDeleteUser.cmdString({userId, delSMPQueues, viewPwd}))
806
+ if (r.type === "cmdOk") return
807
+ throw new ChatCommandError("error deleting user", r)
808
+ }
809
+
810
+ /**
811
+ * Update user profile.
812
+ * Network usage: background.
813
+ */
814
+ async apiUpdateProfile(userId: number, profile: T.Profile): Promise<T.UserProfileUpdateSummary | undefined> {
815
+ const r = await this.sendChatCmd(CC.APIUpdateProfile.cmdString({userId, profile}))
816
+ switch (r.type) {
817
+ case "userProfileNoChange":
818
+ return undefined
819
+ case "userProfileUpdated":
820
+ return r.updateSummary
821
+ default:
822
+ throw new ChatCommandError("error updating profile", r)
823
+ }
824
+ }
825
+
826
+ /**
827
+ * Configure chat preference overrides for the contact.
828
+ * Network usage: background.
829
+ */
830
+ async apiSetContactPrefs(contactId: number, preferences: T.Preferences): Promise<void> {
831
+ const r = await this.sendChatCmd(CC.APISetContactPrefs.cmdString({contactId, preferences}))
832
+ if (r.type !== "contactPrefsUpdated") throw new ChatCommandError("error setting contact prefs", r)
833
+ }
834
+ }