REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY
2
Rich Messages RU
ScuroNeko edited this page 2026-08-19 14:59:10 +03:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Rich-сообщения

Rich-сообщения поддерживают заголовки, абзацы, списки, таблицы, медиа, цитаты, раскрывающиеся секции и формулы. Laniakea поддерживает исходящие деревья InputRichBlock из Bot API 10.2, входящие деревья RichBlock, преобразование в HTML, multipart-загрузки и потоковые черновики.

Пакеты и поток данных

  • tgapi содержит wire-типы Telegram и методы API.
  • tgrich содержит конструкторы tgapi.RichText и tgapi.InputRichBlock, валидацию и преобразование в HTML.
  • Исходящие сообщения используют tgapi.InputRichMessage. Должно быть задано ровно одно из полей Blocks, HTML или Markdown.
  • Входящие сообщения используют Message.RichMessage с узлами tgapi.RichBlock и tgapi.RichText.

Исходящие и входящие типы блоков намеренно разделены. InputRichBlock* описывает данные, принимаемые Telegram, а RichBlock* — нормализованное дерево, возвращаемое Telegram.

Отправка из обработчика

MessageContext.RichAnswer принимает input-блоки и собирает валидированное HTML rich-сообщение:

import (
	"git.scuroneko.dev/scuroneko/laniakea/tgrich"
)

func report(ctx *laniakea.MessageContext, _ struct{}) error {
	ctx.RichAnswer(
		tgrich.H1(tgrich.Text("Дневной отчёт")),
		tgrich.P(tgrich.Concat(
			tgrich.Text("Статус: "),
			tgrich.Bold(tgrich.Text("всё работает")),
		)),
		tgrich.Ul(
			tgrich.NewListItem(tgrich.P(tgrich.Text("бэкапы"))).
				SetCheckbox().SetChecked().Build(),
			tgrich.NewListItem(tgrich.P(tgrich.Text("миграция"))).
				SetCheckbox().Build(),
		),
	)
	return nil
}

RichAnswerKeyboard(keyboard, blocks...) отправляет те же блоки с inline-клавиатурой.

Сборка текста и блоков

Inline-конструкторы: Text, Concat, Bold, Italic, Underline, Strikethrough, Spoiler, Code, Marked, Subscript, Superscript, URL, Email, Phone, TextMention, Mention, Hashtag, Cashtag, BotCommand, Emoji, DateTime, MathExpression, anchors и references.

Блочные конструкторы: P, H1-H6, Pre, CodeBlock, Footer, Hr, Math, Anchor, Ul, Ol, цитаты, коллажи, слайд-шоу, таблицы, details, карты, анимации, аудио, фото, видео, голосовые сообщения и доступный только в черновиках блок Thinking.

Для элементов списков и таблиц используются небольшие builders:

ordered := tgrich.Ol(
	tgrich.OlOpts{Start: 3, Type: tgapi.InputRichBlockListItemTypeLower},
	tgrich.NewListItem(tgrich.P(tgrich.Text("третий"))).Build(),
)

table := tgrich.NewTable(
	tgrich.Row(
		tgrich.CellWithText(tgrich.Text("Имя")).SetHeader().Build(),
		tgrich.CellWithText(tgrich.Text("Значение")).SetHeader().Build(),
	),
	tgrich.Row(
		tgrich.CellWithText(tgrich.Text("Статус")).Build(),
		tgrich.CellWithText(tgrich.Bold(tgrich.Text("ОК"))).Build(),
	),
).SetBordered(true).Build()

Block payload и преобразование в HTML

Если преобразование не требуется, отправляйте input-дерево напрямую:

rich := tgapi.InputRichMessage{
	Blocks: []tgapi.InputRichBlock{
		tgrich.H1(tgrich.Text("Отчёт")),
		tgrich.P(tgrich.Text("Готово")),
	},
}

tgrich.BuildHTML(blocks...) валидирует целое дерево и преобразует его в HTML-вариант InputRichMessage. tgrich.ToHTML(block) — сокращённая форма для одного блока. Преобразование полезно для логов, превью и API вроде MessageContext.RichAnswer, которые отправляют отрендеренный HTML.

BuildHTML включает SkipEntityDetection, экранирует текст и атрибуты и проверяет официальные лимиты Telegram:

  • 32768 UTF-8 символов;
  • 500 блоков, включая вложенные блоки, элементы списков и строки таблиц;
  • 16 общих уровней вложенных блоков и форматирования;
  • 50 media attachments;
  • 20 колонок таблицы с учётом colspan.

Также проверяются discriminators блоков, размеры заголовков, типы маркеров и checkbox-состояние списков, выравнивание и spans таблиц, координаты/zoom/размеры карт и типы медиа.

Медиа и multipart-загрузки

Медиа-конструкторы принимают tgapi.InputMedia, поэтому поле Media может содержать HTTP URL, Telegram file_id или ссылку attach://name:

block := tgrich.PhotoWithCaption(
	tgapi.InputMedia{Media: "telegram-file-id"},
	tgrich.CaptionWithCredit(
		tgrich.Text("Запуск"),
		tgrich.Text("Эксплуатация"),
	),
)

При преобразовании в HTML медиа-блоки превращаются в ссылки tg://photo?id=..., tg://video?id=... или tg://audio?id=.... Исходные значения InputMedia собираются в InputRichMessage.Media со стабильными идентификаторами media_1, media_2, ...

Для multipart-загрузки имя после attach:// должно совпадать с именем multipart-поля:

rich, err := tgrich.BuildHTML(
	tgrich.Photo(tgapi.InputMedia{Media: "attach://report"}),
)
if err != nil {
	return err
}

_, err = uploader.SendRichMessage(
	tgapi.SendRichMessage{ChatID: chatID, RichMessage: rich},
	tgapi.NewUploaderFile("report.jpg", data).SetAttachName("report"),
)

Та же схема загрузки доступна через Uploader.SendRichMessageDraft.

Прямой доступ через API

  • API.SendRichMessage отправляет завершённое rich-сообщение.
  • API.SendRichMessageDraft обновляет эфемерное превью во время генерации. Thinking допустим только в черновиках.
  • Uploader.SendRichMessage и Uploader.SendRichMessageDraft загружают файлы, указанные через attach://.
  • EditMessageText.RichMessage редактирует существующее rich-сообщение; оставьте Text пустым.
  • InputRichMessageContent задаёт rich-контент для inline-результатов.

Приём

Message.RichMessage разбирается автоматически. Обходите принятое дерево через type switch:

for _, block := range msg.RichMessage.Blocks {
	switch b := block.(type) {
	case tgapi.RichBlockWrap: // paragraph, footer, thinking
		handleText(b.Text)
	case tgapi.RichBlockList:
		for _, item := range b.Items {
			// Label — готовый серверный маркер: "1.", "c.", "vii." или "•".
		}
	}
}

Неизвестные будущие узлы с полем text сохраняются как RichTextWrap или RichBlockWrap, поэтому parser переносит совместимые расширения Bot API.

Подводные камни

  • InputRichBlock* и входящие RichBlock* — разные семейства типов.
  • Thinking можно отправлять только через sendRichMessageDraft.
  • У отмеченного элемента списка обязательно должен быть checkbox.
  • Ordered items требуют поддерживаемого типа маркера; unordered items не должны задавать Value.
  • Ширина таблицы учитывает column spans.
  • BuildHTML возвращает ошибки валидации до API-запроса; для их классификации используйте errors.Is с экспортируемыми значениями tgrich.ErrRich*.

Что читать дальше

  • MessageContext — вспомогательные методы ответа.
  • tgapi-Overview — низкоуровневый API-клиент.
  • Drafts — поэтапная генерация сообщений.