@shhhum/simplex-chat 0.0.0-stage → 7.1.0-beta.6.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 (105) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +129 -2
  3. package/dist/api.d.ts +439 -0
  4. package/dist/api.js +911 -0
  5. package/dist/api.js.map +1 -0
  6. package/dist/bot.d.ts +34 -0
  7. package/dist/bot.js +194 -0
  8. package/dist/bot.js.map +1 -0
  9. package/dist/cli.d.ts +4 -0
  10. package/dist/cli.js +46 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/core.d.ts +138 -0
  13. package/dist/core.js +144 -0
  14. package/dist/core.js.map +1 -0
  15. package/dist/index.d.ts +19 -0
  16. package/dist/index.js +23 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/libs.d.ts +12 -0
  19. package/dist/libs.js +210 -0
  20. package/dist/libs.js.map +1 -0
  21. package/dist/release.d.ts +5 -0
  22. package/dist/release.js +20 -0
  23. package/dist/release.js.map +1 -0
  24. package/dist/simplex.d.ts +12 -0
  25. package/dist/simplex.js +22 -0
  26. package/dist/util.d.ts +15 -0
  27. package/dist/util.js +91 -0
  28. package/dist/util.js.map +1 -0
  29. package/docs/Namespace.api.md +33 -0
  30. package/docs/Namespace.bot.md +24 -0
  31. package/docs/Namespace.core.md +50 -0
  32. package/docs/Namespace.util.md +25 -0
  33. package/docs/README.md +12 -0
  34. package/docs/api.Class.ChatApi.md +1851 -0
  35. package/docs/api.Class.ChatCommandError.md +205 -0
  36. package/docs/api.Enumeration.ConnReqType.md +27 -0
  37. package/docs/api.Interface.BotAddressSettings.md +60 -0
  38. package/docs/api.TypeAlias.DbConfig.md +62 -0
  39. package/docs/api.TypeAlias.EventSubscriberFunc.md +31 -0
  40. package/docs/api.TypeAlias.EventSubscribers.md +11 -0
  41. package/docs/api.Variable.defaultBotAddressSettings.md +11 -0
  42. package/docs/bot.Function.run.md +21 -0
  43. package/docs/bot.Function.subscribeChatItems.md +27 -0
  44. package/docs/bot.Interface.BotConfig.md +87 -0
  45. package/docs/bot.Interface.BotOptions.md +81 -0
  46. package/docs/bot.TypeAlias.BotDbOpts.md +21 -0
  47. package/docs/core.Class.ChatAPIError.md +205 -0
  48. package/docs/core.Class.ChatInitError.md +205 -0
  49. package/docs/core.DBMigrationError.Interface.ErrorMigration.md +41 -0
  50. package/docs/core.DBMigrationError.Interface.ErrorNotADatabase.md +33 -0
  51. package/docs/core.DBMigrationError.Interface.ErrorSQL.md +41 -0
  52. package/docs/core.DBMigrationError.Interface.InvalidConfirmation.md +25 -0
  53. package/docs/core.DBMigrationError.Interface.InvalidQueueSize.md +25 -0
  54. package/docs/core.DBMigrationError.TypeAlias.Tag.md +11 -0
  55. package/docs/core.Enumeration.MigrationConfirmation.md +43 -0
  56. package/docs/core.Function.chatCloseStore.md +23 -0
  57. package/docs/core.Function.chatDecryptFile.md +31 -0
  58. package/docs/core.Function.chatEncryptFile.md +31 -0
  59. package/docs/core.Function.chatMigrateInit.md +37 -0
  60. package/docs/core.Function.chatReadFile.md +27 -0
  61. package/docs/core.Function.chatRecvMsgWait.md +27 -0
  62. package/docs/core.Function.chatSendCmd.md +27 -0
  63. package/docs/core.Function.chatWriteFile.md +31 -0
  64. package/docs/core.Function.loadLibrary.md +24 -0
  65. package/docs/core.Interface.APIResult.md +31 -0
  66. package/docs/core.Interface.CryptoArgs.md +27 -0
  67. package/docs/core.Interface.UpMigration.md +25 -0
  68. package/docs/core.MTRError.Interface.MTREDifferent.md +33 -0
  69. package/docs/core.MTRError.Interface.MTRENoDown.md +33 -0
  70. package/docs/core.MTRError.TypeAlias.Tag.md +11 -0
  71. package/docs/core.MigrationError.Interface.MEDowngrade.md +33 -0
  72. package/docs/core.MigrationError.Interface.MEUpgrade.md +33 -0
  73. package/docs/core.MigrationError.Interface.MigrationError.md +33 -0
  74. package/docs/core.MigrationError.TypeAlias.Tag.md +11 -0
  75. package/docs/core.Namespace.DBMigrationError.md +19 -0
  76. package/docs/core.Namespace.MTRError.md +16 -0
  77. package/docs/core.Namespace.MigrationError.md +17 -0
  78. package/docs/core.TypeAlias.Backend.md +11 -0
  79. package/docs/core.TypeAlias.DBMigrationError.md +11 -0
  80. package/docs/core.TypeAlias.MTRError.md +11 -0
  81. package/docs/core.TypeAlias.MigrationError.md +11 -0
  82. package/docs/util.Function.botAddressSettings.md +21 -0
  83. package/docs/util.Function.chatInfoName.md +21 -0
  84. package/docs/util.Function.chatInfoRef.md +21 -0
  85. package/docs/util.Function.ciBotCommand.md +21 -0
  86. package/docs/util.Function.ciContentText.md +21 -0
  87. package/docs/util.Function.contactAddressStr.md +21 -0
  88. package/docs/util.Function.fromLocalProfile.md +21 -0
  89. package/docs/util.Function.reactionText.md +21 -0
  90. package/docs/util.Function.senderName.md +25 -0
  91. package/docs/util.Interface.BotCommand.md +25 -0
  92. package/examples/squaring-bot-readme.js +17 -0
  93. package/examples/squaring-bot.ts +42 -0
  94. package/package.json +55 -4
  95. package/src/api.ts +997 -0
  96. package/src/bot.ts +221 -0
  97. package/src/cli.ts +42 -0
  98. package/src/core.ts +240 -0
  99. package/src/index.ts +22 -0
  100. package/src/libs.ts +202 -0
  101. package/src/release.ts +19 -0
  102. package/src/simplex.d.ts +12 -0
  103. package/src/simplex.js +22 -0
  104. package/src/util.ts +92 -0
  105. package/tsconfig.json +23 -0
package/src/api.ts ADDED
@@ -0,0 +1,997 @@
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}, chat: ChatApi) => 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
+ * Database configuration. `type` selects the libsimplex backend loaded by
61
+ * `ChatApi.init`; one backend per process.
62
+ */
63
+ export type DbConfig =
64
+ | {
65
+ /** SQLite backend (default). */
66
+ type: "sqlite"
67
+ /** File prefix — two schema files are named `<prefix>_chat.db` and `<prefix>_agent.db`. */
68
+ filePrefix: string
69
+ /** Optional SQLCipher encryption key. Empty/omitted = unencrypted. */
70
+ encryptionKey?: string
71
+ }
72
+ | {
73
+ /** PostgreSQL backend (Linux x86_64 only, libpq5 required). */
74
+ type: "postgres"
75
+ /** Schema prefix used to namespace tables. Defaults to `"simplex_v1"` when omitted. */
76
+ schemaPrefix?: string
77
+ /** PostgreSQL connection string (e.g. `postgres://user:pass@host/db`). */
78
+ connectionString: string
79
+ }
80
+
81
+ function dbConfigToMigrateArgs(db: DbConfig): [string, string] {
82
+ switch (db.type) {
83
+ case "sqlite":
84
+ return [db.filePrefix, db.encryptionKey ?? ""]
85
+ case "postgres":
86
+ return [db.schemaPrefix ?? "", db.connectionString]
87
+ default:
88
+ throw new Error(`Invalid DbConfig: ${JSON.stringify(db satisfies never)}`)
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Main API class for interacting with the chat core library.
94
+ */
95
+ export class ChatApi {
96
+ private receiveEvents = false
97
+ private eventsLoop: Promise<void> | undefined = undefined
98
+ private subscribers: {[K in CEvt.Tag]?: EventSubscriber<K>[]} = {}
99
+ private receivers: EventSubscriberFunc<CEvt.Tag>[] = []
100
+
101
+ private constructor(protected ctrl_: bigint | undefined) {}
102
+
103
+ /**
104
+ * Initializes the ChatApi, loading libsimplex for `db.type` (downloaded on first use).
105
+ * @param {DbConfig} db - Database configuration (sqlite or postgres).
106
+ * @param {core.MigrationConfirmation} [confirm=core.MigrationConfirmation.YesUp] - Migration confirmation mode.
107
+ * @param {number} [queueSize] - Size of internal queues, the core default is used when omitted.
108
+ */
109
+ static async init(
110
+ db: DbConfig,
111
+ confirm = core.MigrationConfirmation.YesUp,
112
+ queueSize?: number
113
+ ): Promise<ChatApi> {
114
+ const [path, key] = dbConfigToMigrateArgs(db)
115
+ await core.loadLibrary(db.type)
116
+ const ctrl = await core.chatMigrateInit(path, key, confirm, queueSize)
117
+ return new ChatApi(ctrl)
118
+ }
119
+
120
+ /**
121
+ * Start chat controller. Must be called with the existing user profile.
122
+ */
123
+ async startChat(): Promise<void> {
124
+ if (this.eventsLoop) throw new Error("chat already started")
125
+ const ctrl = this.ctrl
126
+ this.receiveEvents = true
127
+ this.eventsLoop = this.runEventsLoop()
128
+ let r: ChatResponse
129
+ try {
130
+ r = await core.chatSendCmd(ctrl, CC.StartChat.cmdString({mainApp: true, enableSndFiles: true, serviceRequests: false}))
131
+ } catch (e) {
132
+ await this.stopEventsLoop()
133
+ throw e
134
+ }
135
+ if (r.type !== "chatStarted" && r.type !== "chatRunning") {
136
+ await this.stopEventsLoop()
137
+ throw new ChatCommandError("error starting chat", r)
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Stop chat controller.
143
+ * `close` calls it before closing the database.
144
+ * Usually doesn't need to be called in chat bots.
145
+ */
146
+ async stopChat(): Promise<void> {
147
+ const r = await this.sendChatCmd("/_stop")
148
+ if (r.type !== "chatStopped") throw new ChatCommandError("error stopping chat", r)
149
+ await this.stopEventsLoop()
150
+ }
151
+
152
+ /**
153
+ * Stop chat controller and close chat database.
154
+ * The database is not closed if stopping fails.
155
+ * Usually doesn't need to be called in chat bots.
156
+ */
157
+ async close(): Promise<void> {
158
+ // a running controller keeps using the database connections that closing frees
159
+ await this.stopChat()
160
+ await core.chatCloseStore(this.ctrl)
161
+ this.ctrl_ = undefined
162
+ }
163
+
164
+ private async stopEventsLoop(): Promise<void> {
165
+ this.receiveEvents = false
166
+ if (this.eventsLoop) await this.eventsLoop
167
+ this.eventsLoop = undefined
168
+ }
169
+
170
+ private async runEventsLoop(): Promise<void> {
171
+ while (this.receiveEvents) {
172
+ try {
173
+ const event = await this.recvChatEvent()
174
+ if (!event) continue
175
+ const subs = this.subscribers[event.type]
176
+ if (subs) {
177
+ for (const {subscriber, once} of [...subs]) {
178
+ try {
179
+ const p = (subscriber as EventSubscriberFunc<typeof event.type>)(event, this)
180
+ if (p instanceof Promise) await p
181
+ } catch(e) {
182
+ console.log(`${event.type} event processing error`, e)
183
+ }
184
+ if (once) this.off(event.type, subscriber as EventSubscriberFunc<typeof event.type>)
185
+ }
186
+ }
187
+ for (const r of [...this.receivers]) {
188
+ try {
189
+ const p = r(event, this)
190
+ if (p instanceof Promise) await p
191
+ } catch(e) {
192
+ console.log(`${event.type} event processing error`, e)
193
+ }
194
+ }
195
+ } catch(err) {
196
+ const e = err as core.ChatAPIError
197
+ if ("chatError" in e) {
198
+ console.log("Chat error", e.chatError)
199
+ } else {
200
+ console.log("Invalid event", e)
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Subscribe multiple event handlers at once.
208
+ * @param subscribers - An object mapping event types (CEvt.Tag) to their subscriber functions.
209
+ * @throws {Error} If the same function is subscribed to event.
210
+ */
211
+ on<K extends CEvt.Tag>(subscribers: EventSubscribers): void
212
+
213
+ /**
214
+ * Subscribe a handler to a specific event.
215
+ * @param {CEvt.Tag} event - The event type to subscribe to.
216
+ * @param subscriber - The subscriber function for the event.
217
+ * @throws {Error} If the same function is subscribed to event.
218
+ */
219
+ on<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>): void
220
+ on<K extends CEvt.Tag>(events: K | EventSubscribers, subscriber?: EventSubscriberFunc<K>): void {
221
+ if (typeof events === "string" && subscriber) {
222
+ this.on_(events, subscriber)
223
+ } else {
224
+ const eventEntries = Object.entries(events) as [CEvt.Tag, EventSubscriberFunc<CEvt.Tag> | undefined][]
225
+ for (const [event, subscriber] of eventEntries) {
226
+ if (subscriber) this.on_(event, subscriber)
227
+ }
228
+ }
229
+ }
230
+
231
+ private on_<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>, once: boolean = false): void {
232
+ const subs: EventSubscriber<K>[] = this.subscribers[event] || (this.subscribers[event] = [])
233
+ if (subs.some(s => s.subscriber === subscriber)) throw Error(`this function is already subscribed to ${event}`)
234
+ subs.push({subscriber, once})
235
+ }
236
+
237
+ /**
238
+ * Subscribe a handler to any event.
239
+ * @param receiver - The receiver function for any event.
240
+ * @throws {Error} If the same function is subscribed to event.
241
+ */
242
+ onAny(receiver: EventSubscriberFunc<CEvt.Tag>): void {
243
+ if (this.receivers.some(s => s === receiver)) throw Error("this function is already subscribed")
244
+ this.receivers.push(receiver)
245
+ }
246
+
247
+ /**
248
+ * Subscribe a handler to a specific event to be delivered one time.
249
+ * @param {CEvt.Tag} event - The event type to subscribe to.
250
+ * @param subscriber - The subscriber function for the event.
251
+ * @throws {Error} If the same function is subscribed to event.
252
+ */
253
+ once<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K>): void {
254
+ this.on_(event, subscriber, true)
255
+ }
256
+
257
+ /**
258
+ * Waits for specific event, with an optional predicate.
259
+ * Returns `undefined` on timeout if specified.
260
+ */
261
+ wait<K extends CEvt.Tag>(event: K): Promise<ChatEvent & {type: K}>
262
+ wait<K extends CEvt.Tag>(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined): Promise<ChatEvent & {type: K}>
263
+ wait<K extends CEvt.Tag>(event: K, timeout: number): Promise<ChatEvent & {type: K} | undefined>
264
+ wait<K extends CEvt.Tag>(event: K, predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined, timeout: number): Promise<ChatEvent & {type: K} | undefined>
265
+ wait<K extends CEvt.Tag>(
266
+ event: K,
267
+ predicate: ((event: ChatEvent & {type: K}) => boolean) | undefined | number = undefined, // number for timeout
268
+ timeout: number = 0 // milliseconds, default - indefinite
269
+ ): Promise<ChatEvent & {type: K} | undefined> {
270
+ if (typeof predicate === "number") {
271
+ timeout = predicate
272
+ predicate = undefined
273
+ }
274
+ return new Promise((resolve, reject) => {
275
+ let done = false
276
+ const cleanup = () => {
277
+ done = true
278
+ this.off(event, subscriber)
279
+ }
280
+ const subscriber: EventSubscriberFunc<K> = async (evt: ChatEvent & {type: K}) => {
281
+ if (done) return
282
+ if (predicate) {
283
+ try { if (!predicate(evt)) return }
284
+ catch(e) { cleanup(); reject(e); return }
285
+ }
286
+ cleanup()
287
+ resolve(evt)
288
+ }
289
+ this.on(event, subscriber)
290
+ if (timeout > 0) {
291
+ setTimeout(() => { if (!done) { cleanup(); resolve(undefined) } }, timeout)
292
+ }
293
+ })
294
+ }
295
+
296
+ /**
297
+ * Unsubscribe all or a specific handler from a specific event.
298
+ * @param {CEvt.Tag} event - The event type to unsubscribe from.
299
+ * @param subscriber - An optional subscriber function for the event.
300
+ */
301
+ off<K extends CEvt.Tag>(event: K, subscriber: EventSubscriberFunc<K> | undefined = undefined): void {
302
+ if (subscriber) {
303
+ const subs = this.subscribers[event]
304
+ if (subs) {
305
+ const i = subs.findIndex(s => s.subscriber === subscriber)
306
+ if (i >= 0) subs.splice(i, 1)
307
+ }
308
+ } else {
309
+ delete this.subscribers[event]
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Unsubscribe all or a specific handler from any events.
315
+ * @param receiver - An optional subscriber function for the event.
316
+ */
317
+ offAny(receiver: EventSubscriberFunc<CEvt.Tag> | undefined = undefined): void {
318
+ if (receiver) {
319
+ const i = this.receivers.findIndex(r => r === receiver)
320
+ if (i >= 0) this.receivers.splice(i, 1)
321
+ } else {
322
+ this.receivers = []
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Chat controller is initialized
328
+ */
329
+ get initialized(): boolean {
330
+ return typeof this.ctrl_ === "bigint"
331
+ }
332
+
333
+ /**
334
+ * Chat controller is started
335
+ */
336
+ get started(): boolean {
337
+ return this.receiveEvents && this.eventsLoop !== undefined
338
+ }
339
+
340
+ /**
341
+ * Chat controller reference
342
+ */
343
+ get ctrl(): bigint {
344
+ if (typeof this.ctrl_ === "bigint") return this.ctrl_
345
+ else throw Error("chat api controller not initialized")
346
+ }
347
+
348
+ async sendChatCmd(cmd: string): Promise<ChatResponse> {
349
+ return await core.chatSendCmd(this.ctrl, cmd)
350
+ }
351
+
352
+ async recvChatEvent(wait: number = 500_000): Promise<ChatEvent | undefined> {
353
+ return await core.chatRecvMsgWait(this.ctrl, wait)
354
+ }
355
+
356
+ /**
357
+ * Create bot address.
358
+ * Network usage: interactive.
359
+ */
360
+ async apiCreateUserAddress(userId: number): Promise<T.CreatedConnLink> {
361
+ const r = await this.sendChatCmd(CC.APICreateMyAddress.cmdString({userId}))
362
+ if (r.type === "userContactLinkCreated") return r.connLinkContact
363
+ throw new ChatCommandError("error creating user address", r)
364
+ }
365
+
366
+ /**
367
+ * Deletes a user address.
368
+ * Network usage: background.
369
+ */
370
+ async apiDeleteUserAddress(userId: number): Promise<void> {
371
+ const r = await this.sendChatCmd(CC.APIDeleteMyAddress.cmdString({userId}))
372
+ if (r.type === "userContactLinkDeleted") return
373
+ throw new ChatCommandError("error deleting user address", r)
374
+ }
375
+
376
+ /**
377
+ * Get bot address and settings.
378
+ * Network usage: no.
379
+ */
380
+ async apiGetUserAddress(userId: number): Promise<T.UserContactLink | undefined> {
381
+ try {
382
+ const r = await this.sendChatCmd(CC.APIShowMyAddress.cmdString({userId}))
383
+ switch (r.type) {
384
+ case "userContactLink": return r.contactLink
385
+ default: throw new ChatCommandError("error loading user address", r)
386
+ }
387
+ } catch (err) {
388
+ const e = err as any
389
+ if (e.chatError?.type === "errorStore" && e.chatError.storeError?.type === "userContactLinkNotFound") return undefined
390
+ throw e
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Add address to bot profile.
396
+ * Network usage: interactive.
397
+ */
398
+ async apiSetProfileAddress(userId: number, enable: boolean): Promise<T.UserProfileUpdateSummary> {
399
+ const r = await this.sendChatCmd(CC.APISetProfileAddress.cmdString({userId, enable}))
400
+ switch (r.type) {
401
+ case "userProfileUpdated":
402
+ return r.updateSummary
403
+ case "userProfileNoChange":
404
+ return {updateSuccesses: 0, updateFailures: 0, changedContacts: []}
405
+ default:
406
+ throw new ChatCommandError("error setting profile address", r)
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Set bot address settings.
412
+ * Network usage: interactive.
413
+ */
414
+ async apiSetAddressSettings(userId: number, {autoAccept, welcomeMessage, businessAddress}: BotAddressSettings): Promise<void> {
415
+ const autoReply = welcomeMessage || defaultBotAddressSettings.welcomeMessage
416
+ const settings: T.AddressSettings = {
417
+ autoAccept: (autoAccept === undefined ? defaultBotAddressSettings.autoAccept : autoAccept) ? {acceptIncognito: false} : undefined,
418
+ autoReply: typeof autoReply === "string" ? {type: "text", text: autoReply} : autoReply,
419
+ businessAddress: businessAddress || defaultBotAddressSettings.businessAddress || false
420
+ }
421
+ const r = await this.sendChatCmd(CC.APISetAddressSettings.cmdString({userId, settings}))
422
+ if (r.type !== "userContactLinkUpdated") {
423
+ throw new ChatCommandError("error changing user contact address settings", r)
424
+ }
425
+ }
426
+
427
+ async apiSetUserDomain(userId: number, simplexDomain?: string): Promise<T.User> {
428
+ const r = await this.sendChatCmd(CC.APISetUserDomain.cmdString({userId, simplexDomain}))
429
+ switch (r.type) {
430
+ case "userProfileUpdated":
431
+ case "userProfileNoChange":
432
+ return r.user
433
+ default:
434
+ throw new ChatCommandError("error setting SimpleX name", r)
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Send messages.
440
+ * Network usage: background.
441
+ */
442
+ async apiSendMessages(chat: [T.ChatType, number] | T.ChatRef | T.ChatInfo, messages: T.ComposedMessage[], liveMessage = false): Promise<T.AChatItem[]> {
443
+ const sendRef = Array.isArray(chat)
444
+ ? {chatType: chat[0], chatId: chat[1]}
445
+ : "chatType" in chat
446
+ ? chat
447
+ : util.chatInfoRef(chat)
448
+ if (!sendRef) throw Error("apiSendMessages: can't send messages to this chat")
449
+ const r = await this.sendChatCmd(
450
+ CC.APISendMessages.cmdString({
451
+ sendRef,
452
+ composedMessages: messages,
453
+ liveMessage,
454
+ signMessages: false
455
+ })
456
+ )
457
+ if (r.type === "newChatItems") return r.chatItems
458
+ throw new ChatCommandError("unexpected response", r)
459
+ }
460
+
461
+ /**
462
+ * Send text message.
463
+ * Network usage: background.
464
+ */
465
+ async apiSendTextMessage(chat: [T.ChatType, number] | T.ChatRef | T.ChatInfo, text: string, inReplyTo?: number): Promise<T.AChatItem[]> {
466
+ return this.apiSendMessages(chat, [{msgContent: {type: "text", text}, mentions: {}, quotedItemId: inReplyTo}])
467
+ }
468
+
469
+ /**
470
+ * Send text message in reply to received message.
471
+ * Network usage: background.
472
+ */
473
+ async apiSendTextReply(chatItem: T.AChatItem, text: string): Promise<T.AChatItem[]> {
474
+ return this.apiSendTextMessage(chatItem.chatInfo, text, chatItem.chatItem.meta.itemId)
475
+ }
476
+
477
+ /**
478
+ * Update message.
479
+ * Network usage: background.
480
+ */
481
+ async apiUpdateChatItem(chatType: T.ChatType, chatId: number, chatItemId: number, msgContent: T.MsgContent, liveMessage: false): Promise<T.ChatItem> {
482
+ const r = await this.sendChatCmd(
483
+ CC.APIUpdateChatItem.cmdString({
484
+ chatRef: {chatType, chatId},
485
+ chatItemId,
486
+ liveMessage,
487
+ updatedMessage: {msgContent, mentions: {}},
488
+ })
489
+ )
490
+ if (r.type === "chatItemUpdated" || r.type === "chatItemNotChanged") return r.chatItem.chatItem
491
+ throw new ChatCommandError("error updating chat item", r)
492
+ }
493
+
494
+ /**
495
+ * Delete message.
496
+ * Network usage: background.
497
+ */
498
+ async apiDeleteChatItems(
499
+ chatType: T.ChatType,
500
+ chatId: number,
501
+ chatItemIds: number[],
502
+ deleteMode: T.CIDeleteMode
503
+ ): Promise<T.ChatItemDeletion[]> {
504
+ const r = await this.sendChatCmd(CC.APIDeleteChatItem.cmdString({chatRef: {chatType, chatId}, chatItemIds, deleteMode}))
505
+ if (r.type === "chatItemsDeleted") return r.chatItemDeletions
506
+ throw new ChatCommandError("error deleting chat item", r)
507
+ }
508
+
509
+ /**
510
+ * Moderate message. Requires Moderator role (and higher than message author's).
511
+ * Network usage: background.
512
+ */
513
+ async apiDeleteMemberChatItem(groupId: number, chatItemIds: number[]): Promise<T.ChatItemDeletion[]> {
514
+ const r = await this.sendChatCmd(CC.APIDeleteMemberChatItem.cmdString({groupId, chatItemIds}))
515
+ if (r.type === "chatItemsDeleted") return r.chatItemDeletions
516
+ throw new ChatCommandError("error deleting member chat item", r)
517
+ }
518
+
519
+ /**
520
+ * Add/remove message reaction.
521
+ * Network usage: background.
522
+ */
523
+ async apiChatItemReaction(
524
+ chatType: T.ChatType,
525
+ chatId: number,
526
+ chatItemId: number,
527
+ add: boolean,
528
+ reaction: T.MsgReaction
529
+ ): Promise<T.ACIReaction> {
530
+ const r = await this.sendChatCmd(CC.APIChatItemReaction.cmdString({chatRef: {chatType, chatId}, chatItemId, add, reaction}))
531
+ if (r.type === "chatItemReaction") return r.reaction
532
+ throw new ChatCommandError("error setting item reaction", r)
533
+ }
534
+
535
+ /**
536
+ * Receive file.
537
+ * Network usage: no.
538
+ */
539
+ async apiReceiveFile(fileId: number): Promise<T.AChatItem> {
540
+ const r = await this.sendChatCmd(CC.ReceiveFile.cmdString({fileId, userApprovedRelays: true}))
541
+ if (r.type === "rcvFileAccepted") return r.chatItem
542
+ if (r.type === "rcvFileAcceptedSndCancelled") throw new ChatCommandError("file cancelled by sender", r)
543
+ throw new ChatCommandError("error receiving file", r)
544
+ }
545
+
546
+ /**
547
+ * Cancel file.
548
+ * Network usage: background.
549
+ */
550
+ async apiCancelFile(fileId: number): Promise<void> {
551
+ const r = await this.sendChatCmd(CC.CancelFile.cmdString({fileId}))
552
+ if (r.type === "sndFileCancelled" || r.type === "rcvFileCancelled") return
553
+ throw new ChatCommandError("error canceling file", r)
554
+ }
555
+
556
+ /**
557
+ * Add contact to group. Requires bot to have Admin role.
558
+ * Network usage: interactive.
559
+ */
560
+ async apiAddMember(groupId: number, contactId: number, memberRole: T.GroupMemberRole): Promise<T.GroupMember> {
561
+ const r = await this.sendChatCmd(CC.APIAddMember.cmdString({groupId, contactId, memberRole}))
562
+ if (r.type === "sentGroupInvitation") return r.member
563
+ throw new ChatCommandError("error adding member", r)
564
+ }
565
+
566
+ /**
567
+ * 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.
568
+ * Network usage: interactive.
569
+ */
570
+ async apiAddMemberViaGroup(groupId: number, viaGroupId: number, groupMemberId: number, memberRole: T.GroupMemberRole): Promise<T.GroupMember> {
571
+ const r = await this.sendChatCmd(CC.APIAddMemberViaGroup.cmdString({groupId, viaGroupId, groupMemberId, memberRole}))
572
+ if (r.type === "sentGroupInvitationViaGroup") return r.member
573
+ throw new ChatCommandError("error adding member", r)
574
+ }
575
+
576
+ /**
577
+ * Join group.
578
+ * Network usage: interactive.
579
+ */
580
+ async apiJoinGroup(groupId: number): Promise<T.GroupInfo> {
581
+ const r = await this.sendChatCmd(CC.APIJoinGroup.cmdString({groupId}))
582
+ if (r.type === "userAcceptedGroupSent") return r.groupInfo
583
+ throw new ChatCommandError("error joining group", r)
584
+ }
585
+
586
+ /**
587
+ * Accept group member. Requires Admin role.
588
+ * Network usage: background.
589
+ */
590
+ async apiAcceptMember(groupId: number, groupMemberId: number, memberRole: T.GroupMemberRole): Promise<T.GroupMember> {
591
+ const r = await this.sendChatCmd(CC.APIAcceptMember.cmdString({groupId, groupMemberId, memberRole}))
592
+ if (r.type === "memberAccepted") return r.member
593
+ throw new ChatCommandError("error accepting member", r)
594
+ }
595
+
596
+ /**
597
+ * Set members role. Requires Admin role.
598
+ * Network usage: background.
599
+ */
600
+ async apiSetMembersRole(groupId: number, groupMemberIds: number[], memberRole: T.GroupMemberRole): Promise<void> {
601
+ const r = await this.sendChatCmd(CC.APIMembersRole.cmdString({groupId, groupMemberIds, memberRole}))
602
+ if (r.type === "membersRoleUser") return
603
+ throw new ChatCommandError("error setting members role", r)
604
+ }
605
+
606
+ /**
607
+ * Block members. Requires Moderator role.
608
+ * Network usage: background.
609
+ */
610
+ async apiBlockMembersForAll(groupId: number, groupMemberIds: number[], blocked: boolean): Promise<void> {
611
+ const r = await this.sendChatCmd(CC.APIBlockMembersForAll.cmdString({groupId, groupMemberIds, blocked}))
612
+ if (r.type === "membersBlockedForAllUser") return
613
+ throw new ChatCommandError("error blocking members", r)
614
+ }
615
+
616
+ /**
617
+ * Remove members. Requires Admin role.
618
+ * Network usage: background.
619
+ */
620
+ async apiRemoveMembers(groupId: number, memberIds: number[], withMessages = false): Promise<T.GroupMember[]> {
621
+ const r = await this.sendChatCmd(CC.APIRemoveMembers.cmdString({groupId, groupMemberIds: memberIds, withMessages}))
622
+ if (r.type === "userDeletedMembers") return r.members
623
+ throw new ChatCommandError("error removing member", r)
624
+ }
625
+
626
+ /**
627
+ * Leave group.
628
+ * Network usage: background.
629
+ */
630
+ async apiLeaveGroup(groupId: number): Promise<T.GroupInfo> {
631
+ const r = await this.sendChatCmd(CC.APILeaveGroup.cmdString({groupId}))
632
+ if (r.type === "leftMemberUser") return r.groupInfo
633
+ throw new ChatCommandError("error leaving group", r)
634
+ }
635
+
636
+ /**
637
+ * Get group members.
638
+ * Network usage: no.
639
+ */
640
+ async apiListMembers(groupId: number): Promise<T.GroupMember[]> {
641
+ const r = await this.sendChatCmd(CC.APIListMembers.cmdString({groupId}))
642
+ if (r.type === "groupMembers") return r.group.members
643
+ throw new ChatCommandError("error getting group members", r)
644
+ }
645
+
646
+ /**
647
+ * Create group.
648
+ * Network usage: no.
649
+ */
650
+ async apiNewGroup(userId: number, groupProfile: T.GroupProfile): Promise<T.GroupInfo> {
651
+ const r = await this.sendChatCmd(CC.APINewGroup.cmdString({userId, groupProfile, incognito: false}))
652
+ if (r.type === "groupCreated") return r.groupInfo
653
+ throw new ChatCommandError("error creating group", r)
654
+ }
655
+
656
+ /**
657
+ * Update group profile.
658
+ * Network usage: background.
659
+ */
660
+ async apiUpdateGroupProfile(groupId: number, groupProfile: T.GroupProfile): Promise<T.GroupInfo> {
661
+ const r = await this.sendChatCmd(CC.APIUpdateGroupProfile.cmdString({groupId, groupProfile}))
662
+ if (r.type === "groupUpdated") return r.toGroup
663
+ throw new ChatCommandError("error updating group", r)
664
+ }
665
+
666
+ /**
667
+ * Create group link.
668
+ * Network usage: interactive.
669
+ */
670
+ async apiCreateGroupLink(groupId: number, memberRole: T.GroupMemberRole): Promise<string> {
671
+ const r = await this.sendChatCmd(CC.APICreateGroupLink.cmdString({groupId, memberRole}))
672
+ if (r.type === "groupLinkCreated") {
673
+ const link = r.groupLink.connLinkContact
674
+ return link.connShortLink || link.connFullLink
675
+ }
676
+ throw new ChatCommandError("error creating group link", r)
677
+ }
678
+
679
+ /**
680
+ * Set member role for group link.
681
+ * Network usage: no.
682
+ */
683
+ async apiSetGroupLinkMemberRole(groupId: number, memberRole: T.GroupMemberRole): Promise<void> {
684
+ const r = await this.sendChatCmd(CC.APIGroupLinkMemberRole.cmdString({groupId, memberRole}))
685
+ if (r.type !== "groupLink") throw new ChatCommandError("error setting group link member role", r)
686
+ }
687
+
688
+ /**
689
+ * Delete group link.
690
+ * Network usage: background.
691
+ */
692
+ async apiDeleteGroupLink(groupId: number): Promise<void> {
693
+ const r = await this.sendChatCmd(CC.APIDeleteGroupLink.cmdString({groupId}))
694
+ if (r.type !== "groupLinkDeleted") throw new ChatCommandError("error deleting group link", r)
695
+ }
696
+
697
+ /**
698
+ * Get group link.
699
+ * Network usage: no.
700
+ */
701
+ async apiGetGroupLink(groupId: number): Promise<T.GroupLink> {
702
+ const r = await this.sendChatCmd(CC.APIGetGroupLink.cmdString({groupId}))
703
+ if (r.type === "groupLink") return r.groupLink
704
+ throw new ChatCommandError("error getting group link", r)
705
+ }
706
+
707
+ async apiGetGroupLinkStr(groupId: number): Promise<string> {
708
+ const link = (await this.apiGetGroupLink(groupId)).connLinkContact
709
+ return link.connShortLink || link.connFullLink
710
+ }
711
+
712
+ /**
713
+ * Create 1-time invitation link.
714
+ * Network usage: interactive.
715
+ */
716
+ async apiCreateLink(userId: number): Promise<string> {
717
+ const r = await this.sendChatCmd(CC.APIAddContact.cmdString({userId, incognito: false}))
718
+ if (r.type === "invitation") {
719
+ const link = r.connLinkInvitation
720
+ return link.connShortLink || link.connFullLink
721
+ }
722
+ throw new ChatCommandError("error creating link", r)
723
+ }
724
+
725
+ /**
726
+ * Determine SimpleX link type and if the bot is already connected via this link.
727
+ * Network usage: interactive.
728
+ */
729
+ async apiConnectPlan(userId: number, connectionLink: string): Promise<[T.ConnectionPlan, T.CreatedConnLink]> {
730
+ const r = await this.sendChatCmd(CC.APIConnectPlan.cmdString({userId, connectTarget: connectionLink, resolveMode: T.PlanResolveMode.Unknown}))
731
+ if (r.type === "connectionPlan") return [r.connectionPlan, r.connLink]
732
+ throw new ChatCommandError("error getting connect plan", r)
733
+ }
734
+
735
+ /**
736
+ * Connect via prepared SimpleX link. The link can be 1-time invitation link, contact address or group link
737
+ * Network usage: interactive.
738
+ */
739
+ async apiConnect(userId: number, incognito: boolean, preparedLink: T.CreatedConnLink): Promise<ConnReqType> {
740
+ const r = await this.sendChatCmd(CC.APIConnect.cmdString({userId, incognito, preparedLink_: preparedLink}))
741
+ return this.handleConnectResult(r)
742
+ }
743
+
744
+ /**
745
+ * Connect via SimpleX link as string in the active user profile.
746
+ * Network usage: interactive.
747
+ */
748
+ async apiConnectActiveUser(connLink: string): Promise<ConnReqType> {
749
+ const r = await this.sendChatCmd(CC.Connect.cmdString({incognito: false, connTarget_: connLink}))
750
+ return this.handleConnectResult(r)
751
+ }
752
+
753
+ private handleConnectResult(r: ChatResponse): ConnReqType {
754
+ switch (r.type) {
755
+ case "sentConfirmation":
756
+ return ConnReqType.Invitation
757
+ case "sentInvitation":
758
+ return ConnReqType.Contact
759
+ case "contactAlreadyExists":
760
+ throw new ChatCommandError("contact already exists", r)
761
+ default:
762
+ throw new ChatCommandError("connection error", r)
763
+ }
764
+ }
765
+
766
+ /**
767
+ * Accept contact request.
768
+ * Network usage: interactive.
769
+ */
770
+ async apiAcceptContactRequest(contactReqId: number): Promise<T.Contact> {
771
+ const r = await this.sendChatCmd(CC.APIAcceptContact.cmdString({contactReqId}))
772
+ if (r.type === "acceptingContactRequest") return r.contact
773
+ throw new ChatCommandError("error accepting contact request", r)
774
+ }
775
+
776
+ /**
777
+ * Reject contact request. The user who sent the request is **not notified**.
778
+ * Network usage: no.
779
+ */
780
+ async apiRejectContactRequest(contactReqId: number): Promise<void> {
781
+ const r = await this.sendChatCmd(CC.APIRejectContact.cmdString({contactReqId, notify: false}))
782
+ if (r.type === "contactRequestRejected") return
783
+ throw new ChatCommandError("error rejecting contact request", r)
784
+ }
785
+
786
+ /**
787
+ * Get contacts.
788
+ * Network usage: no.
789
+ */
790
+ async apiListContacts(userId: number): Promise<T.Contact[]> {
791
+ const r = await this.sendChatCmd(CC.APIListContacts.cmdString({userId}))
792
+ if (r.type === "contactsList") return r.contacts
793
+ throw new ChatCommandError("error listing contacts", r)
794
+ }
795
+
796
+ /**
797
+ * Get groups.
798
+ * Network usage: no.
799
+ */
800
+ async apiListGroups(userId: number, contactId?: number, search?: string): Promise<T.GroupInfo[]> {
801
+ const r = await this.sendChatCmd(CC.APIListGroups.cmdString({userId, contactId_: contactId, search}))
802
+ if (r.type === "groupsList") return r.groups
803
+ throw new ChatCommandError("error listing groups", r)
804
+ }
805
+
806
+ /**
807
+ * Get chat previews (paginated).
808
+ * Network usage: no.
809
+ *
810
+ * Prefer this over apiListContacts / apiListGroups for any scan: those
811
+ * methods load every record into memory in a single response and will fail
812
+ * on large databases.
813
+ */
814
+ async apiGetChats(
815
+ userId: number,
816
+ pagination: T.PaginationByTime,
817
+ query: T.ChatListQuery = {type: "filters", favorite: false, unread: false},
818
+ pendingConnections = false,
819
+ ): Promise<T.AChat[]> {
820
+ const r = await this.sendChatCmd(CC.APIGetChats.cmdString({userId, pendingConnections, pagination, query}))
821
+ if (r.type === "apiChats") return r.chats
822
+ throw new ChatCommandError("error getting chats", r)
823
+ }
824
+
825
+ /**
826
+ * Delete chat.
827
+ * Network usage: background.
828
+ */
829
+ async apiDeleteChat(chatType: T.ChatType, chatId: number, deleteMode: T.ChatDeleteMode = {type: "full", notify: true}): Promise<void> {
830
+ const r = await this.sendChatCmd(CC.APIDeleteChat.cmdString({chatRef: {chatType, chatId}, chatDeleteMode: deleteMode}))
831
+ switch (chatType) {
832
+ case T.ChatType.Direct:
833
+ if (r.type === "contactDeleted") return
834
+ break
835
+ case T.ChatType.Group:
836
+ if (r.type === "groupDeletedUser") return
837
+ break
838
+ }
839
+ throw new ChatCommandError("error deleting chat", r)
840
+ }
841
+
842
+ /**
843
+ * Set group custom data.
844
+ * Network usage: no.
845
+ */
846
+ async apiSetGroupCustomData(groupId: number, customData?: object): Promise<void> {
847
+ const r = await this.sendChatCmd(CC.APISetGroupCustomData.cmdString({groupId, customData}))
848
+ if (r.type === "cmdOk") return
849
+ throw new ChatCommandError("error setting group custom data", r)
850
+ }
851
+
852
+ /**
853
+ * Set contact custom data.
854
+ * Network usage: no.
855
+ */
856
+ async apiSetContactCustomData(contactId: number, customData?: object): Promise<void> {
857
+ const r = await this.sendChatCmd(CC.APISetContactCustomData.cmdString({contactId, customData}))
858
+ if (r.type === "cmdOk") return
859
+ throw new ChatCommandError("error setting contact custom data", r)
860
+ }
861
+
862
+ /**
863
+ * Set auto-accept member contacts.
864
+ * Network usage: no.
865
+ */
866
+ async apiSetAutoAcceptMemberContacts(userId: number, onOff: boolean): Promise<void> {
867
+ const r = await this.sendChatCmd(CC.APISetUserAutoAcceptMemberContacts.cmdString({userId, onOff}))
868
+ if (r.type === "cmdOk") return
869
+ throw new ChatCommandError("error setting auto-accept member contacts", r)
870
+ }
871
+
872
+ /**
873
+ * Get chat items.
874
+ * Network usage: no.
875
+ */
876
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
877
+ async apiGetChat(chatType: T.ChatType, chatId: number, count: number): Promise<any> {
878
+ const r: any = await this.sendChatCmd(`/_get chat ${T.ChatType.cmdString(chatType)}${chatId} count=${count}`)
879
+ if (r.type === "apiChat") return r.chat
880
+ throw new ChatCommandError("error getting chat", r)
881
+ }
882
+
883
+ /**
884
+ * Get active user profile
885
+ * Network usage: no.
886
+ */
887
+ async apiGetActiveUser(): Promise<T.User | undefined> {
888
+ try {
889
+ const r = await this.sendChatCmd(CC.ShowActiveUser.cmdString({}))
890
+ switch (r.type) {
891
+ case "activeUser":
892
+ return r.user
893
+ default:
894
+ throw new ChatCommandError("unexpected response", r)
895
+ }
896
+ } catch (err) {
897
+ const e = err as core.ChatAPIError
898
+ if (e.chatError?.type === "error" && e.chatError.errorType.type === "noActiveUser") return undefined
899
+ throw err
900
+ }
901
+ }
902
+
903
+ /**
904
+ * Create new user profile
905
+ * Network usage: no.
906
+ */
907
+ async apiCreateActiveUser(profile?: T.Profile): Promise<T.User> {
908
+ const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false, clientService: false}}))
909
+ if (r.type === "activeUser") return r.user
910
+ throw new ChatCommandError("unexpected response", r)
911
+ }
912
+
913
+ /**
914
+ * Get all user profiles
915
+ * Network usage: no.
916
+ */
917
+ async apiListUsers(): Promise<T.UserInfo[]> {
918
+ const r = await this.sendChatCmd(CC.ListUsers.cmdString({}))
919
+ if (r.type === "usersList") return r.users
920
+ throw new ChatCommandError("error listing users", r)
921
+ }
922
+
923
+ /**
924
+ * Set active user profile
925
+ * Network usage: no.
926
+ */
927
+ async apiSetActiveUser(userId: number, viewPwd?: string): Promise<T.User> {
928
+ const r = await this.sendChatCmd(CC.APISetActiveUser.cmdString({userId, viewPwd}))
929
+ if (r.type === "activeUser") return r.user
930
+ throw new ChatCommandError("error setting active user", r)
931
+ }
932
+
933
+ /**
934
+ * Delete user profile.
935
+ * Network usage: background.
936
+ */
937
+ async apiDeleteUser(userId: number, delSMPQueues: boolean, viewPwd?: string): Promise<void> {
938
+ const r = await this.sendChatCmd(CC.APIDeleteUser.cmdString({userId, delSMPQueues, viewPwd}))
939
+ if (r.type === "cmdOk") return
940
+ throw new ChatCommandError("error deleting user", r)
941
+ }
942
+
943
+ /**
944
+ * Update user profile.
945
+ * Network usage: background.
946
+ */
947
+ async apiUpdateProfile(userId: number, profile: T.Profile): Promise<T.UserProfileUpdateSummary | undefined> {
948
+ const r = await this.sendChatCmd(CC.APIUpdateProfile.cmdString({userId, profile}))
949
+ switch (r.type) {
950
+ case "userProfileNoChange":
951
+ return undefined
952
+ case "userProfileUpdated":
953
+ return r.updateSummary
954
+ default:
955
+ throw new ChatCommandError("error updating profile", r)
956
+ }
957
+ }
958
+
959
+ /**
960
+ * Configure chat preference overrides for the contact.
961
+ * Network usage: background.
962
+ */
963
+ async apiSetContactPrefs(contactId: number, preferences: T.Preferences): Promise<void> {
964
+ const r = await this.sendChatCmd(CC.APISetContactPrefs.cmdString({contactId, preferences}))
965
+ if (r.type !== "contactPrefsUpdated") throw new ChatCommandError("error setting contact prefs", r)
966
+ }
967
+
968
+ /**
969
+ * Create a direct message contact with a group member.
970
+ * Returns the created contact.
971
+ * Network usage: interactive.
972
+ */
973
+ async apiCreateMemberContact(groupId: number, groupMemberId: number): Promise<T.Contact> {
974
+ const r: any = await this.sendChatCmd(`/_create member contact #${groupId} ${groupMemberId}`)
975
+ if (r.type === "newMemberContact") return r.contact
976
+ throw new ChatCommandError("error creating member contact", r)
977
+ }
978
+
979
+ /**
980
+ * Send a direct message invitation to a group member contact.
981
+ * The contact must have been created with {@link apiCreateMemberContact}.
982
+ * Network usage: interactive.
983
+ */
984
+ async apiSendMemberContactInvitation(contactId: number, message?: T.MsgContent | string): Promise<T.Contact> {
985
+ let cmd = `/_invite member contact @${contactId}`
986
+ if (message !== undefined) {
987
+ if (typeof message === "string") {
988
+ cmd += ` text ${message}`
989
+ } else {
990
+ cmd += ` json ${JSON.stringify(message)}`
991
+ }
992
+ }
993
+ const r: any = await this.sendChatCmd(cmd)
994
+ if (r.type === "newMemberContactSentInv") return r.contact
995
+ throw new ChatCommandError("error sending member contact invitation", r)
996
+ }
997
+ }