@mtkruto/node 0.0.835 → 0.0.900

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 (65) hide show
  1. package/esm/client/client.d.ts +23 -3
  2. package/esm/client/client.js +495 -75
  3. package/esm/constants.d.ts +2 -0
  4. package/esm/constants.js +2 -0
  5. package/esm/deps/deno.land/std@0.190.0/encoding/base64.d.ts +11 -0
  6. package/esm/deps/deno.land/std@0.190.0/encoding/base64.js +140 -0
  7. package/esm/deps/deno.land/x/q@v0.0.1/mod.d.ts +6 -0
  8. package/esm/deps/deno.land/x/q@v0.0.1/mod.js +71 -0
  9. package/esm/deps.d.ts +2 -0
  10. package/esm/deps.js +2 -0
  11. package/esm/mod.d.ts +3 -3
  12. package/esm/mod.js +3 -3
  13. package/esm/storage/storage.d.ts +18 -0
  14. package/esm/storage/storage.js +103 -0
  15. package/esm/tl/1_tl_object.d.ts +0 -1
  16. package/esm/tl/1_tl_object.js +1 -1
  17. package/esm/tl/4_tl_writer.d.ts +5 -0
  18. package/esm/tl/5_rpc_result.d.ts +9 -0
  19. package/esm/tl/6_message.d.ts +10 -0
  20. package/esm/tl/{5_message.js → 6_message.js} +2 -2
  21. package/esm/tl/{6_message_container.d.ts → 7_message_container.d.ts} +1 -1
  22. package/esm/tl/{6_message_container.js → 7_message_container.js} +2 -2
  23. package/esm/types/3_message.d.ts +5 -1
  24. package/esm/types/3_message.js +26 -22
  25. package/esm/utilities/1_message.d.ts +2 -2
  26. package/esm/utilities/1_message.js +3 -3
  27. package/esm/utilities/1_password.js +1 -1
  28. package/package.json +1 -1
  29. package/script/client/client.d.ts +23 -3
  30. package/script/client/client.js +495 -75
  31. package/script/constants.d.ts +2 -0
  32. package/script/constants.js +3 -1
  33. package/script/deps/deno.land/std@0.190.0/encoding/base64.d.ts +11 -0
  34. package/script/deps/deno.land/std@0.190.0/encoding/base64.js +145 -0
  35. package/script/deps/deno.land/x/q@v0.0.1/mod.d.ts +6 -0
  36. package/script/deps/deno.land/x/q@v0.0.1/mod.js +75 -0
  37. package/script/deps.d.ts +2 -0
  38. package/script/deps.js +6 -1
  39. package/script/mod.d.ts +3 -3
  40. package/script/mod.js +3 -3
  41. package/script/storage/storage.d.ts +18 -0
  42. package/script/storage/storage.js +126 -0
  43. package/script/tl/1_tl_object.d.ts +0 -1
  44. package/script/tl/1_tl_object.js +1 -1
  45. package/script/tl/4_tl_writer.d.ts +5 -0
  46. package/script/tl/5_rpc_result.d.ts +9 -0
  47. package/script/tl/6_message.d.ts +10 -0
  48. package/script/tl/{5_message.js → 6_message.js} +6 -6
  49. package/script/tl/{6_message_container.d.ts → 7_message_container.d.ts} +1 -1
  50. package/script/tl/{6_message_container.js → 7_message_container.js} +4 -4
  51. package/script/types/3_message.d.ts +5 -1
  52. package/script/types/3_message.js +26 -22
  53. package/script/utilities/1_message.d.ts +2 -2
  54. package/script/utilities/1_message.js +9 -9
  55. package/script/utilities/1_password.js +1 -1
  56. package/esm/tl/3_tl_writer.d.ts +0 -5
  57. package/esm/tl/4_rpc_result.d.ts +0 -8
  58. package/esm/tl/5_message.d.ts +0 -11
  59. package/script/tl/3_tl_writer.d.ts +0 -5
  60. package/script/tl/4_rpc_result.d.ts +0 -8
  61. package/script/tl/5_message.d.ts +0 -11
  62. /package/esm/tl/{3_tl_writer.js → 4_tl_writer.js} +0 -0
  63. /package/esm/tl/{4_rpc_result.js → 5_rpc_result.js} +0 -0
  64. /package/script/tl/{3_tl_writer.js → 4_tl_writer.js} +0 -0
  65. /package/script/tl/{4_rpc_result.js → 5_rpc_result.js} +0 -0
@@ -1,5 +1,5 @@
1
- import { debug, gunzip } from "../deps.js";
2
- import { ackThreshold, DEFAULT_APP_VERSION, DEFAULT_DEVICE_MODEL, DEFAULT_INITIAL_DC, DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, DEFAULT_SYSTEM_LANG_CODE, DEFAULT_SYSTEM_VERSION, LAYER, MAX_CHANNEL_ID, MAX_CHAT_ID, USERNAME_TTL, ZERO_CHANNEL_ID } from "../constants.js";
1
+ import { debug, gunzip, Mutex, queue } from "../deps.js";
2
+ import { ackThreshold, CHANNEL_DIFFERENCE_LIMIT_BOT, CHANNEL_DIFFERENCE_LIMIT_USER, DEFAULT_APP_VERSION, DEFAULT_DEVICE_MODEL, DEFAULT_INITIAL_DC, DEFAULT_LANG_CODE, DEFAULT_LANG_PACK, DEFAULT_SYSTEM_LANG_CODE, DEFAULT_SYSTEM_VERSION, LAYER, MAX_CHANNEL_ID, MAX_CHAT_ID, USERNAME_TTL, ZERO_CHANNEL_ID } from "../constants.js";
3
3
  import { bigIntFromBuffer, getRandomBigInt, getRandomId } from "../utilities/0_bigint.js";
4
4
  import { UNREACHABLE } from "../utilities/0_control.js";
5
5
  import { sha1 } from "../utilities/0_hash.js";
@@ -10,9 +10,9 @@ import { as } from "../tl/1_tl_object.js";
10
10
  import * as types from "../tl/2_types.js";
11
11
  import * as functions from "../tl/3_functions.js";
12
12
  import { TLReader } from "../tl/3_tl_reader.js";
13
- import { RPCResult } from "../tl/4_rpc_result.js";
14
- import { Message } from "../tl/5_message.js";
15
- import { MessageContainer } from "../tl/6_message_container.js";
13
+ import { RPCResult } from "../tl/5_rpc_result.js";
14
+ import { Message as Message_ } from "../tl/6_message.js"; // MTProto API message
15
+ import { MessageContainer } from "../tl/7_message_container.js";
16
16
  import { ClientAbstract } from "./client_abstract.js";
17
17
  import { ClientPlain } from "./client_plain.js";
18
18
  import { StorageMemory } from "../storage/storage_memory.js";
@@ -21,8 +21,12 @@ import { replyKeyboardRemoveToTlObject } from "../types/0_reply_keyboard_remove.
21
21
  import { forceReplyToTlObject } from "../types/0_force_reply.js";
22
22
  import { replyKeyboardMarkupToTlObject } from "../types/2_reply_keyboard_markup.js";
23
23
  import { inlineKeyboardMarkupToTlObject } from "../types/2_inline_keyboard_markup.js";
24
- import { constructMessage } from "../types/3_message.js";
24
+ import { constructMessage } from "../types/3_message.js"; // high-level wrapper for Telegram API's message
25
25
  const d = debug("client");
26
+ const dRecoverUpdateGap = debug("client_recoverUpdateGap");
27
+ const dRecoverChannelUpdateGap = debug("client_recoverChannelUpdateGap");
28
+ const UPDATE_GAP = Symbol();
29
+ export const getEntity = Symbol();
26
30
  export const restartAuth = Symbol();
27
31
  export var ParseMode;
28
32
  (function (ParseMode) {
@@ -88,7 +92,13 @@ export class Client extends ClientAbstract {
88
92
  writable: true,
89
93
  value: new Set()
90
94
  });
91
- Object.defineProperty(this, "updatesHandler", {
95
+ Object.defineProperty(this, "updateState", {
96
+ enumerable: true,
97
+ configurable: true,
98
+ writable: true,
99
+ value: void 0
100
+ });
101
+ Object.defineProperty(this, "updateHandler", {
92
102
  enumerable: true,
93
103
  configurable: true,
94
104
  writable: true,
@@ -142,6 +152,79 @@ export class Client extends ClientAbstract {
142
152
  writable: true,
143
153
  value: false
144
154
  });
155
+ Object.defineProperty(this, "messageProcessQueue", {
156
+ enumerable: true,
157
+ configurable: true,
158
+ writable: true,
159
+ value: queue(async (message) => {
160
+ let body = message.body;
161
+ if (body instanceof types.GZIPPacked) {
162
+ body = new TLReader(gunzip(body.packedData)).readObject();
163
+ }
164
+ d("received %s", body.constructor.name);
165
+ if (body instanceof types.Updates || body instanceof types.TypeUpdate) {
166
+ await this.processUpdates(body);
167
+ }
168
+ else if (message.body instanceof RPCResult) {
169
+ let result = message.body.result;
170
+ if (result instanceof types.GZIPPacked) {
171
+ result = new TLReader(gunzip(result.packedData)).readObject();
172
+ }
173
+ if (result instanceof types.RPCError) {
174
+ d("RPCResult: %d %s", result.errorCode, result.errorMessage);
175
+ }
176
+ else {
177
+ d("RPCResult: %s", result.constructor.name);
178
+ }
179
+ if (result instanceof types.Updates || result instanceof types.TypeUpdate) {
180
+ await this.processUpdates(result);
181
+ }
182
+ else {
183
+ await this.processResult(result);
184
+ }
185
+ const promise = this.promises.get(message.body.messageId);
186
+ if (promise) {
187
+ if (result instanceof types.RPCError) {
188
+ promise.reject(result);
189
+ }
190
+ else {
191
+ promise.resolve(result);
192
+ }
193
+ this.promises.delete(message.body.messageId);
194
+ }
195
+ }
196
+ else if (message.body instanceof types.Pong) {
197
+ const promise = this.promises.get(message.body.msgId);
198
+ if (promise) {
199
+ promise.resolve(message.body);
200
+ this.promises.delete(message.body.msgId);
201
+ }
202
+ }
203
+ else if (message.body instanceof types.BadMsgNotification || message.body instanceof types.BadServerSalt) {
204
+ if (message.body instanceof types.BadServerSalt) {
205
+ this.state.salt = message.body.newServerSalt;
206
+ }
207
+ const promise = this.promises.get(message.body.badMsgId);
208
+ if (promise) {
209
+ promise.resolve(message.body);
210
+ this.promises.delete(message.body.badMsgId);
211
+ }
212
+ }
213
+ this.toAcknowledge.add(message.id);
214
+ }, 2)
215
+ });
216
+ Object.defineProperty(this, "updateApplicationMutex", {
217
+ enumerable: true,
218
+ configurable: true,
219
+ writable: true,
220
+ value: new Mutex()
221
+ });
222
+ Object.defineProperty(this, "updateGapRecoveryMutex", {
223
+ enumerable: true,
224
+ configurable: true,
225
+ writable: true,
226
+ value: new Mutex()
227
+ });
145
228
  this.parseMode = params?.parseMode ?? ParseMode.None;
146
229
  this.appVersion = params?.appVersion ?? DEFAULT_APP_VERSION;
147
230
  this.deviceModel = params?.deviceModel ?? DEFAULT_DEVICE_MODEL;
@@ -211,6 +294,11 @@ export class Client extends ClientAbstract {
211
294
  this.receiveLoop();
212
295
  this.pingLoop();
213
296
  }
297
+ async fetchState(source) {
298
+ const state = await this.invoke(new functions.UpdatesGetState());
299
+ this.updateState = state;
300
+ d("state fetched [%s]", source);
301
+ }
214
302
  /**
215
303
  * Calls [initConnection](1) and authorizes the client with one of the following:
216
304
  *
@@ -260,6 +348,7 @@ export class Client extends ClientAbstract {
260
348
  const password = typeof params.password === "string" ? params.password : await params.password();
261
349
  const input = await checkPassword(password, ap);
262
350
  await this.invoke(new functions.AuthCheckPassword({ password: input }));
351
+ await this.storage.setAccountType("user");
263
352
  d("authorized as user");
264
353
  break;
265
354
  }
@@ -282,7 +371,7 @@ export class Client extends ClientAbstract {
282
371
  }
283
372
  };
284
373
  try {
285
- await this.invoke(new functions.UpdatesGetState());
374
+ await this.fetchState("authorize");
286
375
  d("already authorized");
287
376
  return;
288
377
  }
@@ -323,6 +412,7 @@ export class Client extends ClientAbstract {
323
412
  }
324
413
  else {
325
414
  signedIn = true;
415
+ await this.storage.setAccountType("user");
326
416
  d("authorized as user");
327
417
  break;
328
418
  }
@@ -357,6 +447,7 @@ export class Client extends ClientAbstract {
357
447
  }
358
448
  else {
359
449
  await this.invoke(new functions.AuthImportBotAuthorization({ apiId: this.apiId, apiHash: this.apiHash, botAuthToken: params, flags: 0 }));
450
+ await this.storage.setAccountType("bot");
360
451
  d("authorized as bot");
361
452
  }
362
453
  }
@@ -384,6 +475,14 @@ export class Client extends ClientAbstract {
384
475
  throw err;
385
476
  }
386
477
  }
478
+ finally {
479
+ try {
480
+ await this.fetchState("authorize");
481
+ }
482
+ catch (_err) {
483
+ //
484
+ }
485
+ }
387
486
  }
388
487
  async receiveLoop() {
389
488
  if (!this.auth) {
@@ -416,58 +515,7 @@ export class Client extends ClientAbstract {
416
515
  }
417
516
  const messages = decrypted instanceof MessageContainer ? decrypted.messages : [decrypted];
418
517
  for (const message of messages) {
419
- let body = message.body;
420
- if (body instanceof types.GZIPPacked) {
421
- body = new TLReader(gunzip(body.packedData)).readObject();
422
- }
423
- d("received %s", body.constructor.name);
424
- if (body instanceof types.Updates) {
425
- this.processUpdates(body);
426
- }
427
- else if (message.body instanceof RPCResult) {
428
- let result = message.body.result;
429
- if (result instanceof types.GZIPPacked) {
430
- result = new TLReader(gunzip(result.packedData)).readObject();
431
- }
432
- if (result instanceof types.RPCError) {
433
- d("RPCResult: %d %s", result.errorCode, result.errorMessage);
434
- }
435
- else {
436
- d("RPCResult: %s", result.constructor.name);
437
- }
438
- const promise = this.promises.get(message.body.messageId);
439
- if (promise) {
440
- if (result instanceof types.RPCError) {
441
- promise.reject(result);
442
- }
443
- else {
444
- if (result instanceof types.Updates) {
445
- await this.processChats(result.chats);
446
- await this.processUsers(result.users);
447
- }
448
- promise.resolve(result);
449
- }
450
- this.promises.delete(message.body.messageId);
451
- }
452
- }
453
- else if (message.body instanceof types.Pong) {
454
- const promise = this.promises.get(message.body.msgId);
455
- if (promise) {
456
- promise.resolve(message.body);
457
- this.promises.delete(message.body.msgId);
458
- }
459
- }
460
- else if (message.body instanceof types.BadMsgNotification || message.body instanceof types.BadServerSalt) {
461
- if (message.body instanceof types.BadServerSalt) {
462
- this.state.salt = message.body.newServerSalt;
463
- }
464
- const promise = this.promises.get(message.body.badMsgId);
465
- if (promise) {
466
- promise.resolve(message.body);
467
- this.promises.delete(message.body.badMsgId);
468
- }
469
- }
470
- this.toAcknowledge.add(message.id);
518
+ this.messageProcessQueue.push(message);
471
519
  }
472
520
  }
473
521
  }
@@ -491,7 +539,7 @@ export class Client extends ClientAbstract {
491
539
  seqNo++;
492
540
  this.state.seqNo++;
493
541
  }
494
- const message = new Message(getMessageId(), seqNo, function_);
542
+ const message = new Message_(getMessageId(), seqNo, function_);
495
543
  await this.transport.send(await encryptMessage(message, this.auth.key, this.auth.id, this.state.salt, this.sessionId));
496
544
  d("invoked %s", function_.constructor.name);
497
545
  if (noWait) {
@@ -516,6 +564,7 @@ export class Client extends ClientAbstract {
516
564
  async processChats(chats) {
517
565
  for (const chat of chats) {
518
566
  if (chat instanceof types.Channel && chat.accessHash) {
567
+ await this.storage.setEntity(chat);
519
568
  await this.storage.setChannelAccessHash(chat.id, chat.accessHash);
520
569
  if (chat.username) {
521
570
  await this.storage.updateUsernames("channel", chat.id, [chat.username]);
@@ -524,11 +573,15 @@ export class Client extends ClientAbstract {
524
573
  await this.storage.updateUsernames("channel", chat.id, chat.usernames.map((v) => v[as](types.Username)).map((v) => v.username));
525
574
  }
526
575
  }
576
+ else if (chat instanceof types.Chat) {
577
+ await this.storage.setEntity(chat);
578
+ }
527
579
  }
528
580
  }
529
581
  async processUsers(users) {
530
582
  for (const user of users) {
531
583
  if (user instanceof types.User && user.accessHash) {
584
+ await this.storage.setEntity(user);
532
585
  await this.storage.setUserAccessHash(user.id, user.accessHash);
533
586
  if (user.username) {
534
587
  await this.storage.updateUsernames("user", user.id, [user.username]);
@@ -539,19 +592,310 @@ export class Client extends ClientAbstract {
539
592
  }
540
593
  }
541
594
  }
595
+ async applyUpdateNoGap(update, usePts = true) {
596
+ const release = await this.updateApplicationMutex.acquire();
597
+ try {
598
+ if ((update instanceof types.UpdateNewMessage) ||
599
+ (update instanceof types.UpdateDeleteMessages) ||
600
+ (update instanceof types.UpdateReadHistoryInbox) ||
601
+ (update instanceof types.UpdateReadHistoryOutbox) ||
602
+ (update instanceof types.UpdateWebPage) ||
603
+ (update instanceof types.UpdateReadMessagesContents) ||
604
+ (update instanceof types.UpdateEditMessage) ||
605
+ (update instanceof types.UpdateFolderPeers) ||
606
+ (update instanceof types.UpdatePinnedMessages) ||
607
+ (update instanceof types.UpdatePinnedChannelMessages) ||
608
+ (update instanceof types.UpdateShortMessage) ||
609
+ (update instanceof types.UpdateShortChatMessage) ||
610
+ (update instanceof types.UpdateShortSentMessage)) {
611
+ const localState = await this.getLocalState();
612
+ if (localState.pts + update.ptsCount > update.pts) {
613
+ // the update is already applied
614
+ return;
615
+ }
616
+ else if (localState.pts + update.ptsCount < update.pts) {
617
+ // there is an update gap that needs to be filled
618
+ throw UPDATE_GAP;
619
+ }
620
+ localState.pts = update.pts;
621
+ d("applied update with pts %d", update.pts);
622
+ await this.storage.setState(localState);
623
+ }
624
+ else if (usePts &&
625
+ ((update instanceof types.UpdateNewChannelMessage) ||
626
+ (update instanceof types.UpdateDeleteChannelMessages) ||
627
+ (update instanceof types.UpdateEditChannelMessage) ||
628
+ (update instanceof types.UpdateChannelWebPage))) {
629
+ const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message[as](types.Message).peerId[as](types.PeerChannel).channelId : update.channelId;
630
+ let localPts = await this.storage.getChannelPts(channelId);
631
+ if (!localPts) {
632
+ localPts = update.pts - update.ptsCount;
633
+ }
634
+ if (localPts + update.ptsCount > update.pts) {
635
+ // already applied
636
+ return;
637
+ }
638
+ else if (localPts + update.ptsCount < update.pts) {
639
+ // should call channelGetDifference
640
+ throw UPDATE_GAP;
641
+ }
642
+ d("applied update with pts %d", update.pts);
643
+ await this.storage.setChannelPts(channelId, update.pts);
644
+ }
645
+ // apply update (call listeners)
646
+ this.updateHandler?.(this, update);
647
+ }
648
+ finally {
649
+ release();
650
+ }
651
+ }
652
+ async applyUpdate(update) {
653
+ if (update instanceof types.TypeUpdates &&
654
+ !(update instanceof types.UpdateShortMessage) &&
655
+ !(update instanceof types.UpdateShortChatMessage) &&
656
+ !(update instanceof types.UpdateShortSentMessage)) {
657
+ // other constructors inheriting Updates are not applicable
658
+ UNREACHABLE();
659
+ }
660
+ if (update instanceof types.TypeUpdate && update instanceof types.UpdateChannelTooLong) {
661
+ // updateChannelTooLong is not applicable
662
+ UNREACHABLE();
663
+ }
664
+ // can't apply updates when filling gap
665
+ const release = await this.updateGapRecoveryMutex.acquire();
666
+ try {
667
+ await this.applyUpdateNoGap(update);
668
+ release();
669
+ }
670
+ catch (err) {
671
+ release();
672
+ if (err == UPDATE_GAP) {
673
+ if ((update instanceof types.UpdateNewChannelMessage) ||
674
+ (update instanceof types.UpdateDeleteChannelMessages) ||
675
+ (update instanceof types.UpdateEditChannelMessage) ||
676
+ (update instanceof types.UpdateChannelWebPage)) {
677
+ const channelId = update instanceof types.UpdateNewChannelMessage || update instanceof types.UpdateEditChannelMessage ? update.message[as](types.Message).peerId[as](types.PeerChannel).channelId : update.channelId;
678
+ await this.recoverChannelUpdateGap(channelId, "applyUpdate");
679
+ }
680
+ else if ((update instanceof types.UpdateNewMessage) ||
681
+ (update instanceof types.UpdateDeleteMessages) ||
682
+ (update instanceof types.UpdateReadHistoryInbox) ||
683
+ (update instanceof types.UpdateReadHistoryOutbox) ||
684
+ (update instanceof types.UpdateWebPage) ||
685
+ (update instanceof types.UpdateReadMessagesContents) ||
686
+ (update instanceof types.UpdateEditMessage) ||
687
+ (update instanceof types.UpdateFolderPeers) ||
688
+ (update instanceof types.UpdatePinnedMessages) ||
689
+ (update instanceof types.UpdatePinnedChannelMessages) ||
690
+ (update instanceof types.UpdateShortMessage) ||
691
+ (update instanceof types.UpdateShortChatMessage) ||
692
+ (update instanceof types.UpdateShortSentMessage)) {
693
+ await this.recoverUpdateGap("applyUpdate");
694
+ }
695
+ else {
696
+ // can't detect update gap from other types of updates
697
+ UNREACHABLE();
698
+ }
699
+ // just for integrity
700
+ const release = await this.updateGapRecoveryMutex.acquire();
701
+ try {
702
+ await this.applyUpdateNoGap(update);
703
+ }
704
+ catch (err) {
705
+ if (err == UPDATE_GAP) {
706
+ // the gap must have been filled until now
707
+ UNREACHABLE();
708
+ }
709
+ else {
710
+ throw err;
711
+ }
712
+ }
713
+ finally {
714
+ release();
715
+ }
716
+ }
717
+ else {
718
+ throw err;
719
+ }
720
+ }
721
+ }
542
722
  async processUpdates(updates) {
543
723
  try {
544
- await this.processChats(updates.chats);
545
- await this.processUsers(updates.users);
546
- for (const update of updates.updates) {
547
- if (update instanceof types.UpdateUserName) {
548
- await this.storage.updateUsernames("user", update.userId, update.usernames.map((v) => v[as](types.Username)).map((v) => v.username));
724
+ if (updates instanceof types.TypeUpdates) {
725
+ if (updates instanceof types.Updates) {
726
+ await this.processChats(updates.chats);
727
+ await this.processUsers(updates.users);
728
+ await this.setUpdateStateDate(updates.date);
729
+ for (const update of updates.updates) {
730
+ await this.processUpdates(update);
731
+ }
732
+ }
733
+ else if (updates instanceof types.UpdateShortMessage ||
734
+ updates instanceof types.UpdateShortChatMessage ||
735
+ updates instanceof types.UpdateShortSentMessage) {
736
+ await this.setUpdateStateDate(updates.date);
737
+ await this.applyUpdate(updates);
738
+ }
739
+ else if (updates instanceof types.UpdatesTooLong) {
740
+ await this.recoverUpdateGap("updatesTooLong");
741
+ }
742
+ else if (updates instanceof types.UpdatesCombined) {
743
+ await this.setUpdateStateDate(updates.date);
744
+ await this.processChats(updates.chats);
745
+ await this.processUsers(updates.users);
746
+ for (const update of updates.updates) {
747
+ await this.processUpdates(update);
748
+ }
749
+ }
750
+ }
751
+ else if (updates instanceof types.TypeUpdate && updates instanceof types.UpdateChannelTooLong) {
752
+ if (updates.pts != undefined) {
753
+ await this.storage.setChannelPts(updates.channelId, updates.pts);
754
+ }
755
+ await this.recoverChannelUpdateGap(updates.channelId, "updateChannelTooLong");
756
+ }
757
+ else {
758
+ if (updates instanceof types.UpdateUserName) {
759
+ await this.storage.updateUsernames("user", updates.userId, updates.usernames.map((v) => v[as](types.Username)).map((v) => v.username));
760
+ }
761
+ else if (updates instanceof types.UpdatePtsChanged) {
762
+ await this.fetchState("updatePtsChanged");
763
+ if (this.updateState) {
764
+ await this.storage.setState(this.updateState);
765
+ }
766
+ else {
767
+ UNREACHABLE();
768
+ }
549
769
  }
770
+ await this.applyUpdate(updates);
550
771
  }
551
- await this.updatesHandler?.(this, updates);
552
772
  }
553
773
  catch (err) {
554
- console.error("Error processing updates:", err);
774
+ d("error processing updates: %O", err);
775
+ }
776
+ }
777
+ async setUpdateStateDate(date) {
778
+ const release = await this.updateApplicationMutex.acquire();
779
+ try {
780
+ const localState = await this.getLocalState();
781
+ localState.date = date;
782
+ await this.storage.setState(localState);
783
+ }
784
+ finally {
785
+ release();
786
+ }
787
+ }
788
+ async getLocalState() {
789
+ let localState = await this.storage.getState();
790
+ if (!localState) {
791
+ if (this.updateState) {
792
+ localState = this.updateState;
793
+ await this.storage.setState(localState);
794
+ }
795
+ else {
796
+ UNREACHABLE();
797
+ }
798
+ }
799
+ return localState;
800
+ }
801
+ async recoverUpdateGap(source) {
802
+ dRecoverUpdateGap("recovering from update gap [%s]", source);
803
+ const release = await this.updateGapRecoveryMutex.acquire();
804
+ try {
805
+ let state = await this.getLocalState();
806
+ while (true) {
807
+ const difference = await this.invoke(new functions.UpdatesGetDifference({ pts: state.pts, date: state.date, qts: state.qts }));
808
+ if (difference instanceof types.UpdatesDifference || difference instanceof types.UpdatesDifferenceSlice) {
809
+ await this.processChats(difference.chats);
810
+ await this.processUsers(difference.users);
811
+ for (const message of difference.newMessages) {
812
+ await this.applyUpdateNoGap(new types.UpdateNewMessage({ message, pts: 0, ptsCount: 0 }));
813
+ }
814
+ for (const update of difference.otherUpdates) {
815
+ await this.applyUpdateNoGap(update);
816
+ }
817
+ if (difference instanceof types.UpdatesDifference) {
818
+ dRecoverUpdateGap("recovered from update gap");
819
+ break;
820
+ }
821
+ else if (difference instanceof types.UpdatesDifferenceSlice) {
822
+ state = difference.intermediateState[as](types.UpdatesState);
823
+ }
824
+ else {
825
+ UNREACHABLE();
826
+ }
827
+ }
828
+ else if (difference instanceof types.UpdatesDifferenceTooLong) {
829
+ // stored messages should be invalidated in case we store messages in the future
830
+ state.pts = difference.pts;
831
+ dRecoverUpdateGap("received differenceTooLong");
832
+ }
833
+ else if (difference instanceof types.UpdatesDifferenceEmpty) {
834
+ await this.setUpdateStateDate(difference.date);
835
+ dRecoverUpdateGap("there was no update gap");
836
+ break;
837
+ }
838
+ else {
839
+ UNREACHABLE();
840
+ }
841
+ }
842
+ }
843
+ finally {
844
+ release();
845
+ }
846
+ }
847
+ async recoverChannelUpdateGap(channelId, source) {
848
+ dRecoverChannelUpdateGap("recovering channel update gap [%o, %s]", channelId, source);
849
+ const release = await this.updateGapRecoveryMutex.acquire();
850
+ try {
851
+ const pts_ = await this.storage.getChannelPts(channelId);
852
+ let pts = pts_ == null ? 1 : pts_;
853
+ while (true) {
854
+ const { accessHash } = await this.getInputPeer(ZERO_CHANNEL_ID + -Number(channelId)).then((v) => v[as](types.InputPeerChannel));
855
+ const difference = await this.invoke(new functions.UpdatesGetChannelDifference({
856
+ pts,
857
+ channel: new types.InputChannel({ channelId, accessHash: accessHash }),
858
+ filter: new types.ChannelMessagesFilterEmpty(),
859
+ limit: await this.storage.getAccountType() == "user" ? CHANNEL_DIFFERENCE_LIMIT_USER : CHANNEL_DIFFERENCE_LIMIT_BOT,
860
+ }));
861
+ if (difference instanceof types.UpdatesChannelDifference) {
862
+ await this.processChats(difference.chats);
863
+ await this.processUsers(difference.users);
864
+ for (const message of difference.newMessages) {
865
+ await this.applyUpdateNoGap(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), false);
866
+ }
867
+ for (const update of difference.otherUpdates) {
868
+ await this.applyUpdateNoGap(update, false);
869
+ }
870
+ await this.storage.setChannelPts(channelId, difference.pts);
871
+ dRecoverChannelUpdateGap("recovered from update gap");
872
+ break;
873
+ }
874
+ else if (difference instanceof types.UpdatesChannelDifferenceTooLong) {
875
+ // invalidate messages
876
+ dRecoverChannelUpdateGap("received channelDifferenceTooLong");
877
+ await this.processChats(difference.chats);
878
+ await this.processUsers(difference.users);
879
+ for (const message of difference.messages) {
880
+ await this.applyUpdateNoGap(new types.UpdateNewChannelMessage({ message, pts: 0, ptsCount: 0 }), false);
881
+ }
882
+ const pts_ = difference.dialog[as](types.Dialog).pts;
883
+ if (pts_ != undefined) {
884
+ pts = pts_;
885
+ }
886
+ else {
887
+ UNREACHABLE();
888
+ }
889
+ dRecoverChannelUpdateGap("processed channelDifferenceTooLong");
890
+ }
891
+ else if (difference instanceof types.UpdatesChannelDifferenceEmpty) {
892
+ dRecoverChannelUpdateGap("there was no update gap");
893
+ break;
894
+ }
895
+ }
896
+ }
897
+ finally {
898
+ release();
555
899
  }
556
900
  }
557
901
  async getInputPeer(id) {
@@ -620,6 +964,50 @@ export class Client extends ClientAbstract {
620
964
  throw new Error("ID format unknown or not implemented");
621
965
  }
622
966
  }
967
+ [getEntity](peer) {
968
+ const type = peer instanceof types.PeerUser ? "user" : peer instanceof types.PeerChat ? "chat" : peer instanceof types.PeerChannel ? "channel" : UNREACHABLE();
969
+ const id = peer instanceof types.PeerUser ? peer.userId : peer instanceof types.PeerChat ? peer.chatId : peer instanceof types.PeerChannel ? peer.channelId : UNREACHABLE();
970
+ return this.storage.getEntity(type, id);
971
+ }
972
+ async processResult(result) {
973
+ if (result instanceof types.MessagesDialogs ||
974
+ result instanceof types.MessagesDialogsSlice ||
975
+ result instanceof types.MessagesMessages ||
976
+ result instanceof types.MessagesMessagesSlice ||
977
+ result instanceof types.MessagesChannelMessages ||
978
+ result instanceof types.MessagesChatFull ||
979
+ result instanceof types.ContactsFound ||
980
+ result instanceof types.AccountPrivacyRules ||
981
+ result instanceof types.ContactsResolvedPeer ||
982
+ result instanceof types.ChannelsChannelParticipants ||
983
+ result instanceof types.ChannelsChannelParticipant ||
984
+ result instanceof types.MessagesPeerDialogs ||
985
+ result instanceof types.ContactsTopPeers ||
986
+ result instanceof types.ChannelsAdminLogResults ||
987
+ result instanceof types.HelpRecentMeURLs ||
988
+ result instanceof types.MessagesInactiveChats ||
989
+ result instanceof types.HelpPromoData ||
990
+ result instanceof types.MessagesMessageViews ||
991
+ result instanceof types.MessagesDiscussionMessage ||
992
+ result instanceof types.PhoneGroupCall ||
993
+ result instanceof types.PhoneGroupParticipants ||
994
+ result instanceof types.PhoneJoinAsPeers ||
995
+ result instanceof types.MessagesSponsoredMessages ||
996
+ result instanceof types.MessagesSearchResultsCalendar ||
997
+ result instanceof types.ChannelsSendAsPeers ||
998
+ result instanceof types.UsersUserFull ||
999
+ result instanceof types.MessagesPeerSettings ||
1000
+ result instanceof types.MessagesMessageReactionsList ||
1001
+ result instanceof types.MessagesForumTopics ||
1002
+ result instanceof types.AccountAutoSaveSettings ||
1003
+ result instanceof types.ChatlistsExportedInvites ||
1004
+ result instanceof types.ChatlistsChatlistInviteAlready ||
1005
+ result instanceof types.ChatlistsChatlistInvite ||
1006
+ result instanceof types.ChatlistsChatlistUpdates) {
1007
+ await this.processChats(result.chats);
1008
+ await this.processUsers(result.users);
1009
+ }
1010
+ }
623
1011
  async sendMessage(chatId, text, params) {
624
1012
  const entities_ = params?.entities ?? [];
625
1013
  const parseMode = params?.parseMode ?? this.parseMode;
@@ -665,7 +1053,7 @@ export class Client extends ClientAbstract {
665
1053
  const topMsgId = params?.messageThreadId;
666
1054
  const sendAs = params?.sendAs ? await this.getInputPeer(params.sendAs) : undefined;
667
1055
  const entities = entities_?.length > 0 ? entities_.map((v) => messageEntityToTlObject(v)) : undefined;
668
- const updates = await this.invoke(new functions.MessagesSendMessage({
1056
+ const result = await this.invoke(new functions.MessagesSendMessage({
669
1057
  peer,
670
1058
  randomId,
671
1059
  message,
@@ -677,15 +1065,47 @@ export class Client extends ClientAbstract {
677
1065
  sendAs,
678
1066
  entities,
679
1067
  replyMarkup,
680
- })).then((v) => v[as](types.Updates));
681
- for (const update of updates.updates) {
682
- if (update instanceof types.UpdateNewMessage) {
683
- return constructMessage(update.message[as](types.Message), updates.users, updates.chats);
1068
+ }));
1069
+ if (result instanceof types.Updates) {
1070
+ for (const update of result.updates) {
1071
+ if (update instanceof types.UpdateNewMessage) {
1072
+ return constructMessage(update.message[as](types.Message), this[getEntity].bind(this));
1073
+ }
1074
+ else if (update instanceof types.UpdateNewChannelMessage) {
1075
+ return constructMessage(update.message[as](types.Message), this[getEntity].bind(this));
1076
+ }
684
1077
  }
685
- else if (update instanceof types.UpdateNewChannelMessage) {
686
- return constructMessage(update.message[as](types.Message), updates.users, updates.chats);
1078
+ }
1079
+ else if (result instanceof types.UpdateShortSentMessage || result instanceof types.UpdateShortSentMessage) {
1080
+ const message = await this.getMessage(chatId, result.id);
1081
+ if (message != null) {
1082
+ return message;
687
1083
  }
688
1084
  }
689
1085
  UNREACHABLE();
690
1086
  }
1087
+ async getMessages(chatId, messageIds) {
1088
+ const peer = await this.getInputPeer(chatId);
1089
+ let messages_;
1090
+ if (peer instanceof types.InputPeerChannel) {
1091
+ messages_ = await this.invoke(new functions.ChannelsGetMessages({
1092
+ channel: new types.InputChannel({ channelId: peer.channelId, accessHash: peer.accessHash }),
1093
+ id: messageIds.map((v) => new types.InputMessageID({ id: v })),
1094
+ })).then((v) => v[as](types.MessagesMessages));
1095
+ }
1096
+ else {
1097
+ messages_ = await this.invoke(new functions.MessagesGetMessages({
1098
+ id: messageIds.map((v) => new types.InputMessageID({ id: v })),
1099
+ })).then((v) => v[as](types.MessagesMessages));
1100
+ }
1101
+ const messages = new Array();
1102
+ for (const message_ of messages_.messages) {
1103
+ messages.push(await constructMessage(message_[as](types.Message), this[getEntity].bind(this)));
1104
+ }
1105
+ return messages;
1106
+ }
1107
+ async getMessage(chatId, messageId) {
1108
+ const messages = await this.getMessages(chatId, [messageId]);
1109
+ return messages[0] ?? null;
1110
+ }
691
1111
  }