@imessaging/telegram-mtproto 0.8.4 → 0.8.6
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.
|
|
3
|
+
"version": "0.8.6",
|
|
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.6",
|
|
35
35
|
"big-integer": "^1.6.52"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
PhoneLookupOptions,
|
|
3
|
+
PhoneLookupTransport,
|
|
4
|
+
PhoneMatch,
|
|
2
5
|
DraftTransport,
|
|
3
6
|
MessageDraft,
|
|
4
7
|
OutgoingObserverTransport,
|
|
@@ -94,6 +97,9 @@ function isChannel(chat: Api.TypeChat): chat is Api.Channel {
|
|
|
94
97
|
|
|
95
98
|
const OWN_MEMORY_MS = 10 * 60 * 1000;
|
|
96
99
|
|
|
100
|
+
/** Номер не занят Telegram или не принят в этой форме — «не найдено», а не отказ. */
|
|
101
|
+
const PHONE_NOT_FOUND_ERRORS = new Set(["PHONE_NOT_OCCUPIED", "PHONE_NUMBER_INVALID"]);
|
|
102
|
+
|
|
97
103
|
/** Номер чата входного пира в том же маркированном виде, что chatId во входящем (peerToChat). */
|
|
98
104
|
function inputPeerChatId(peer: Api.TypeInputPeer): string {
|
|
99
105
|
if (peer instanceof Api.InputPeerChannel) return peerToChat({ channelId: peer.channelId }).chatId;
|
|
@@ -110,6 +116,7 @@ export class TelegramMtprotoTransport
|
|
|
110
116
|
ButtonPressTransport,
|
|
111
117
|
IncomingMessageTransport,
|
|
112
118
|
OutgoingObserverTransport,
|
|
119
|
+
PhoneLookupTransport,
|
|
113
120
|
ReactionTransport,
|
|
114
121
|
ReadReceiptTransport,
|
|
115
122
|
TypingTransport
|
|
@@ -220,6 +227,87 @@ export class TelegramMtprotoTransport
|
|
|
220
227
|
};
|
|
221
228
|
}
|
|
222
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Учётка по номеру телефона: сначала contacts.ResolvePhone (номер, открытый настройками приватности),
|
|
232
|
+
* затем contacts.ImportContacts (номер виден только контакту) — он и заводит контакт. С `keepContact: false`
|
|
233
|
+
* заведённый контакт удаляется сразу; бывший контактом до поиска не удаляется никогда.
|
|
234
|
+
*/
|
|
235
|
+
async findByPhone(phone: string, options: PhoneLookupOptions = {}): Promise<PhoneMatch | null> {
|
|
236
|
+
this.assertConnected();
|
|
237
|
+
const digits = phone.replace(/[\s()-]/g, "").replace(/^\+/, "");
|
|
238
|
+
if (!/^\d{7,15}$/.test(digits))
|
|
239
|
+
throw new Error(`Phone must be E.164, e.g. +79991234567: ${phone}`);
|
|
240
|
+
|
|
241
|
+
// Одни серверы ждут номер без «+», другие с ним — так вёл поиск и прежний клиент italent.
|
|
242
|
+
for (const variant of [digits, `+${digits}`]) {
|
|
243
|
+
try {
|
|
244
|
+
const resolved = await this.client.invoke(
|
|
245
|
+
new Api.contacts.ResolvePhone({ phone: variant }),
|
|
246
|
+
);
|
|
247
|
+
const peer = resolved.peer;
|
|
248
|
+
const user = isPeerUser(peer)
|
|
249
|
+
? resolved.users.find((u): u is Api.User => isUser(u) && u.id.equals(peer.userId))
|
|
250
|
+
: undefined;
|
|
251
|
+
if (user) return this.rememberPhoneMatch(user);
|
|
252
|
+
} catch (error: unknown) {
|
|
253
|
+
if (!PHONE_NOT_FOUND_ERRORS.has((error as TelegramRpcError).errorMessage ?? ""))
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const known = new Set(
|
|
259
|
+
(await this.client.invoke(new Api.contacts.GetContactIDs({ hash: bigInt(0) }))).map(String),
|
|
260
|
+
);
|
|
261
|
+
const imported = await this.client.invoke(
|
|
262
|
+
new Api.contacts.ImportContacts({
|
|
263
|
+
contacts: [
|
|
264
|
+
new Api.InputPhoneContact({
|
|
265
|
+
clientId: bigInt(Date.now()),
|
|
266
|
+
phone: `+${digits}`,
|
|
267
|
+
firstName: options.firstName?.trim() || `+${digits}`,
|
|
268
|
+
lastName: options.lastName?.trim() ?? "",
|
|
269
|
+
}),
|
|
270
|
+
],
|
|
271
|
+
}),
|
|
272
|
+
);
|
|
273
|
+
const user = imported.users.find((u): u is Api.User => isUser(u));
|
|
274
|
+
if (!user) return null;
|
|
275
|
+
try {
|
|
276
|
+
return await this.rememberPhoneMatch(user);
|
|
277
|
+
} finally {
|
|
278
|
+
if (options.keepContact === false && !known.has(user.id.toString()) && user.accessHash) {
|
|
279
|
+
await this.client.invoke(
|
|
280
|
+
new Api.contacts.DeleteContacts({
|
|
281
|
+
id: [new Api.InputUser({ userId: user.id, accessHash: user.accessHash })],
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async rememberPhoneMatch(user: Api.User): Promise<PhoneMatch> {
|
|
289
|
+
const userId = user.id.toString();
|
|
290
|
+
if (user.accessHash) {
|
|
291
|
+
await this.peerStore.set(
|
|
292
|
+
this.options.accountId,
|
|
293
|
+
{ type: "user", id: userId },
|
|
294
|
+
{
|
|
295
|
+
type: "user",
|
|
296
|
+
id: userId,
|
|
297
|
+
accessHash: user.accessHash.toString(),
|
|
298
|
+
...(user.username ? { username: user.username } : {}),
|
|
299
|
+
resolvedAt: new Date().toISOString(),
|
|
300
|
+
},
|
|
301
|
+
this.options.peerTtlSeconds,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
userId,
|
|
306
|
+
...(user.username ? { username: user.username } : {}),
|
|
307
|
+
...(user.firstName ? { firstName: user.firstName } : {}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
223
311
|
/**
|
|
224
312
|
* Черновик в поле ввода чата — ответ готовит машина, отправляет человек. Разметка — тем же рендером, что у
|
|
225
313
|
* send (`format`); без него текст уходит как есть. Пустой текст очищает черновик.
|