aiogram 富文本消息之 InputRichBlockSectionHeading:章节标题块字段、类型体系与 send_rich_message 实战

原创2026-10-12 11:42:301,855 阅读
文章标签:后端即时通讯

aiogram 富文本消息之 InputRichBlockSectionHeading:章节标题块字段、类型体系与 send_rich_message 实战

导读

InputRichBlockSectionHeading 是 aiogram 中用于构建 Telegram 富文本消息(Rich Message)的 24 种输入块之一,它在 Telegram 客户端中渲染为与 HTML <h1> 至 <h6> 对应的章节标题。本文以 docs/api/types/input_rich_block_section_heading.rst 指向的 InputRichBlockSectionHeading 类为核心,完整讲解其 type、text、size 三个字段的语义,梳理它所在的 InputRichBlockUnion 判别联合与 RichTextUnion 文本类型体系,并结合 send_rich_message、answer_rich/reply_rich 快捷方法给出可运行的发送示例。读完本文,你将掌握如何用 aiogram 构造带多级标题的富文本消息,并理解其底层 Pydantic 校验与判别联合的工作原理。

一、什么是 InputRichBlockSectionHeading

在 Telegram Bot API 的富文本消息体系中,InputRichBlockSectionHeading 是一个描述"待发送富文本消息中章节标题"的输入块,它对应 HTML 标签 <h1>、<h2>、<h3>、<h4>、<h5>、<h6>(见 源码 docstring)。

从命名上可以拆解为三层含义:

  • Input:表示它是"输入侧"(待发送)对象,与服务器返回的"输出侧"对象 RichBlockSectionHeading 一一对应;
  • RichBlock:表示它是富文本消息中的一个"块"(Block),消息正文由若干块按顺序排列组成;
  • SectionHeading:表示该块的语义是"章节标题",用于组织长文结构,而非普通段落。

aiogram 中所有输入块都继承自 InputRichBlock 基类。该类 docstring 明确列出了全部 24 种子类型,InputRichBlockSectionHeading 与段落(Paragraph)、预格式化文本(Preformatted)、页脚(Footer)、分隔线(Divider)、引用(BlockQuotation)、列表(List)、表格(Table)等共同构成富文本消息的"积木"集合。

二、字段详解:type / text / size

InputRichBlockSectionHeading 只有三个字段,定义于 aiogram/types/input_rich_block_section_heading.py#L20-L25:

字段 类型 默认值 说明
type Literal[InputRichBlockType.HEADING] "heading" 块的类型,恒为 heading,用于判别联合的标签
text RichTextUnion 无(必填) 标题的文本内容,可以是普通字符串、文本数组或格式化文本对象
size int 无(必填) 字体相对大小,取值 1–6;1 最大,6 最小

1. type:固定标签 "heading"

type 是 Telegram Bot API 所有富文本块的判别字段。在 enums/input_rich_block_type.py 中,枚举 InputRichBlockType 定义了 HEADING = "heading" 等 23 个取值。InputRichBlockSectionHeading 将 type 声明为 Literal[InputRichBlockType.HEADING] 并默认赋值为 "heading",意味着实例化时无需也无法传入其他值——这一"唯一常量标签"正是 Pydantic 判别联合(Discriminated Union)得以高效工作的前提。

2. size:字号等级 1–6

size 是一个 int,取值范围 1–6,语义与 HTML 标题相反:size=1 对应最大的 <h1>,size=6 对应最小的 <h6>。这与 Telegram 服务端渲染的字体相对大小一致。构造时通常与文本内容配套使用,例如 size=1 用于文档主标题、size=2 用于一级章节、size=3 用于二级小节。

3. text:RichTextUnion 标题文本

text 是标题的文字内容,其类型为 RichTextUnion——这是 aiogram 中最灵活的类型之一(详见下文第四节),允许你仅在标题中使用加粗、斜体、代码、链接、@提及、emoji、数学表达式等任意富文本格式。

三、它在类型体系中的位置:InputRichBlockUnion 判别联合

InputRichBlockSectionHeading 不能单独作为消息发送,它必须作为 InputRichMessage.blocks 列表的一员出现。在 aiogram/types/input_rich_block_union.py 中,所有 24 种输入块被组合为一个 Pydantic Annotated 联合类型 InputRichBlockUnion,并指定 Field(discriminator="type"):

InputRichBlockUnion: TypeAlias = Annotated[
    InputRichBlockParagraph
    | InputRichBlockSectionHeading
    | InputRichBlockPreformatted
    | ...
    | InputRichBlockThinking,
    Field(discriminator="type"),
]

这意味着 Pydantic 在校验 blocks 列表中的每个元素时,会直接读取其 type 字段跳转到匹配的子类,而不是逐一尝试所有成员。由于每个子类都持有独一无二的 type 常量(例如 heading 只属于 InputRichBlockSectionHeading),这种判别式校验是确定且线性的。

从源码结构看,这也是 tests/test_issues/test_1842_rich_block_union_discriminator.py 专门设计回归测试的原因:嵌套块(如 blockquote 套 blockquote)若退化为"智能联合"逐个回溯,校验复杂度会呈指数增长;判别联合让 30 层嵌套也能在 1 秒内完成校验。该测试对非法 type 断言了唯一的 union_tag_invalid 错误(定位在 blocks, 0),可直接印证判别联合生效。

因此,当你以字典形式构造 blocks 时,只需保证每个块的 type 正确,aiogram 会自动解析为对应的具体类。

四、text 字段的底层:RichTextUnion

text: RichTextUnion 是标题文本的承载类型。在 aiogram/types/rich_text_union.py 中,RichTextUnion 是一个带自定义判别函数的联合,运行时通过 _rich_text_union_tag 判定:

  • 若传入的是 str,则视为纯文本;
  • 若传入的是 list/tuple,则视为多个文本片段构成的数组;
  • 若传入的是 dict 或对象,则按其 type 字段匹配对应的富文本类型。

可用的富文本类型共有 26 种,包括但不限于 RichTextBold(加粗)、RichTextItalic(斜体)、RichTextCode(代码)、RichTextCustomEmoji(自定义表情)、RichTextUrl(链接)、RichTextTextMention(文本提及)、RichTextSpoiler(剧透隐藏)、RichTextSubscript/RichTextSuperscript(上下标)等(见 rich_text_union.py#L36-L66)。

以 RichTextBold 为例,它与标题块结构相似:固定 type: Literal[RichTextType.BOLD] = "bold",text 字段同样是 RichTextUnion——也就是说富文本对象可以无限嵌套,例如"加粗的斜体"这类组合是完全合法的。

五、实战:构造并发送带标题的富文本消息

1. 承载容器 InputRichMessage

要发送富文本消息,需要先构造 InputRichMessage。其关键字段如下:

字段 类型 说明
html `str None`
markdown `str None`
blocks `list[InputRichBlockUnion] None`
is_rtl `bool None`
skip_entity_detection `bool None`
media `list[InputRichMessageMedia] None`

其 docstring 强调:html、markdown、blocks 三者必须且只能使用其中一个。本文聚焦 blocks 方式。

2. 使用 bot.send_rich_message 发送

Bot.send_rich_message 是发送富文本消息的入口,底层构造 SendRichMessage 方法对象并提交。一个同时包含主标题、二级标题和段落的完整示例:

from aiogram import Bot
from aiogram.types import (
    InputRichMessage,
    InputRichBlockSectionHeading,
    InputRichBlockParagraph,
    RichTextBold,
    RichTextUrl,
)

bot = Bot(token="BOT_TOKEN")

rich_message = InputRichMessage(
    blocks=[
        # 主标题:size=1(最大),标题文本为加粗的纯文本
        InputRichBlockSectionHeading(
            text=RichTextBold(text="aiogram 使用指南"),
            size=1,
        ),
        # 二级标题:size=2,标题文本是一个富文本数组
        InputRichBlockSectionHeading(
            text=["快速上手", RichTextUrl(text="(官方文档)", url="https://docs.aiogram.dev")],
            size=2,
        ),
        # 普通段落
        InputRichBlockParagraph(text="aiogram 是完全异步的 Telegram Bot API 框架。"),
    ]
)

message = await bot.send_rich_message(chat_id=123456789, rich_message=rich_message)

几点说明:

  • InputRichBlockSectionHeading(text=..., size=...) 中 type 会自动取默认值 "heading",无需手动传入;
  • 由于 blocks 是 list[InputRichBlockUnion],直接传入具体类实例即可,Pydantic 会按判别标签解析;
  • 若消息包含媒体块(如 photo/video 块),Bot 需要拥有向该会话发送媒体的权限,见 SendRichMessage docstring;
  • 成功后将返回 Message 对象,其中包含服务端回传的输出侧富文本 RichMessage。

3. 快捷方法:answer_rich 与 reply_rich

aiogram 还提供了从接收对象发起的快捷方法,其完整清单见 docs/api/methods/send_rich_message.rst:

  • Message.answer_rich(...):在收到消息的会话中回复(自动填充 chat_id、message_thread_id、business_connection_id),见 message.py#L5232;
  • Message.reply_rich(...):以引用(reply)方式回复(额外自动填充 reply_parameters 与 ephemeral_message_parameters),见 message.py#L5296;
  • InaccessibleMessage.answer_rich / InaccessibleMessage.reply_rich:用于已不可访问消息的对应快捷方式。

在处理器中可这样使用:

from aiogram import Router
from aiogram.types import Message, InputRichMessage, InputRichBlockSectionHeading

router = Router()

@router.message()
async def send_toc(message: Message) -> None:
    await message.answer_rich(
        InputRichMessage(
            blocks=[
                InputRichBlockSectionHeading(text="目录", size=1),
                InputRichBlockSectionHeading(text="第一章 安装与配置", size=2),
                InputRichBlockSectionHeading(text="第二章 发送消息", size=2),
            ]
        )
    )

若在 Webhook 模式下手动构造方法对象返回,也可以直接 return SendRichMessage(chat_id=..., rich_message=...),aiogram 会自动调用它。

4. 底层调用链小结

await bot.send_rich_message(...) 的实际执行路径为:

  1. Bot.send_rich_message 组装 SendRichMessage 方法对象(bot.py#L6503-L6517);
  2. SendRichMessage 声明 __api_method__ = "sendRichMessage" 与 __returning__ = Message(send_rich_message.py#L23-L24);
  3. 发送前,Pydantic 按判别联合校验 rich_message.blocks,将 {"type": "heading", ...} 解析为 InputRichBlockSectionHeading;
  4. 请求经会话层发出,响应解析为 Message 返回。

测试 tests/test_api/test_methods/test_send_rich_message.py 验证了这一整条链路:用 MockedBot 注册 SendRichMessage 的模拟响应,再通过 bot.send_rich_message(chat_id=42, rich_message=InputRichMessage(html="<p>Hello</p>")) 调用并断言返回结果一致。

六、输入侧与输出侧:RichBlockSectionHeading

发送成功后,Telegram 返回的是服务端渲染后的消息,其中富文本块对应"输出侧"类型 RichBlockSectionHeading,字段与输入侧完全一致(type、text、size),只是 text 使用输出侧的 RichTextUnion。两者结构对称,payload 中 {"type": "heading", "text": ..., "size": 1} 既能被输入侧校验也能被输出侧校验——这正是 test_1842 测试 中"同一 payload 对两侧联合均有效"的由来。

如果你收到一个 Message 并希望读取其中的标题,可以在 message.rich_message.blocks 中逐个判断 isinstance(block, RichBlockSectionHeading),再按 size 还原文档的层级结构。

七、源码实现细节与注意事项

1. Pydantic 模型与 Literal 类型

InputRichBlockSectionHeading 继承 InputRichBlock(后者继承 TelegramObject),本质是 Pydantic v2 模型。type 字段使用 Literal 类型限定,配合默认值实现了"自动填充判别标签"的约定。类中 if TYPE_CHECKING 块内的 __init__ 签名由 aiogram 的 butcher 工具自动生成,仅为 IDE 类型提示服务,运行时不生效——直接实例化即可。

2. 常见错误

  • 缺少 size:size 无默认值,构造时漏传会触发 Pydantic 校验错误;
  • size 超出 1–6:虽然字段类型是 int,但服务端仅接受 1–6,超出范围会导致请求失败;
  • type 写错:例如写成 "h1" 或 "title" 会被判别联合以 union_tag_invalid 错误拒绝(可参考 test_1842 中的断言),而不是静默解析成其他块;
  • 同时设置 html/markdown/blocks:InputRichMessage 要求三者只能取其一。

3. 使用建议

  • 长文内容建议用 size=1 做主标题、size=2/3 做各级小节,形成清晰的层级;配合 InputRichBlockDivider(分隔线)与 InputRichBlockList(列表)可进一步增强可读性;
  • 需要流式输出时,可先用 bot.send_rich_message_draft 发送 30 秒的临时预览(见 bot.py#L6520),最终仍需调用 send_rich_message 固化消息;
  • 所有块都支持嵌套富文本(如标题内混排加粗、链接、emoji),但注意 RichTextUnion 的数组元素同样要满足其判别规则。

结语

InputRichBlockSectionHeading 虽然只是 aiogram 富文本块体系中的一个"积木",但它承载了 Telegram 富文本消息的层级排版能力。理解它的 type/text/size 三字段语义、InputRichBlockUnion 判别联合的校验机制,以及 send_rich_message/answer_rich 的调用链路,就能在 aiogram 中自如地构造结构化的富文本长文。相关源码、方法与测试均可直接在仓库对应路径中进一步查阅:类型定义、判别联合、发送方法、快捷方法、回归测试。

登录后查看全文
aiogram