@imessaging/telegram-mtproto 0.7.1 → 0.7.2
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 +70 -4
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imessaging/telegram-mtproto",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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.7.
|
|
34
|
+
"@imessaging/core": "^0.7.2",
|
|
35
35
|
"big-integer": "^1.6.52"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
package/src/incoming.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IncomingAttachment, IncomingMessage } from "@imessaging/core";
|
|
1
|
+
import type { IncomingAttachment, IncomingLink, IncomingMessage } from "@imessaging/core";
|
|
2
2
|
|
|
3
3
|
/** Пир MTProto в той части, по которой определяется чат. Приходит из `Api.TypePeer`. */
|
|
4
4
|
export type MtprotoPeerLike = {
|
|
@@ -17,6 +17,15 @@ export type MtprotoMessageLike = {
|
|
|
17
17
|
peerId?: MtprotoPeerLike;
|
|
18
18
|
fromId?: MtprotoPeerLike;
|
|
19
19
|
media?: MtprotoMediaLike;
|
|
20
|
+
/**
|
|
21
|
+
* Разметка текста: жирное, ссылки, упоминания. Отсюда берутся адреса, которых В ТЕКСТЕ НЕТ.
|
|
22
|
+
*
|
|
23
|
+
* Смещения Telegram считает в кодовых единицах UTF-16 — тех же, которыми режет строку JS, так
|
|
24
|
+
* что `slice` по ним точен. Считать в символах значило бы промахиваться на каждом эмодзи.
|
|
25
|
+
*/
|
|
26
|
+
entities?: MtprotoEntityLike[];
|
|
27
|
+
/** Кнопки под сообщением. У поста в канале здесь `ReplyInlineMarkup`, у остальных пусто. */
|
|
28
|
+
replyMarkup?: { rows?: { buttons?: MtprotoButtonLike[] }[] };
|
|
20
29
|
replyTo?: { replyToMsgId?: number | string };
|
|
21
30
|
/** Заголовок форварда. Пусто — сообщение своё, не пересланное. */
|
|
22
31
|
fwdFrom?: {
|
|
@@ -52,6 +61,23 @@ export type MtprotoMediaLike = {
|
|
|
52
61
|
attributes?: { className?: string; fileName?: string }[];
|
|
53
62
|
};
|
|
54
63
|
photo?: unknown;
|
|
64
|
+
/** Предпросмотр страницы. Вложением НЕ считается: скачивать нечего, адрес есть. */
|
|
65
|
+
webpage?: { className?: string; url?: string; displayUrl?: string; title?: string };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** Сущность разметки. `url` есть только у скрытой гиперссылки (`MessageEntityTextUrl`). */
|
|
69
|
+
export type MtprotoEntityLike = {
|
|
70
|
+
className?: string;
|
|
71
|
+
offset?: number;
|
|
72
|
+
length?: number;
|
|
73
|
+
url?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Кнопка под сообщением. Адрес несёт только `KeyboardButtonUrl`, у остальных его нет. */
|
|
77
|
+
export type MtprotoButtonLike = {
|
|
78
|
+
className?: string;
|
|
79
|
+
text?: string;
|
|
80
|
+
url?: string;
|
|
55
81
|
};
|
|
56
82
|
|
|
57
83
|
/**
|
|
@@ -96,9 +122,7 @@ export function unmarkPeerId(chatId: string): string {
|
|
|
96
122
|
* Пир источника МОЖЕТ отсутствовать: автор форварда вправе скрыть его. Тогда остаётся имя, а
|
|
97
123
|
* иногда и оно пустое — и это честнее выдумывания, чем подстановка чата, в котором сообщение лежит.
|
|
98
124
|
*/
|
|
99
|
-
function toForwardedFrom(
|
|
100
|
-
fwdFrom: MtprotoMessageLike["fwdFrom"],
|
|
101
|
-
): IncomingMessage["forwardedFrom"] {
|
|
125
|
+
function toForwardedFrom(fwdFrom: MtprotoMessageLike["fwdFrom"]): IncomingMessage["forwardedFrom"] {
|
|
102
126
|
if (!fwdFrom) return undefined;
|
|
103
127
|
const peer = fwdFrom.fromId ? peerToChat(fwdFrom.fromId) : null;
|
|
104
128
|
return {
|
|
@@ -146,6 +170,47 @@ export function collectAttachments(
|
|
|
146
170
|
return [];
|
|
147
171
|
}
|
|
148
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Адреса из сообщения: текст, скрытые гиперссылки, кнопки, предпросмотр.
|
|
175
|
+
*
|
|
176
|
+
* Разбирается ЗДЕСЬ, а не у потребителя, по одной причине: половины этих адресов в тексте нет.
|
|
177
|
+
* Слово «откликнуться» со скрытой ссылкой, надпись на кнопке и предпросмотр живут в разметке, и
|
|
178
|
+
* поиск регулярным выражением по `text` не находит их ВООБЩЕ — а выглядит это не промахом
|
|
179
|
+
* разбора, а вакансией без ссылки. Замер 16.09.2026 по боевой базе italent: у всех 379 вакансий,
|
|
180
|
+
* приехавших шлюзом, поле адреса пустое, тогда как у 8190 прежних оно заполнено.
|
|
181
|
+
*
|
|
182
|
+
* Повторы снимаются по адресу: один и тот же URL часто и в тексте, и в предпросмотре, и
|
|
183
|
+
* потребитель, считающий их разными, показал бы человеку две одинаковые ссылки.
|
|
184
|
+
*/
|
|
185
|
+
export function collectLinks(message: MtprotoMessageLike): IncomingLink[] {
|
|
186
|
+
const text = message.message ?? "";
|
|
187
|
+
const links: IncomingLink[] = [];
|
|
188
|
+
const seen = new Set<string>();
|
|
189
|
+
const add = (url: string | undefined, origin: IncomingLink["origin"], label?: string): void => {
|
|
190
|
+
const trimmed = url?.trim();
|
|
191
|
+
if (!trimmed || seen.has(trimmed)) return;
|
|
192
|
+
seen.add(trimmed);
|
|
193
|
+
links.push(label ? { url: trimmed, label, origin } : { url: trimmed, origin });
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Порядок — как в сообщении: смещения приходят по возрастанию, но полагаться на это не надо.
|
|
197
|
+
const entities = [...(message.entities ?? [])].sort((a, b) => (a.offset ?? 0) - (b.offset ?? 0));
|
|
198
|
+
for (const entity of entities) {
|
|
199
|
+
const shown = text.slice(entity.offset ?? 0, (entity.offset ?? 0) + (entity.length ?? 0));
|
|
200
|
+
// Голый адрес: Telegram размечает его сам, и сам адрес — это и есть показанный текст.
|
|
201
|
+
if (entity.className === "MessageEntityUrl") add(shown, "text");
|
|
202
|
+
// Скрытая гиперссылка: адрес в поле, а показанное слово — подпись к нему.
|
|
203
|
+
if (entity.className === "MessageEntityTextUrl") add(entity.url, "markup", shown || undefined);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const row of message.replyMarkup?.rows ?? []) {
|
|
207
|
+
for (const button of row.buttons ?? []) add(button.url, "button", button.text);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
add(message.media?.webpage?.url, "preview", message.media?.webpage?.title);
|
|
211
|
+
return links;
|
|
212
|
+
}
|
|
213
|
+
|
|
149
214
|
/**
|
|
150
215
|
* Сообщение живой учётной записи → общий вид.
|
|
151
216
|
*
|
|
@@ -178,6 +243,7 @@ export function toIncomingMessage(
|
|
|
178
243
|
messageId: String(message.id),
|
|
179
244
|
text: message.message ?? "",
|
|
180
245
|
attachments: collectAttachments(message, chatId),
|
|
246
|
+
links: collectLinks(message),
|
|
181
247
|
replyToMessageId:
|
|
182
248
|
message.replyTo?.replyToMsgId === undefined
|
|
183
249
|
? undefined
|
package/src/index.ts
CHANGED
|
@@ -4,12 +4,15 @@ export {
|
|
|
4
4
|
} from "./telegram-mtproto-transport";
|
|
5
5
|
export {
|
|
6
6
|
collectAttachments,
|
|
7
|
+
collectLinks,
|
|
7
8
|
downloadFromMessage,
|
|
8
9
|
IncomingDispatcher,
|
|
9
10
|
peerToChat,
|
|
10
11
|
unmarkPeerId,
|
|
11
12
|
toIncomingMessage,
|
|
12
13
|
type DownloadingClient,
|
|
14
|
+
type MtprotoButtonLike,
|
|
15
|
+
type MtprotoEntityLike,
|
|
13
16
|
type MtprotoMediaLike,
|
|
14
17
|
type MtprotoMessageLike,
|
|
15
18
|
type MtprotoPeerLike,
|