@imessaging/telegram-mtproto 0.8.12 → 0.8.14
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 +2 -2
- package/src/incoming.ts +4 -0
- package/src/telegram-mtproto-transport.ts +97 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imessaging/telegram-mtproto",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.14",
|
|
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.
|
|
34
|
+
"@imessaging/core": "^0.8.12",
|
|
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,
|
|
@@ -27,6 +29,8 @@ import type {
|
|
|
27
29
|
OutboundMessage,
|
|
28
30
|
ReactionTransport,
|
|
29
31
|
ResolvedPeer,
|
|
32
|
+
SelfAccount,
|
|
33
|
+
SelfAwareTransport,
|
|
30
34
|
SendResult,
|
|
31
35
|
TelegramPeerStore,
|
|
32
36
|
TelegramRecipient,
|
|
@@ -108,6 +112,18 @@ const OWN_MEMORY_MS = 10 * 60 * 1000;
|
|
|
108
112
|
/** Номер не занят Telegram или не принят в этой форме — «не найдено», а не отказ. */
|
|
109
113
|
const PHONE_NOT_FOUND_ERRORS = new Set(["PHONE_NOT_OCCUPIED", "PHONE_NUMBER_INVALID"]);
|
|
110
114
|
|
|
115
|
+
/** @username не занят или записан неверно — «не найдено», а не отказ. */
|
|
116
|
+
const USERNAME_NOT_FOUND_ERRORS = new Set(["USERNAME_NOT_OCCUPIED", "USERNAME_INVALID"]);
|
|
117
|
+
|
|
118
|
+
function toPeerInfo(user: Api.User): PeerInfo {
|
|
119
|
+
return {
|
|
120
|
+
id: user.id.toString(),
|
|
121
|
+
username: user.username ?? undefined,
|
|
122
|
+
contact: Boolean(user.contact),
|
|
123
|
+
mutualContact: Boolean(user.mutualContact),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
111
127
|
/** Номер чата входного пира в том же маркированном виде, что chatId во входящем (peerToChat). */
|
|
112
128
|
function inputPeerChatId(peer: Api.TypeInputPeer): string {
|
|
113
129
|
if (peer instanceof Api.InputPeerChannel) return peerToChat({ channelId: peer.channelId }).chatId;
|
|
@@ -146,8 +162,10 @@ export class TelegramMtprotoTransport
|
|
|
146
162
|
IncomingMessageTransport,
|
|
147
163
|
OutgoingObserverTransport,
|
|
148
164
|
OutgoingReadReceiptTransport,
|
|
165
|
+
PeerInfoTransport,
|
|
149
166
|
PhoneLookupTransport,
|
|
150
167
|
ReactionTransport,
|
|
168
|
+
SelfAwareTransport,
|
|
151
169
|
ReadReceiptTransport,
|
|
152
170
|
TypingTransport
|
|
153
171
|
{
|
|
@@ -176,6 +194,8 @@ export class TelegramMtprotoTransport
|
|
|
176
194
|
private readonly ownEdited = new Map<string, number>();
|
|
177
195
|
/** Идущие send/edit: обновление о своём сообщении может прийти раньше, чем вызов вернёт его номер. */
|
|
178
196
|
private readonly ownPending = new Set<Promise<unknown>>();
|
|
197
|
+
/** Своя учётка: не меняется за жизнь сессии, спрашивается один раз. */
|
|
198
|
+
private selfAccount?: Promise<SelfAccount>;
|
|
179
199
|
/** Отметка жизни подписки: срабатывает на любом обновлении, включая служебные. */
|
|
180
200
|
private activityListener: ((event: unknown) => void) | null = null;
|
|
181
201
|
|
|
@@ -718,6 +738,80 @@ export class TelegramMtprotoTransport
|
|
|
718
738
|
this.lastActivityAt = new Date();
|
|
719
739
|
}
|
|
720
740
|
|
|
741
|
+
/** Своя учётка — users.GetUsers(InputUserSelf); удачный ответ кэшируется, отказ — нет. */
|
|
742
|
+
async self(): Promise<SelfAccount> {
|
|
743
|
+
this.assertConnected();
|
|
744
|
+
this.selfAccount ??= this.client
|
|
745
|
+
.invoke(new Api.users.GetUsers({ id: [new Api.InputUserSelf()] }))
|
|
746
|
+
.then(([user]) => {
|
|
747
|
+
if (!user || !isUser(user)) throw new Error("users.GetUsers(self) returned no user");
|
|
748
|
+
return {
|
|
749
|
+
id: user.id.toString(),
|
|
750
|
+
...(user.username ? { username: user.username } : {}),
|
|
751
|
+
isBot: user.bot === true,
|
|
752
|
+
};
|
|
753
|
+
})
|
|
754
|
+
.catch((error: unknown) => {
|
|
755
|
+
this.selfAccount = undefined;
|
|
756
|
+
throw error;
|
|
757
|
+
});
|
|
758
|
+
return this.selfAccount;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/**
|
|
762
|
+
* Контакт ли собеседник и взаимно ли. Пир берётся из хранилища и кэша клиента, иначе по @username;
|
|
763
|
+
* по телефону не ищет — это findByPhone.
|
|
764
|
+
*/
|
|
765
|
+
async peerInfo(recipient: MessageRecipient): Promise<PeerInfo | null> {
|
|
766
|
+
this.assertConnected();
|
|
767
|
+
if (recipient.type !== "user" && recipient.type !== "username") return null;
|
|
768
|
+
const cached = await this.peerStore.get(this.options.accountId, recipient);
|
|
769
|
+
if (cached && cached.type !== "user") return null;
|
|
770
|
+
const peer = cached ? this.toInputPeer(cached) : await this.knownInputPeer(recipient);
|
|
771
|
+
if (peer instanceof Api.InputPeerUser) {
|
|
772
|
+
const [user] = await this.client.invoke(
|
|
773
|
+
new Api.users.GetUsers({
|
|
774
|
+
id: [new Api.InputUser({ userId: peer.userId, accessHash: peer.accessHash })],
|
|
775
|
+
}),
|
|
776
|
+
);
|
|
777
|
+
return user && isUser(user) ? toPeerInfo(user) : null;
|
|
778
|
+
}
|
|
779
|
+
if (peer) return null;
|
|
780
|
+
|
|
781
|
+
const username = recipient.username?.replace(/^@/, "");
|
|
782
|
+
if (!username) return null;
|
|
783
|
+
let result: Api.contacts.ResolvedPeer;
|
|
784
|
+
try {
|
|
785
|
+
result = await this.client.invoke(new Api.contacts.ResolveUsername({ username }));
|
|
786
|
+
} catch (error: unknown) {
|
|
787
|
+
if (USERNAME_NOT_FOUND_ERRORS.has((error as TelegramRpcError).errorMessage ?? ""))
|
|
788
|
+
return null;
|
|
789
|
+
throw error;
|
|
790
|
+
}
|
|
791
|
+
const resolved = result.peer;
|
|
792
|
+
if (!isPeerUser(resolved)) return null;
|
|
793
|
+
const user = result.users.find(
|
|
794
|
+
(candidate): candidate is Api.User =>
|
|
795
|
+
isUser(candidate) && candidate.id.equals(resolved.userId),
|
|
796
|
+
);
|
|
797
|
+
if (!user) return null;
|
|
798
|
+
if (user.accessHash) {
|
|
799
|
+
await this.peerStore.set(
|
|
800
|
+
this.options.accountId,
|
|
801
|
+
recipient,
|
|
802
|
+
{
|
|
803
|
+
type: "user",
|
|
804
|
+
id: user.id.toString(),
|
|
805
|
+
accessHash: user.accessHash.toString(),
|
|
806
|
+
username,
|
|
807
|
+
resolvedAt: new Date().toISOString(),
|
|
808
|
+
},
|
|
809
|
+
this.options.peerTtlSeconds,
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
return toPeerInfo(user);
|
|
813
|
+
}
|
|
814
|
+
|
|
721
815
|
/** Меню команд бота. Сессия обычной учётки получит отказ Telegram: меню бывает только у бота. */
|
|
722
816
|
async setCommands(commands: ChannelCommand[]): Promise<void> {
|
|
723
817
|
this.assertConnected();
|
|
@@ -907,10 +1001,13 @@ export class TelegramMtprotoTransport
|
|
|
907
1001
|
this.rememberOwn(this.ownSent, peer, file.id);
|
|
908
1002
|
}
|
|
909
1003
|
this.lastActivityAt = new Date();
|
|
1004
|
+
// Получатель есть, только если ответ принёс users: короткий ответ лички (UpdateShortSentMessage) их не несёт.
|
|
1005
|
+
const chat = sent.chat;
|
|
910
1006
|
return {
|
|
911
1007
|
transportId: this.id,
|
|
912
1008
|
messageId: String(sent.id),
|
|
913
1009
|
recipientId: this.getPeerId(peer),
|
|
1010
|
+
...(chat instanceof Api.User ? { mutualContact: Boolean(chat.mutualContact) } : {}),
|
|
914
1011
|
};
|
|
915
1012
|
} catch (error: unknown) {
|
|
916
1013
|
if (retried || !this.isInvalidPeerError(error)) throw error;
|