@imessaging/telegram-mtproto 0.8.1 → 0.8.3
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 +4 -0
- package/src/incoming.ts +38 -1
- package/src/index.ts +1 -0
- package/src/telegram-mtproto-transport.ts +59 -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.3",
|
|
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.3",
|
|
35
35
|
"big-integer": "^1.6.52"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
package/src/entities.ts
CHANGED
|
@@ -22,6 +22,10 @@ export function toTelegramEntities(ft: FormattedText): Api.TypeMessageEntity[] {
|
|
|
22
22
|
return new Api.MessageEntityPre({ ...base, language: e.language ?? "" });
|
|
23
23
|
case "link":
|
|
24
24
|
return new Api.MessageEntityTextUrl({ ...base, url: e.url ?? "" });
|
|
25
|
+
case "spoiler":
|
|
26
|
+
return new Api.MessageEntitySpoiler(base);
|
|
27
|
+
case "quote":
|
|
28
|
+
return new Api.MessageEntityBlockquote(base);
|
|
25
29
|
}
|
|
26
30
|
});
|
|
27
31
|
}
|
package/src/incoming.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
IncomingAttachment,
|
|
3
|
+
IncomingLink,
|
|
4
|
+
IncomingMessage,
|
|
5
|
+
TextEntity,
|
|
6
|
+
TextEntityType,
|
|
7
|
+
} from "@imessaging/core";
|
|
2
8
|
|
|
3
9
|
/** Пир MTProto в той части, по которой определяется чат. Приходит из `Api.TypePeer`. */
|
|
4
10
|
export type MtprotoPeerLike = {
|
|
@@ -93,6 +99,7 @@ export type MtprotoEntityLike = {
|
|
|
93
99
|
offset?: number;
|
|
94
100
|
length?: number;
|
|
95
101
|
url?: string;
|
|
102
|
+
language?: string;
|
|
96
103
|
};
|
|
97
104
|
|
|
98
105
|
/** Кнопка под сообщением. Адрес несёт только `KeyboardButtonUrl`, у остальных его нет. */
|
|
@@ -233,6 +240,35 @@ export function collectLinks(message: MtprotoMessageLike): IncomingLink[] {
|
|
|
233
240
|
return links;
|
|
234
241
|
}
|
|
235
242
|
|
|
243
|
+
const FORMATTING: Record<string, TextEntityType> = {
|
|
244
|
+
MessageEntityBold: "bold",
|
|
245
|
+
MessageEntityItalic: "italic",
|
|
246
|
+
MessageEntityUnderline: "underline",
|
|
247
|
+
MessageEntityStrike: "strike",
|
|
248
|
+
MessageEntitySpoiler: "spoiler",
|
|
249
|
+
MessageEntityCode: "code",
|
|
250
|
+
MessageEntityPre: "pre",
|
|
251
|
+
MessageEntityTextUrl: "link",
|
|
252
|
+
MessageEntityBlockquote: "quote",
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export function formattingEntities(message: MtprotoMessageLike): TextEntity[] {
|
|
256
|
+
const result: TextEntity[] = [];
|
|
257
|
+
for (const entity of message.entities ?? []) {
|
|
258
|
+
// Упоминания, хэштеги, голые адреса — не форматирование: пары в общей модели у них нет.
|
|
259
|
+
const type = FORMATTING[entity.className ?? ""];
|
|
260
|
+
if (!type || entity.offset === undefined || entity.length === undefined) continue;
|
|
261
|
+
result.push({
|
|
262
|
+
type,
|
|
263
|
+
offset: entity.offset,
|
|
264
|
+
length: entity.length,
|
|
265
|
+
...(type === "link" && entity.url ? { url: entity.url } : {}),
|
|
266
|
+
...(type === "pre" && entity.language ? { language: entity.language } : {}),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
|
|
236
272
|
/**
|
|
237
273
|
* Сообщение живой учётной записи → общий вид.
|
|
238
274
|
*
|
|
@@ -297,6 +333,7 @@ export function toIncomingMessage(
|
|
|
297
333
|
sentAt: message.date ? new Date(message.date * 1000) : undefined,
|
|
298
334
|
// Непоправленное сообщение приходит с `editDate: null` — это не правка в 1970 году.
|
|
299
335
|
editedAt: message.editDate ? new Date(message.editDate * 1000) : undefined,
|
|
336
|
+
entities: formattingEntities(message),
|
|
300
337
|
attachments: collectAttachments(message, chatId),
|
|
301
338
|
links: collectLinks(message),
|
|
302
339
|
mentionsMe: typeof message.mentioned === "boolean" ? message.mentioned : undefined,
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,12 @@ import type {
|
|
|
7
7
|
ReadReceiptTransport,
|
|
8
8
|
EditableMessage,
|
|
9
9
|
EditableMessageTransport,
|
|
10
|
+
EditedMessageTransport,
|
|
10
11
|
EditResult,
|
|
12
|
+
MessageReaction,
|
|
11
13
|
MessageRecipient,
|
|
12
14
|
OutboundMessage,
|
|
15
|
+
ReactionTransport,
|
|
13
16
|
ResolvedPeer,
|
|
14
17
|
SendResult,
|
|
15
18
|
TelegramPeerStore,
|
|
@@ -25,6 +28,7 @@ import { Api, TelegramClient } from "telegram";
|
|
|
25
28
|
import { CustomFile } from "telegram/client/uploads";
|
|
26
29
|
import { CallbackQuery } from "telegram/events/CallbackQuery";
|
|
27
30
|
import { NewMessage } from "telegram/events/NewMessage";
|
|
31
|
+
import { EditedMessage } from "telegram/events/EditedMessage";
|
|
28
32
|
import type { TelegramClientParams } from "telegram/client/telegramBaseClient";
|
|
29
33
|
import { StringSession } from "telegram/sessions";
|
|
30
34
|
|
|
@@ -87,8 +91,10 @@ function isChannel(chat: Api.TypeChat): chat is Api.Channel {
|
|
|
87
91
|
export class TelegramMtprotoTransport
|
|
88
92
|
implements
|
|
89
93
|
EditableMessageTransport,
|
|
94
|
+
EditedMessageTransport,
|
|
90
95
|
ButtonPressTransport,
|
|
91
96
|
IncomingMessageTransport,
|
|
97
|
+
ReactionTransport,
|
|
92
98
|
ReadReceiptTransport,
|
|
93
99
|
TypingTransport
|
|
94
100
|
{
|
|
@@ -99,6 +105,8 @@ export class TelegramMtprotoTransport
|
|
|
99
105
|
private lastActivityAt?: Date;
|
|
100
106
|
private readonly incoming = new IncomingDispatcher();
|
|
101
107
|
private incomingListener: ((event: unknown) => void) | null = null;
|
|
108
|
+
private readonly edits = new IncomingDispatcher();
|
|
109
|
+
private editListener: ((event: unknown) => void) | null = null;
|
|
102
110
|
/** Отметка жизни подписки: срабатывает на любом обновлении, включая служебные. */
|
|
103
111
|
private activityListener: ((event: unknown) => void) | null = null;
|
|
104
112
|
|
|
@@ -184,6 +192,34 @@ export class TelegramMtprotoTransport
|
|
|
184
192
|
};
|
|
185
193
|
}
|
|
186
194
|
|
|
195
|
+
onEdit(handler: (message: IncomingMessage) => void | Promise<void>): () => void {
|
|
196
|
+
const remove = this.edits.add(handler);
|
|
197
|
+
this.startEditListeningIfNeeded();
|
|
198
|
+
return () => {
|
|
199
|
+
remove();
|
|
200
|
+
this.stopEditListeningIfIdle();
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private startEditListeningIfNeeded(): void {
|
|
205
|
+
if (this.editListener !== null || this.edits.size === 0) return;
|
|
206
|
+
const listener = (event: unknown): void => {
|
|
207
|
+
const message = (event as { message?: MtprotoMessageLike }).message;
|
|
208
|
+
if (!message) return;
|
|
209
|
+
this.lastActivityAt = new Date();
|
|
210
|
+
void this.rememberSender(message);
|
|
211
|
+
void this.edits.dispatch(message, this.id, this.options.accountId);
|
|
212
|
+
};
|
|
213
|
+
this.editListener = listener;
|
|
214
|
+
this.client.addEventHandler(listener, new EditedMessage({}));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private stopEditListeningIfIdle(): void {
|
|
218
|
+
if (this.edits.size > 0 || this.editListener === null) return;
|
|
219
|
+
this.client.removeEventHandler(this.editListener, new EditedMessage({}));
|
|
220
|
+
this.editListener = null;
|
|
221
|
+
}
|
|
222
|
+
|
|
187
223
|
private startListeningIfNeeded(): void {
|
|
188
224
|
if (this.incomingListener !== null || this.incoming.size === 0) return;
|
|
189
225
|
const listener = (event: unknown): void => {
|
|
@@ -326,6 +362,29 @@ export class TelegramMtprotoTransport
|
|
|
326
362
|
);
|
|
327
363
|
}
|
|
328
364
|
|
|
365
|
+
async react(reaction: MessageReaction): Promise<void> {
|
|
366
|
+
this.assertConnected();
|
|
367
|
+
if (!isTelegramRecipient(reaction.recipient)) {
|
|
368
|
+
if (reaction.recipient.type === "email") {
|
|
369
|
+
throw new Error("Telegram MTProto cannot react to an email");
|
|
370
|
+
}
|
|
371
|
+
throw new Error(`Telegram MTProto cannot react to ${reaction.recipient.type}`);
|
|
372
|
+
}
|
|
373
|
+
const messageId = Number(reaction.messageId);
|
|
374
|
+
if (!Number.isSafeInteger(messageId) || messageId <= 0) {
|
|
375
|
+
throw new Error(`Telegram message id must be a positive integer: ${reaction.messageId}`);
|
|
376
|
+
}
|
|
377
|
+
const peer = await this.getInputPeer(reaction.recipient);
|
|
378
|
+
await this.client.invoke(
|
|
379
|
+
new Api.messages.SendReaction({
|
|
380
|
+
peer,
|
|
381
|
+
msgId: messageId,
|
|
382
|
+
reaction: reaction.emoji ? [new Api.ReactionEmoji({ emoticon: reaction.emoji })] : [],
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
this.lastActivityAt = new Date();
|
|
386
|
+
}
|
|
387
|
+
|
|
329
388
|
async disconnect(): Promise<void> {
|
|
330
389
|
await this.client.disconnect();
|
|
331
390
|
this.connected = false;
|