aiogram 中的 Link 类型全解:Telegram Bot API 链接的建模、工具与实战

原创2026-10-12 17:04:221,434 阅读
文章标签:后端即时通讯

aiogram 中的 Link 类型全解:Telegram Bot API 链接的建模、工具与实战

本文基于 aiogram 官方 API 文档中的 Link 类型(见 docs/api/types/link.rst)展开,系统讲解 aiogram 如何为 Telegram Bot API 中的 HTTP 链接建模:从 Link 类型本体、链接预览选项、邀请链接、故事链接区域等关联类型,到链接构造工具、deep linking 深度链接与 payload 编解码实战。读完本文,你将掌握在 aiogram 中构造、发送、解析各类 Telegram 链接的完整方案与底层实现细节。

Link 类型本体:一个极简的 HTTP 链接模型

Link 是 Telegram Bot API 中表示 HTTP 链接 的基础类型,对应 aiogram/types/link.py 中的同名类。它是一个非常精简的 Pydantic 模型,继承自 TelegramObject(见 aiogram/types/base.py),整个类型只有一个字段:

字段 类型 说明
url str 链接的 URL 地址(必填)

源码中的定义极为简洁:

class Link(TelegramObject):
    """
    Represents an HTTP link.

    Source: https://core.telegram.org/bots/api#link
    """

    url: str
    """URL of the link"""

可以看到 Link 本身没有任何可选字段——它只承载一个 url 字符串。这与 Telegram Bot API 文档对 Link 的定义完全一致。值得注意的是,文件中 if TYPE_CHECKING: 块内的 __init__ 方法由 butcher 工具自动生成,仅用于为 IDE 提供类型提示与自动补全支持(url 以关键字参数形式传入),运行时并不影响 Pydantic 的解析行为。

Link 通过 aiogram/types/init.py 导出,因此你可以直接从 aiogram.types 导入:

from aiogram.types import Link

link = Link(url="https://example.com")
print(link.url)  # https://example.com

链接家族:与 Link 同族的关联类型

在 aiogram 的类型体系中,Link 并不是孤立的。围绕"链接"这一概念,仓库中存在一批职责各异但相互关联的类型,理解它们有助于在实际开发中选对类型。

LinkPreviewOptions:链接预览控制

消息文本中的链接默认会生成"链接预览"(link preview),而 aiogram/types/link_preview_options.py 中的 LinkPreviewOptions 专门用于控制这一行为,字段包括:

  • is_disabled:是否禁用链接预览;
  • url:指定用于预览的 URL;为空时使用消息文本中出现的第一个 URL;
  • prefer_small_media:是否偏好缩小预览媒体(仅在显式指定 URL 且媒体尺寸可调时生效);
  • prefer_large_media:是否偏好放大预览媒体(同上);
  • show_above_text:预览显示在消息文本上方;否则显示在文本下方。

注意该类型继承自 TelegramObject,与 Link 不同,其四个布尔字段的默认值均为 Default(...) 占位(如 Default("link_preview_is_disabled")),表示"跟随机器人全局默认值"。

ChatInviteLink:聊天邀请链接

邀请链接是链接中最常见的业务场景。aiogram/types/chat_invite_link.py 中的 ChatInviteLink 完整建模了 Telegram 的邀请链接状态:除了 invite_link 本身,还包含 creator(创建者)、creates_join_request(是否需要管理员审批)、is_primary(是否主链接)、is_revoked(是否已撤销),以及可选的 name、expire_date(过期时间戳)、member_limit(成员上限 1–99999)、pending_join_request_count、subscription_period(订阅周期秒数)与 subscription_price(Telegram Stars 订阅价格)等字段。对应的方法文档见 docs/api/types/chat_invite_link.rst。

StoryAreaTypeLink:故事中的链接区域

在故事(Story)场景下,链接被用作可点击的区域。aiogram/types/story_area_type_link.py 中的 StoryAreaTypeLink 描述了一个指向 HTTP 或 tg:// 链接的故事区域,type 固定为 link,url 字段接受 HTTP 或 tg:// 协议地址。根据其文档字符串,单个故事最多可包含 3 个链接区域。

富文本中的链接:RichTextAnchorLink 与 RichTextReferenceLink

在富文本(Rich Text)体系中,链接被进一步细分:

两者的 type 字段分别固定为 anchor_link 与 reference_link,均继承自 RichText。

InputMediaLink:作为媒体发送的链接

aiogram/types/input_media_link.py 中的 InputMediaLink 表示"要发送的 HTTP 链接",继承自 InputPollOptionMedia,type 固定为 "link",唯一的业务字段同样是 url。

消息文本中的链接:text_link 实体与格式化工具

在普通文本消息中,链接以 MessageEntity 的形式内嵌在消息里。aiogram/types/message_entity.py 定义的类型清单中,text_link 实体专用于"可点击的文本 URL",其 url 字段只在 text_link 类型时生效。如果你需要从一段消息文本中提取某个实体对应的字符串,可以调用实体上的 extract_from(text) 方法(见 aiogram/types/message_entity.py),它基于 UTF-16 code unit 偏移量切出子串。

aiogram 还在 aiogram/utils/formatting.py 中提供了声明式的 TextLink 元素,它会被包装为 MessageEntityType.TEXT_LINK 类型的实体:

from aiogram.utils.formatting import TextLink

# 构造一个带链接的文本元素
element = TextLink("查看官方文档", url="https://docs.aiogram.dev/")

这种元素化的写法便于与格式化输出工具组合,避免手工拼接 parse_mode 语法字符串。

链接构造工具:aiogram.utils.link

如果说 Link 类型解决的是"链接如何被描述",那么 aiogram/utils/link.py 解决的就是"链接如何被构造"。该模块提供了三个核心构造函数:

函数 行为 示例结果
create_telegram_link(*path, **kwargs) 基于 https://t.me 构造链接,路径与查询参数自动拼接 create_telegram_link("username", start="test") → https://t.me/username?start=test
create_tg_link(link, **kwargs) 基于 tg:// 协议构造链接 create_tg_link("user", id=42) → tg://user?id=42
create_channel_bot_link(username, ...) 构造将机器人拉入群组的"群组邀请"链接 create_channel_bot_link("test_bot", parameter="x") → https://t.me/test_bot?startgroup=x

三个函数都依赖内部辅助函数 _format_url(aiogram/utils/link.py),它依次完成路径拼接(urljoin)、查询参数编码(urlencode)与 fragment 追加。create_channel_bot_link 特别之处在于:它支持通过 change_info、post_messages、edit_messages、delete_messages、restrict_members、invite_users、pin_messages、promote_members、manage_video_chats、anonymous、manage_chat 共 11 个布尔参数一次性声明机器人的管理员权限,权限名会被合并为 admin 查询参数。

这一行为在 tests/test_utils/test_link.py 中有完整的单元测试覆盖:TestLink 验证了 create_tg_link 与 create_telegram_link 的精确输出;TestCreateChannelBotLink 则用排列组合穷举了 2^11 种权限组合,逐一断言 admin 查询参数中的权限集合与传入参数完全一致。

实战:Deep Linking 深度链接

深度链接(Deep Linking)是链接工具最典型的落地场景,aiogram 官方在 docs/utils/deep_linking.rst 中给出了完整指南。其原理是:Telegram 机器人可以通过 start 参数在用户点击链接启动机器人的同时,向机器人传递附加参数(例如启动命令或用于关联外部服务账号的认证令牌)。

构造 start 链接

aiogram/utils/deep_linking.py 中的 create_start_link 是入口函数,它先通过 (await bot.me()).username 获取机器人用户名,再交给 create_deep_link 完成拼接:

from aiogram.utils.deep_linking import create_start_link

link = await create_start_link(bot, 'foo')
# result: 'https://t.me/MyBot?start=foo'

编码 payload

payload 字符集受限时(见下文"边界与校验"),可以开启 base64url 编码。编码实现位于 aiogram/utils/payload.py,默认使用 URL-safe base64 且去掉尾部 = 填充:

from aiogram.utils.deep_linking import create_start_link

link = await create_start_link(bot, 'foo', encode=True)
# result: 'https://t.me/MyBot?start=Zm9v'

接收并解码

在机器人侧,配合 CommandStart(deep_link=True) 过滤器接收 /start 命令,再用 decode_payload 还原原始 payload:

from aiogram.utils.deep_linking import decode_payload
from aiogram.filters import CommandStart, CommandObject
from aiogram.types import Message

@router.message(CommandStart(deep_link=True))
async def handler(message: Message, command: CommandObject):
    args = command.args
    payload = decode_payload(args)
    await message.answer(f"Your payload: {payload}")

更多链接类型与自定义编解码

create_deep_link(aiogram/utils/deep_linking.py)统一支持三种 link_type:start、startgroup(将机器人加入群组)、startapp(启动 Mini App,可结合 app_name 生成直接 Mini App 链接),分别由 create_start_link、create_startgroup_link、create_startapp_link 三个异步函数封装。

如果默认的 base64url 编码不满足需求,encode_payload 与 decode_payload 还接受自定义的 encoder / decoder 回调。文档示例(见 aiogram/utils/payload.py)展示了用 AES 加密 payload 的完整流程:先实现 Cryptor 类提供 encrypt / decrypt 方法,再把可调用对象传入:

from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad

class Cryptor:
    def __init__(self, key: str):
        self.key = key.encode("utf-8")
        self.mode = AES.MODE_ECB  # 强加密系统中不应使用 ECB
        self.size = 32

    @property
    def cipher(self):
        return AES.new(self.key, self.mode)

    def encrypt(self, data: bytes) -> bytes:
        return self.cipher.encrypt(pad(data, self.size))

    def decrypt(self, data: bytes) -> bytes:
        return self.cipher.decrypt(data)  # 注意需自行 unpad

cryptor = Cryptor("abcdefghijklmnop")
encoded = encode_payload("foo", encoder=cryptor.encrypt)
decoded = decode_payload(encoded, decoder=cryptor.decrypt)
# decoded == "foo"

注意 encode_payload 的实现是"先调用自定义 encoder,再统一做 base64url 编码",因此无论是否自定义编码器,最终输出都是 URL-safe 字符串。

链接的边界与校验

create_deep_link 内部实现了两道硬性校验(aiogram/utils/deep_linking.py),这是实际开发中最容易踩坑的地方:

  1. 字符集限制:payload 只允许 A-Z、a-z、0-9、_ 和 -(对应模块顶部的 BAD_PATTERN = re.compile(r"[^a-zA-Z0-9-_]"))。包含其他字符会抛出 ValueError,并提示 Pass encode=True or encode payload manually.;
  2. 长度限制:payload 最长 64 个字符(DEEPLINK_PAYLOAD_LENGTH = 64),超长同样抛出 ValueError。

因此,当 payload 包含空格、中文、特殊符号或超过 64 字符时,务必使用 encode=True 预先编码。另外,create_start_link、create_startgroup_link 等函数会先调用 bot.me() 获取机器人用户名,这意味着它们必须在机器人完成初始化(token 有效)之后才能调用。

链接预览的默认值机制

回到消息发送场景:如果你希望在发送消息时统一控制链接预览,不必每次构造 LinkPreviewOptions。aiogram 的 Default 机制允许在 aiogram/client/default.py 中配置全局默认值——link_preview_is_disabled、link_preview_prefer_small_media、link_preview_prefer_large_media、link_preview_show_above_text 四个字段会被合并进 LinkPreviewOptions 对象;__post_init__ 中通过 has_any_link_preview_option 判断:只要设置了其中任意一项且未显式传入 link_preview,就会自动构造完整的 LinkPreviewOptions 并注入请求。这也是 LinkPreviewOptions 各字段默认值使用 Default("link_preview_...") 占位的原因——它们会回退到机器人的默认行为。

小结

aiogram 对 Telegram Bot API 中"链接"的建模是分层而完整的:Link 作为最基础的 HTTP 链接类型只有一个 url 字段,而围绕它展开的 LinkPreviewOptions、ChatInviteLink、StoryAreaTypeLink、RichTextAnchorLink、InputMediaLink 等类型分别覆盖预览、邀请、故事、富文本与媒体发送等具体场景;工具层面则由 aiogram/utils/link.py 负责链接字符串的构造、aiogram/utils/deep_linking.py 与 aiogram/utils/payload.py 负责深度链接的生成与 payload 编解码。理解这些类型与工具的分工,你就能在真实机器人开发中精准选择方案:发送带预览控制的文本链接、创建带权限的邀请链接、实现带认证 payload 的深度链接,而无需自己手写 URL 拼接逻辑。

延伸阅读:

登录后查看全文
aiogram