@imessaging/telegram-mtproto 0.8.11 → 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 +2 -2
- package/src/incoming.ts +4 -0
- package/src/telegram-mtproto-transport.ts +98 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imessaging/telegram-mtproto",
|
|
3
|
-
"version": "0.8.
|
|
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.
|
|
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),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ChannelCommand,
|
|
3
|
+
CommandMenuTransport,
|
|
2
4
|
ContactCard,
|
|
3
5
|
ContactTransport,
|
|
4
6
|
PhoneLookupOptions,
|
|
@@ -9,6 +11,8 @@ import type {
|
|
|
9
11
|
OutgoingObserverTransport,
|
|
10
12
|
OutgoingReadReceipt,
|
|
11
13
|
OutgoingReadReceiptTransport,
|
|
14
|
+
PeerInfo,
|
|
15
|
+
PeerInfoTransport,
|
|
12
16
|
ButtonPress,
|
|
13
17
|
ButtonPressTransport,
|
|
14
18
|
IncomingAttachment,
|
|
@@ -106,6 +110,18 @@ const OWN_MEMORY_MS = 10 * 60 * 1000;
|
|
|
106
110
|
/** Номер не занят Telegram или не принят в этой форме — «не найдено», а не отказ. */
|
|
107
111
|
const PHONE_NOT_FOUND_ERRORS = new Set(["PHONE_NOT_OCCUPIED", "PHONE_NUMBER_INVALID"]);
|
|
108
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
|
+
|
|
109
125
|
/** Номер чата входного пира в том же маркированном виде, что chatId во входящем (peerToChat). */
|
|
110
126
|
function inputPeerChatId(peer: Api.TypeInputPeer): string {
|
|
111
127
|
if (peer instanceof Api.InputPeerChannel) return peerToChat({ channelId: peer.channelId }).chatId;
|
|
@@ -114,6 +130,14 @@ function inputPeerChatId(peer: Api.TypeInputPeer): string {
|
|
|
114
130
|
throw new Error(`Unsupported input peer: ${peer.className}`);
|
|
115
131
|
}
|
|
116
132
|
|
|
133
|
+
/** Разметка отправки: кнопки, а без них — снятие постоянной клавиатуры по `removeKeyboard`. */
|
|
134
|
+
function sendMarkup(message: OutboundMessage) {
|
|
135
|
+
return (
|
|
136
|
+
toGramJsButtons(message.buttons) ??
|
|
137
|
+
(message.removeKeyboard ? new Api.ReplyKeyboardHide({}) : undefined)
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
117
141
|
function toGramJsButtons(buttons?: OutboundButton[][]) {
|
|
118
142
|
if (!buttons || buttons.length === 0) return undefined;
|
|
119
143
|
return buttons.map((row) =>
|
|
@@ -127,6 +151,7 @@ function toGramJsButtons(buttons?: OutboundButton[][]) {
|
|
|
127
151
|
|
|
128
152
|
export class TelegramMtprotoTransport
|
|
129
153
|
implements
|
|
154
|
+
CommandMenuTransport,
|
|
130
155
|
ContactTransport,
|
|
131
156
|
DraftTransport,
|
|
132
157
|
EditableMessageTransport,
|
|
@@ -135,6 +160,7 @@ export class TelegramMtprotoTransport
|
|
|
135
160
|
IncomingMessageTransport,
|
|
136
161
|
OutgoingObserverTransport,
|
|
137
162
|
OutgoingReadReceiptTransport,
|
|
163
|
+
PeerInfoTransport,
|
|
138
164
|
PhoneLookupTransport,
|
|
139
165
|
ReactionTransport,
|
|
140
166
|
ReadReceiptTransport,
|
|
@@ -707,6 +733,74 @@ export class TelegramMtprotoTransport
|
|
|
707
733
|
this.lastActivityAt = new Date();
|
|
708
734
|
}
|
|
709
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
|
+
|
|
790
|
+
/** Меню команд бота. Сессия обычной учётки получит отказ Telegram: меню бывает только у бота. */
|
|
791
|
+
async setCommands(commands: ChannelCommand[]): Promise<void> {
|
|
792
|
+
this.assertConnected();
|
|
793
|
+
await this.client.invoke(
|
|
794
|
+
new Api.bots.SetBotCommands({
|
|
795
|
+
scope: new Api.BotCommandScopeDefault(),
|
|
796
|
+
langCode: "",
|
|
797
|
+
commands: commands.map(
|
|
798
|
+
(c) => new Api.BotCommand({ command: c.command, description: c.description }),
|
|
799
|
+
),
|
|
800
|
+
}),
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
710
804
|
async disconnect(): Promise<void> {
|
|
711
805
|
await this.client.disconnect();
|
|
712
806
|
this.connected = false;
|
|
@@ -869,7 +963,7 @@ export class TelegramMtprotoTransport
|
|
|
869
963
|
parseMode,
|
|
870
964
|
formattingEntities,
|
|
871
965
|
replyTo: message.replyToMessageId ?? message.threadId,
|
|
872
|
-
buttons:
|
|
966
|
+
buttons: sendMarkup(message),
|
|
873
967
|
});
|
|
874
968
|
this.rememberOwn(this.ownSent, peer, sent.id);
|
|
875
969
|
for (const document of message.documents ?? []) {
|
|
@@ -882,10 +976,13 @@ export class TelegramMtprotoTransport
|
|
|
882
976
|
this.rememberOwn(this.ownSent, peer, file.id);
|
|
883
977
|
}
|
|
884
978
|
this.lastActivityAt = new Date();
|
|
979
|
+
// Получатель есть, только если ответ принёс users: короткий ответ лички (UpdateShortSentMessage) их не несёт.
|
|
980
|
+
const chat = sent.chat;
|
|
885
981
|
return {
|
|
886
982
|
transportId: this.id,
|
|
887
983
|
messageId: String(sent.id),
|
|
888
984
|
recipientId: this.getPeerId(peer),
|
|
985
|
+
...(chat instanceof Api.User ? { mutualContact: Boolean(chat.mutualContact) } : {}),
|
|
889
986
|
};
|
|
890
987
|
} catch (error: unknown) {
|
|
891
988
|
if (retried || !this.isInvalidPeerError(error)) throw error;
|