@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imessaging/telegram-mtproto",
3
- "version": "0.8.1",
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.1",
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 { IncomingAttachment, IncomingLink, IncomingMessage } from "@imessaging/core";
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,6 +7,7 @@ export {
7
7
  collectAttachments,
8
8
  collectLinks,
9
9
  downloadFromMessage,
10
+ formattingEntities,
10
11
  IncomingDispatcher,
11
12
  peerToChat,
12
13
  unmarkPeerId,
@@ -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;