@imessaging/telegram-mtproto 0.8.12 → 0.8.13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imessaging/telegram-mtproto",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
4
4
  "description": "Telegram MTProto user-account transport for imessaging",
5
5
  "keywords": [
6
6
  "messaging",
@@ -31,7 +31,7 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@imessaging/core": "^0.8.10",
34
+ "@imessaging/core": "^0.8.11",
35
35
  "big-integer": "^1.6.52"
36
36
  },
37
37
  "peerDependencies": {
package/src/incoming.ts CHANGED
@@ -60,6 +60,8 @@ export type MtprotoMessageLike = {
60
60
  lastName?: string;
61
61
  title?: string;
62
62
  accessHash?: { toString(): string } | string | number;
63
+ /** Есть только у пользователя (Api.User). */
64
+ mutualContact?: boolean;
63
65
  };
64
66
  /** Сущность САМОГО чата из обновления: канал, группа или собеседник. Может не прийти. */
65
67
  chat?: {
@@ -325,6 +327,8 @@ export function toIncomingMessage(
325
327
  // связывают человека со своей учётной записью.
326
328
  senderUsername: message.sender?.username,
327
329
  senderName,
330
+ senderMutualContact:
331
+ typeof message.sender?.mutualContact === "boolean" ? message.sender.mutualContact : undefined,
328
332
  chatTitle,
329
333
  chatUsername: message.chat?.username ?? undefined,
330
334
  messageId: String(message.id),
@@ -11,6 +11,8 @@ import type {
11
11
  OutgoingObserverTransport,
12
12
  OutgoingReadReceipt,
13
13
  OutgoingReadReceiptTransport,
14
+ PeerInfo,
15
+ PeerInfoTransport,
14
16
  ButtonPress,
15
17
  ButtonPressTransport,
16
18
  IncomingAttachment,
@@ -108,6 +110,18 @@ const OWN_MEMORY_MS = 10 * 60 * 1000;
108
110
  /** Номер не занят Telegram или не принят в этой форме — «не найдено», а не отказ. */
109
111
  const PHONE_NOT_FOUND_ERRORS = new Set(["PHONE_NOT_OCCUPIED", "PHONE_NUMBER_INVALID"]);
110
112
 
113
+ /** @username не занят или записан неверно — «не найдено», а не отказ. */
114
+ const USERNAME_NOT_FOUND_ERRORS = new Set(["USERNAME_NOT_OCCUPIED", "USERNAME_INVALID"]);
115
+
116
+ function toPeerInfo(user: Api.User): PeerInfo {
117
+ return {
118
+ id: user.id.toString(),
119
+ username: user.username ?? undefined,
120
+ contact: Boolean(user.contact),
121
+ mutualContact: Boolean(user.mutualContact),
122
+ };
123
+ }
124
+
111
125
  /** Номер чата входного пира в том же маркированном виде, что chatId во входящем (peerToChat). */
112
126
  function inputPeerChatId(peer: Api.TypeInputPeer): string {
113
127
  if (peer instanceof Api.InputPeerChannel) return peerToChat({ channelId: peer.channelId }).chatId;
@@ -146,6 +160,7 @@ export class TelegramMtprotoTransport
146
160
  IncomingMessageTransport,
147
161
  OutgoingObserverTransport,
148
162
  OutgoingReadReceiptTransport,
163
+ PeerInfoTransport,
149
164
  PhoneLookupTransport,
150
165
  ReactionTransport,
151
166
  ReadReceiptTransport,
@@ -718,6 +733,60 @@ export class TelegramMtprotoTransport
718
733
  this.lastActivityAt = new Date();
719
734
  }
720
735
 
736
+ /**
737
+ * Контакт ли собеседник и взаимно ли. Пир берётся из хранилища и кэша клиента, иначе по @username;
738
+ * по телефону не ищет — это findByPhone.
739
+ */
740
+ async peerInfo(recipient: MessageRecipient): Promise<PeerInfo | null> {
741
+ this.assertConnected();
742
+ if (recipient.type !== "user" && recipient.type !== "username") return null;
743
+ const cached = await this.peerStore.get(this.options.accountId, recipient);
744
+ if (cached && cached.type !== "user") return null;
745
+ const peer = cached ? this.toInputPeer(cached) : await this.knownInputPeer(recipient);
746
+ if (peer instanceof Api.InputPeerUser) {
747
+ const [user] = await this.client.invoke(
748
+ new Api.users.GetUsers({
749
+ id: [new Api.InputUser({ userId: peer.userId, accessHash: peer.accessHash })],
750
+ }),
751
+ );
752
+ return user && isUser(user) ? toPeerInfo(user) : null;
753
+ }
754
+ if (peer) return null;
755
+
756
+ const username = recipient.username?.replace(/^@/, "");
757
+ if (!username) return null;
758
+ let result: Api.contacts.ResolvedPeer;
759
+ try {
760
+ result = await this.client.invoke(new Api.contacts.ResolveUsername({ username }));
761
+ } catch (error: unknown) {
762
+ if (USERNAME_NOT_FOUND_ERRORS.has((error as TelegramRpcError).errorMessage ?? ""))
763
+ return null;
764
+ throw error;
765
+ }
766
+ const resolved = result.peer;
767
+ if (!isPeerUser(resolved)) return null;
768
+ const user = result.users.find(
769
+ (candidate): candidate is Api.User =>
770
+ isUser(candidate) && candidate.id.equals(resolved.userId),
771
+ );
772
+ if (!user) return null;
773
+ if (user.accessHash) {
774
+ await this.peerStore.set(
775
+ this.options.accountId,
776
+ recipient,
777
+ {
778
+ type: "user",
779
+ id: user.id.toString(),
780
+ accessHash: user.accessHash.toString(),
781
+ username,
782
+ resolvedAt: new Date().toISOString(),
783
+ },
784
+ this.options.peerTtlSeconds,
785
+ );
786
+ }
787
+ return toPeerInfo(user);
788
+ }
789
+
721
790
  /** Меню команд бота. Сессия обычной учётки получит отказ Telegram: меню бывает только у бота. */
722
791
  async setCommands(commands: ChannelCommand[]): Promise<void> {
723
792
  this.assertConnected();
@@ -907,10 +976,13 @@ export class TelegramMtprotoTransport
907
976
  this.rememberOwn(this.ownSent, peer, file.id);
908
977
  }
909
978
  this.lastActivityAt = new Date();
979
+ // Получатель есть, только если ответ принёс users: короткий ответ лички (UpdateShortSentMessage) их не несёт.
980
+ const chat = sent.chat;
910
981
  return {
911
982
  transportId: this.id,
912
983
  messageId: String(sent.id),
913
984
  recipientId: this.getPeerId(peer),
985
+ ...(chat instanceof Api.User ? { mutualContact: Boolean(chat.mutualContact) } : {}),
914
986
  };
915
987
  } catch (error: unknown) {
916
988
  if (retried || !this.isInvalidPeerError(error)) throw error;