simplex-chat 0.2.1 → 6.5.0-beta.10

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