aiogram 富文本消息之 InputRichBlockSectionHeading:章节标题块字段、类型体系与 send_rich_message 实战
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(...) 的实际执行路径为:
Bot.send_rich_message组装SendRichMessage方法对象(bot.py#L6503-L6517);SendRichMessage声明__api_method__ = "sendRichMessage"与__returning__ = Message(send_rich_message.py#L23-L24);- 发送前,Pydantic 按判别联合校验
rich_message.blocks,将{"type": "heading", ...}解析为InputRichBlockSectionHeading; - 请求经会话层发出,响应解析为
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 中自如地构造结构化的富文本长文。相关源码、方法与测试均可直接在仓库对应路径中进一步查阅:类型定义、判别联合、发送方法、快捷方法、回归测试。