在Telegram群组中,使用Bot自动发送消息是一种常见的自动化手段。然而,当消息需要通知特定成员时,如何在消息中正确“@”对方,成为许多开发者开发初期遇到的痛点。Telegram Bot API提供了灵活的文本提及机制,既支持传统的@username,也支持更稳定的text_mention实体(通过用户ID)。本文将逐步讲解这两种方式的原理、实现步骤及常见陷阱,帮助你打造更智能的群组Bot。
一、Telegram消息中的@成员机制概述
Telegram的实体(Entity)系统定义了一系列特殊文本格式,其中mention和text_mention专门用于提及用户。
- mention:针对用户名(
@username)的提及,Bot需要知道用户的@用户名。 - text_mention:针对用户ID的提及,无需用户名,通过实体中的
user对象指定用户,常用于用户名被修改或不可用的情况。
在发送消息时,通过entities参数标记文本中某一位置为提及,Telegram客户端会高亮显示并触发通知。此外,如果Bot不知道对方的用户名或ID,也可以直接发送普通文本,Telegram会自动识别其中的@username,但这种方式不够精确,且无法使用text_mention。
二、方法一:直接在文本中使用@username
最简单的方式是在消息文本中直接包含@username,Telegram会自动识别为提及。但此方法要求Bot知道对方的准确用户名,且若用户没有设置用户名或用户名已变更,则无法生效。
步骤指南
- 获取目标成员的
@username(可通过群组消息中的发送者信息获取)。 - 在构造消息文本时,插入
@username。 - 调用
sendMessageAPI发送即可。
Python代码示例
import requests
TOKEN = "YOUR_BOT_TOKEN"
CHAT_ID = "@your_group_username" # 或群组ID
username = "john_doe"
text = f"你好 @,请查看这条消息!"
url = f"https://api.telegram.org/bot/sendMessage"
data = {"chat_id": CHAT_ID, "text": text}
resp = requests.post(url, data=data)
print(resp.json())
优点:实现简单,无需额外处理实体。缺点:依赖用户名,不稳定;如果用户名包含特殊字符或过长,可能被截断。
三、方法二:使用entities实体精确控制提及
更专业的方式是利用entities参数显式声明提及类型。我们可以选择mention(针对@username)或text_mention(针对用户ID)。这种方式允许你完全控制提及的文本和目标,即使用户名不存在也能通过ID提及。
3.1 使用mention实体(基于@username)
当你确定对方的用户名时,可以创建一个mention实体,指定文本中@username的偏移量和长度。
import requests
TOKEN = "YOUR_BOT_TOKEN"
CHAT_ID = "@your_group_username"
username = "john_doe"
text = f"你好 @,这是精确的提及!"
# 计算@username在文本中的位置
offset = text.find(f"@")
length = len(f"@")
entities = [{
"type": "mention",
"offset": offset,
"length": length
}]
url = f"https://api.telegram.org/bot/sendMessage"
data = {
"chat_id": CHAT_ID,
"text": text,
"entities": entities
}
resp = requests.post(url, data=data)
print(resp.json())
3.2 使用text_mention实体(基于用户ID)
当用户没有设置用户名或用户名未知时,这是最佳方案。你需要提供用户ID(可通过update消息中的from.id获取)。在实体中指定user对象,然后设置要替换的文本(通常为用户的显示名)。
import requests
TOKEN = "YOUR_BOT_TOKEN"
CHAT_ID = "@your_group_username"
USER_ID = 123456789 # 替换为实际用户ID
DISPLAY_NAME = "John Doe" # 可以是任意文本,但建议与用户显示名一致
text = f"你好 ,请查看这条消息!"
offset = len("你好 ")
length = len(DISPLAY_NAME)
entities = [{
"type": "text_mention",
"offset": offset,
"length": length,
"user": {"id": USER_ID}
}]
url = f"https://api.telegram.org/bot/sendMessage"
data = {
"chat_id": CHAT_ID,
"text": text,
"entities": entities
}
resp = requests.post(url, data=data)
print(resp.json())
注意:offset和length必须以UTF-16代码单位计算。对于大多数拉丁字符和数字,与字节数相同,但涉及中文时,一个汉字在UTF-16中占2个代码单位。因此上述示例若包含中文,需要特别注意计算方式。建议将显示名单独放在一个位置,并计算偏移量。
四、正确计算UTF-16偏移量的实用建议
Telegram Bot API规定,offset和length基于UTF-16编码。而Python的字符串索引默认按Unicode代码点,这对基本多语言平面之外的字符可能不一致。稳妥的做法是使用字节码或预先构造好文本,并确保实体覆盖的字符位于基本多语言平面(如英文、数字、常见符号)。对于中文,一个汉字占2个UTF-16代码单位,因此必须谨慎。
一种可靠的方法:使用占位符(例如一个无法打印的字符)来构建消息,然后只对占位符应用实体。但更简单的方式是使用英文或数字作为显示名,或者手动计算UTF-16偏移。
这里提供一个Python辅助函数来计算UTF-16偏移:
def utf16_len(s):
return len(s.encode('utf-16-le')) // 2
def utf16_offset(text, pos):
# pos为Unicode代码点索引
return utf16_len(text[:pos])
使用示例:
text = "你好 John Doe"
pos = text.find("John Doe")
offset = utf16_offset(text, pos) # 计算正确偏移
length = utf16_len("John Doe")
这样可以确保任何语言都能正确工作。
五、权限要求与常见问题
Bot在群组中@成员需要什么权限?
Bot需要被提升为管理员,或者群组开启了“允许Bot发送私聊”等权限?实际上,发送@成员消息本身不需要管理员权限,但Bot如果要在群组中发送内容,就必须具有发送消息的权限(通常默认拥有)。不过,如果Bot要@一个从未在群组中出现过的用户,可能无法触发通知,因为Telegram限制了Bot必须与用户有过交互才能通过ID提及。这一点尤为重要:text_mention只有在Bot与该用户有过私聊或同一群组会话时才有效。否则,消息发送仍会成功,但对方不会收到通知。
为什么@成员不生效?
- 用户名输入错误或用户已更改用户名。
- 用户未设置用户名,导致
mention失效。 - Bot和该成员不在同一个群组,且没有私聊记录,
text_mention无法触发通知。 - 实体参数计算错误,导致实体位置不对。
如何获取群组成员的用户ID?
可以通过监听群组消息中的from.id字段,或使用@getidsbot等工具。更常见的是在数据库中保存用户ID和群组关系。
六、总结
在Telegram Bot发送群组消息时@成员,实质上是利用实体的mention或text_mention功能。推荐优先使用text_mention(基于用户ID),因为它不依赖可能变更的用户名,且能确保通知到达。实现时务必注意UTF-16偏移量的计算,避免中文等字符导致实体错位。结合群组权限和用户交互记录,你就能让Bot智能地提及任何成员,提升群组的自动化交互体验。