@imessaging/telegram-mtproto 0.7.3 → 0.8.0

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.7.3",
3
+ "version": "0.8.0",
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.3",
34
+ "@imessaging/core": "^0.8.0",
35
35
  "big-integer": "^1.6.52"
36
36
  },
37
37
  "peerDependencies": {
@@ -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,10 @@ export type MtprotoMessageLike = {
14
14
  message?: string;
15
15
  /** `true` — сообщение отправлено САМОЙ учётной записью. */
16
16
  out?: boolean;
17
- date?: number;
18
- editDate?: number;
19
- groupedId?: { toString(): string };
17
+ // gramjs отдаёт отсутствующее поле как `null`, а не `undefined`: проверки ниже — на оба.
18
+ date?: number | null;
19
+ editDate?: number | null;
20
+ groupedId?: { toString(): string } | null;
20
21
  peerId?: MtprotoPeerLike;
21
22
  fromId?: MtprotoPeerLike;
22
23
  media?: MtprotoMediaLike;
@@ -31,7 +32,7 @@ export type MtprotoMessageLike = {
31
32
  replyMarkup?: { rows?: { buttons?: MtprotoButtonLike[] }[] };
32
33
  replyTo?: {
33
34
  replyToMsgId?: number | string;
34
- replyToTopId?: number | string;
35
+ replyToTopId?: number | string | null;
35
36
  forumTopic?: boolean;
36
37
  };
37
38
  /** Заголовок форварда. Пусто — сообщение своё, не пересланное. */
@@ -53,6 +54,13 @@ export type MtprotoMessageLike = {
53
54
  title?: string;
54
55
  accessHash?: { toString(): string } | string | number;
55
56
  };
57
+ /** Сущность САМОГО чата из обновления: канал, группа или собеседник. Может не прийти. */
58
+ chat?: {
59
+ username?: string;
60
+ firstName?: string;
61
+ lastName?: string;
62
+ title?: string;
63
+ };
56
64
  /**
57
65
  * Пир отправителя ВМЕСТЕ с `accessHash` — так его отдаёт само обновление.
58
66
  *
@@ -251,12 +259,18 @@ export function toIncomingMessage(
251
259
  else if (message.sender.title) senderName = message.sender.title;
252
260
  }
253
261
 
262
+ const chatName = [message.chat?.firstName, message.chat?.lastName]
263
+ .filter(Boolean)
264
+ .join(" ")
265
+ .trim();
266
+ const chatTitle = message.chat?.title || chatName || undefined;
267
+
254
268
  let threadId: number | undefined;
255
269
  let replyToMessageId: string | undefined;
256
270
  if (message.replyTo?.forumTopic === true) {
257
271
  threadId = Number(message.replyTo.replyToTopId ?? message.replyTo.replyToMsgId);
258
272
  replyToMessageId =
259
- message.replyTo.replyToTopId === undefined ? undefined : String(message.replyTo.replyToMsgId);
273
+ message.replyTo.replyToTopId == null ? undefined : String(message.replyTo.replyToMsgId);
260
274
  } else {
261
275
  replyToMessageId =
262
276
  message.replyTo?.replyToMsgId === undefined
@@ -274,11 +288,14 @@ export function toIncomingMessage(
274
288
  // связывают человека со своей учётной записью.
275
289
  senderUsername: message.sender?.username,
276
290
  senderName,
291
+ chatTitle,
292
+ chatUsername: message.chat?.username ?? undefined,
277
293
  messageId: String(message.id),
278
294
  albumId: message.groupedId?.toString(),
279
295
  text: message.message ?? "",
280
- sentAt: message.date !== undefined ? new Date(message.date * 1000) : undefined,
281
- editedAt: message.editDate !== undefined ? new Date(message.editDate * 1000) : undefined,
296
+ sentAt: message.date ? new Date(message.date * 1000) : undefined,
297
+ // Непоправленное сообщение приходит с `editDate: null` — это не правка в 1970 году.
298
+ editedAt: message.editDate ? new Date(message.editDate * 1000) : undefined,
282
299
  attachments: collectAttachments(message, chatId),
283
300
  links: collectLinks(message),
284
301
  replyToMessageId,
@@ -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";
@@ -442,9 +443,15 @@ export class TelegramMtprotoTransport
442
443
  private async sendWithRetry(message: OutboundMessage, retried: boolean): Promise<SendResult> {
443
444
  try {
444
445
  const peer = await this.getInputPeer(message.recipient);
446
+ // С `format` уходят готовые выделения: gramjs тогда не разбирает текст своим markdown вовсе.
447
+ const ft = formattedOf(message);
448
+ const text = ft ? ft.text : message.text;
449
+ const parseMode = ft ? undefined : message.parseMode?.toLowerCase();
450
+ const formattingEntities = ft ? toTelegramEntities(ft) : undefined;
445
451
  const sent = await this.client.sendMessage(peer, {
446
- message: message.text,
447
- parseMode: message.parseMode?.toLowerCase(),
452
+ message: text,
453
+ parseMode,
454
+ formattingEntities,
448
455
  replyTo: message.replyToMessageId ?? message.threadId,
449
456
  });
450
457
  for (const document of message.documents ?? []) {
@@ -604,12 +611,17 @@ export class TelegramMtprotoTransport
604
611
  let peer: Api.TypeInputPeer | null = null;
605
612
  try {
606
613
  peer = await this.getInputPeer(message.recipient);
614
+ const ft = formattedOf(message);
615
+ const text = ft ? ft.text : message.text;
616
+ const parseMode = ft ? undefined : message.parseMode?.toLowerCase();
617
+ const formattingEntities = ft ? toTelegramEntities(ft) : undefined;
607
618
  // Кнопки здесь не передаются — ровно как и при отправке этим транспортом: inline-клавиатуру
608
619
  // MTProto-аккаунт не рисует. Передать их сюда значило бы обещать разметку, которой не будет.
609
620
  await this.client.editMessage(peer, {
610
621
  message: messageId,
611
- text: message.text,
612
- parseMode: message.parseMode?.toLowerCase(),
622
+ text,
623
+ parseMode,
624
+ formattingEntities,
613
625
  });
614
626
  this.lastActivityAt = new Date();
615
627
  return {