@imessaging/telegram-mtproto 0.7.4 → 0.8.1
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/entities.ts +27 -0
- package/src/incoming.ts +11 -7
- package/src/telegram-mtproto-transport.ts +55 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imessaging/telegram-mtproto",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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.
|
|
34
|
+
"@imessaging/core": "^0.8.1",
|
|
35
35
|
"big-integer": "^1.6.52"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
package/src/entities.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Api } from "telegram";
|
|
2
|
+
import type { FormattedText } from "@imessaging/core";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Выделения FormattedText → сущности MTProto. Смещения у обоих в UTF-16, переводить их не нужно.
|
|
6
|
+
*/
|
|
7
|
+
export function toTelegramEntities(ft: FormattedText): Api.TypeMessageEntity[] {
|
|
8
|
+
return ft.entities.map((e) => {
|
|
9
|
+
const base = { offset: e.offset, length: e.length };
|
|
10
|
+
switch (e.type) {
|
|
11
|
+
case "bold":
|
|
12
|
+
return new Api.MessageEntityBold(base);
|
|
13
|
+
case "italic":
|
|
14
|
+
return new Api.MessageEntityItalic(base);
|
|
15
|
+
case "underline":
|
|
16
|
+
return new Api.MessageEntityUnderline(base);
|
|
17
|
+
case "strike":
|
|
18
|
+
return new Api.MessageEntityStrike(base);
|
|
19
|
+
case "code":
|
|
20
|
+
return new Api.MessageEntityCode(base);
|
|
21
|
+
case "pre":
|
|
22
|
+
return new Api.MessageEntityPre({ ...base, language: e.language ?? "" });
|
|
23
|
+
case "link":
|
|
24
|
+
return new Api.MessageEntityTextUrl({ ...base, url: e.url ?? "" });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
package/src/incoming.ts
CHANGED
|
@@ -14,9 +14,11 @@ export type MtprotoMessageLike = {
|
|
|
14
14
|
message?: string;
|
|
15
15
|
/** `true` — сообщение отправлено САМОЙ учётной записью. */
|
|
16
16
|
out?: boolean;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// gramjs отдаёт отсутствующее поле как `null`, а не `undefined`: проверки ниже — на оба.
|
|
18
|
+
date?: number | null;
|
|
19
|
+
editDate?: number | null;
|
|
20
|
+
groupedId?: { toString(): string } | null;
|
|
21
|
+
mentioned?: boolean | null;
|
|
20
22
|
peerId?: MtprotoPeerLike;
|
|
21
23
|
fromId?: MtprotoPeerLike;
|
|
22
24
|
media?: MtprotoMediaLike;
|
|
@@ -31,7 +33,7 @@ export type MtprotoMessageLike = {
|
|
|
31
33
|
replyMarkup?: { rows?: { buttons?: MtprotoButtonLike[] }[] };
|
|
32
34
|
replyTo?: {
|
|
33
35
|
replyToMsgId?: number | string;
|
|
34
|
-
replyToTopId?: number | string;
|
|
36
|
+
replyToTopId?: number | string | null;
|
|
35
37
|
forumTopic?: boolean;
|
|
36
38
|
};
|
|
37
39
|
/** Заголовок форварда. Пусто — сообщение своё, не пересланное. */
|
|
@@ -269,7 +271,7 @@ export function toIncomingMessage(
|
|
|
269
271
|
if (message.replyTo?.forumTopic === true) {
|
|
270
272
|
threadId = Number(message.replyTo.replyToTopId ?? message.replyTo.replyToMsgId);
|
|
271
273
|
replyToMessageId =
|
|
272
|
-
message.replyTo.replyToTopId
|
|
274
|
+
message.replyTo.replyToTopId == null ? undefined : String(message.replyTo.replyToMsgId);
|
|
273
275
|
} else {
|
|
274
276
|
replyToMessageId =
|
|
275
277
|
message.replyTo?.replyToMsgId === undefined
|
|
@@ -292,10 +294,12 @@ export function toIncomingMessage(
|
|
|
292
294
|
messageId: String(message.id),
|
|
293
295
|
albumId: message.groupedId?.toString(),
|
|
294
296
|
text: message.message ?? "",
|
|
295
|
-
sentAt: message.date
|
|
296
|
-
|
|
297
|
+
sentAt: message.date ? new Date(message.date * 1000) : undefined,
|
|
298
|
+
// Непоправленное сообщение приходит с `editDate: null` — это не правка в 1970 году.
|
|
299
|
+
editedAt: message.editDate ? new Date(message.editDate * 1000) : undefined,
|
|
297
300
|
attachments: collectAttachments(message, chatId),
|
|
298
301
|
links: collectLinks(message),
|
|
302
|
+
mentionsMe: typeof message.mentioned === "boolean" ? message.mentioned : undefined,
|
|
299
303
|
replyToMessageId,
|
|
300
304
|
threadId,
|
|
301
305
|
forwardedFrom: toForwardedFrom(message.fwdFrom),
|
|
@@ -17,7 +17,8 @@ import type {
|
|
|
17
17
|
TransportStatus,
|
|
18
18
|
TypingTransport,
|
|
19
19
|
} from "@imessaging/core";
|
|
20
|
-
import { isTelegramRecipient, MemoryPeerStore } from "@imessaging/core";
|
|
20
|
+
import { isTelegramRecipient, MemoryPeerStore, formattedOf } from "@imessaging/core";
|
|
21
|
+
import { toTelegramEntities } from "./entities";
|
|
21
22
|
import { peerToChat, unmarkPeerId } from "./incoming";
|
|
22
23
|
import bigInt from "big-integer";
|
|
23
24
|
import { Api, TelegramClient } from "telegram";
|
|
@@ -42,6 +43,7 @@ export type TelegramChatSummary = {
|
|
|
42
43
|
title: string;
|
|
43
44
|
username?: string;
|
|
44
45
|
unreadCount: number;
|
|
46
|
+
unreadMentions: number;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
export type TelegramMtprotoTransportOptions = {
|
|
@@ -407,7 +409,14 @@ export class TelegramMtprotoTransport
|
|
|
407
409
|
);
|
|
408
410
|
}
|
|
409
411
|
|
|
410
|
-
summaries.push({
|
|
412
|
+
summaries.push({
|
|
413
|
+
chatId,
|
|
414
|
+
chatType,
|
|
415
|
+
title,
|
|
416
|
+
username,
|
|
417
|
+
unreadCount: dialog.unreadCount,
|
|
418
|
+
unreadMentions: dialog.unreadMentionsCount ?? 0,
|
|
419
|
+
});
|
|
411
420
|
if (summaries.length >= limit) break;
|
|
412
421
|
}
|
|
413
422
|
return summaries;
|
|
@@ -416,16 +425,21 @@ export class TelegramMtprotoTransport
|
|
|
416
425
|
/**
|
|
417
426
|
* Последние `limit` сообщений чата, от старых к новым, ВКЛЮЧАЯ свои — это чтение истории, а не приём.
|
|
418
427
|
* Служебные события (вход в группу, закреп) пропускаются: текста и вложений у них нет.
|
|
428
|
+
*
|
|
429
|
+
* @param options.mentionsOnly Только упоминания аккаунта. Фильтр работает на уровне всего чата;
|
|
430
|
+
* вызывающий код должен сам отфильтровать результат по `threadId`.
|
|
419
431
|
*/
|
|
420
432
|
async history(
|
|
421
433
|
recipient: TelegramRecipient,
|
|
422
|
-
options?: { limit?: number; threadId?: number },
|
|
434
|
+
options?: { limit?: number; threadId?: number; mentionsOnly?: boolean },
|
|
423
435
|
): Promise<IncomingMessage[]> {
|
|
424
436
|
this.assertConnected();
|
|
425
437
|
const peer = await this.getInputPeer(recipient);
|
|
438
|
+
// gramjs игнорирует filter, если передан replyTo, поэтому для mentionsOnly replyTo не передаём
|
|
426
439
|
const messages = await this.client.getMessages(peer, {
|
|
427
440
|
limit: options?.limit ?? 30,
|
|
428
|
-
replyTo: options?.threadId,
|
|
441
|
+
replyTo: options?.mentionsOnly ? undefined : options?.threadId,
|
|
442
|
+
filter: options?.mentionsOnly ? new Api.InputMessagesFilterMyMentions() : undefined,
|
|
429
443
|
});
|
|
430
444
|
return [...messages]
|
|
431
445
|
.reverse()
|
|
@@ -439,12 +453,40 @@ export class TelegramMtprotoTransport
|
|
|
439
453
|
);
|
|
440
454
|
}
|
|
441
455
|
|
|
456
|
+
/**
|
|
457
|
+
* Запросить конкретные сообщения по их номерам.
|
|
458
|
+
* Контекст для упоминаний, то есть сообщение, на которое кто-то ответил.
|
|
459
|
+
* Если запросить пустой список, вернёт `[]` без сетевых вызовов (но после проверки соединения).
|
|
460
|
+
*/
|
|
461
|
+
async messagesByIds(recipient: TelegramRecipient, ids: number[]): Promise<IncomingMessage[]> {
|
|
462
|
+
this.assertConnected();
|
|
463
|
+
if (ids.length === 0) return [];
|
|
464
|
+
const peer = await this.getInputPeer(recipient);
|
|
465
|
+
const messages = await this.client.getMessages(peer, { ids });
|
|
466
|
+
return messages
|
|
467
|
+
.filter((message) => message && message.className === "Message")
|
|
468
|
+
.map((message) =>
|
|
469
|
+
toIncomingMessage(
|
|
470
|
+
message as unknown as MtprotoMessageLike,
|
|
471
|
+
this.id,
|
|
472
|
+
this.options.accountId,
|
|
473
|
+
),
|
|
474
|
+
)
|
|
475
|
+
.sort((a, b) => Number(a.messageId) - Number(b.messageId));
|
|
476
|
+
}
|
|
477
|
+
|
|
442
478
|
private async sendWithRetry(message: OutboundMessage, retried: boolean): Promise<SendResult> {
|
|
443
479
|
try {
|
|
444
480
|
const peer = await this.getInputPeer(message.recipient);
|
|
481
|
+
// С `format` уходят готовые выделения: gramjs тогда не разбирает текст своим markdown вовсе.
|
|
482
|
+
const ft = formattedOf(message);
|
|
483
|
+
const text = ft ? ft.text : message.text;
|
|
484
|
+
const parseMode = ft ? undefined : message.parseMode?.toLowerCase();
|
|
485
|
+
const formattingEntities = ft ? toTelegramEntities(ft) : undefined;
|
|
445
486
|
const sent = await this.client.sendMessage(peer, {
|
|
446
|
-
message:
|
|
447
|
-
parseMode
|
|
487
|
+
message: text,
|
|
488
|
+
parseMode,
|
|
489
|
+
formattingEntities,
|
|
448
490
|
replyTo: message.replyToMessageId ?? message.threadId,
|
|
449
491
|
});
|
|
450
492
|
for (const document of message.documents ?? []) {
|
|
@@ -604,12 +646,17 @@ export class TelegramMtprotoTransport
|
|
|
604
646
|
let peer: Api.TypeInputPeer | null = null;
|
|
605
647
|
try {
|
|
606
648
|
peer = await this.getInputPeer(message.recipient);
|
|
649
|
+
const ft = formattedOf(message);
|
|
650
|
+
const text = ft ? ft.text : message.text;
|
|
651
|
+
const parseMode = ft ? undefined : message.parseMode?.toLowerCase();
|
|
652
|
+
const formattingEntities = ft ? toTelegramEntities(ft) : undefined;
|
|
607
653
|
// Кнопки здесь не передаются — ровно как и при отправке этим транспортом: inline-клавиатуру
|
|
608
654
|
// MTProto-аккаунт не рисует. Передать их сюда значило бы обещать разметку, которой не будет.
|
|
609
655
|
await this.client.editMessage(peer, {
|
|
610
656
|
message: messageId,
|
|
611
|
-
text
|
|
612
|
-
parseMode
|
|
657
|
+
text,
|
|
658
|
+
parseMode,
|
|
659
|
+
formattingEntities,
|
|
613
660
|
});
|
|
614
661
|
this.lastActivityAt = new Date();
|
|
615
662
|
return {
|