在Telegram机器人开发中,获取消息发送者的信息是构建用户画像、实现权限控制、统计活跃用户等功能的基础。很多开发者在最初接触Bot API时,都会试图从Message对象中直接提取这些字段,但往往会遇到匿名身份、频道转发、无用户名等陷阱。本文将系统性地讲解如何可靠地提取发送者的ID与用户名,并给出可落地的代码方案。
一、为什么需要提取发送者ID与用户名?
发送者ID是Telegram中每个用户的唯一数字标识,而用户名(username)是人类可读的公开@别名。二者在机器人应用中的用途各不相同:
- ID:用于精确识别用户、存储到数据库、针对特定用户发送消息、配置白名单/黑名单等。
- 用户名:用于展示、生成@链接、外部系统关联,有时也作为敏感操作的第二重校验。
在大多数情况下,机器人需要同时获取ID和用户名,因为用户名可能为空(用户未设置),而ID一定存在。因此,设计一个健壮的提取逻辑是必要的。
二、从Message对象的from字段获取基础信息
Telegram Bot API中的Message对象包含一个from字段,类型为User。该字段代表消息的发送者。对于普通用户发送的消息,直接读取即可。
Python和python-telegram-bot库的示例:
from telegram import Update
from telegram.ext import Application, MessageHandler, filters
async def handle_message(update: Update, context):
msg = update.effective_message
if msg.from_user:
user_id = msg.from_user.id
username = msg.from_user.username
print(f"收到来自ID: , 用户名: 的消息")
else:
print("消息没有from_user字段")
def main():
app = Application.builder().token("YOUR_TOKEN").build()
app.add_handler(MessageHandler(filters.ALL, handle_message))
app.run_polling()
if __name__ == "__main__":
main()
注意:from_user可能为None。例如频道频道帖子被转发到群组时,forward_from_chat会存在,而from_user可能是转发者的信息或空。所以务必做空值判断。
三、处理匿名身份与隐藏用户名
Telegram中,群组和频道可以开启“匿名管理”功能。当管理员以频道身份发言时,from_user会变为频道本身(即sender_chat字段),此时没有常规的id和username概念。另外,用户在隐私设置中也可以隐藏自己的用户名,但这种情况下from_user.username会返回null,ID仍然存在。
因此,提取逻辑需要分层判断:
- 优先检查
from_user——这是最常见的普通用户消息。 - 若
from_user为None,检查sender_chat——可能是频道或群组,代表匿名发言。 - 若两者都为空,检查
forward_from或forward_from_chat——这是转发消息时的原始发送者。
参考代码片段:
def extract_sender_info(update):
msg = update.effective_message
sender_id = None
sender_username = None
sender_type = None
if msg.from_user:
sender_id = msg.from_user.id
sender_username = msg.from_user.username
sender_type = "user"
elif msg.sender_chat:
sender_id = msg.sender_chat.id
sender_username = msg.sender_chat.username
sender_type = "chat"
elif msg.forward_from:
sender_id = msg.forward_from.id
sender_username = msg.forward_from.username
sender_type = "forwarded_user"
elif msg.forward_from_chat:
sender_id = msg.forward_from_chat.id
sender_username = msg.forward_from_chat.username
sender_type = "forwarded_chat"
# 注意:匿名频道的id与普通用户id不同,通常以负数表示,且无法主动向该ID发送私聊消息
return sender_id, sender_username, sender_type
四、通过ChatMember信息获取群组内的用户资料
有时候你收到的不是普通消息,而是服务消息(如“XX加入了群组”)。此时可以从new_chat_members或left_chat_member中提取用户信息。另外,在群组中即使消息的from_user存在,也可能缺少用户名(用户没设置),但你可以通过getChatMember方法获取该用户在群组内的完整资料,包括用户名(如果存在)。
from telegram.error import TelegramError
async def get_user_info_from_chat(update, user_id):
chat_id = update.effective_chat.id
try:
member = await context.bot.get_chat_member(chat_id, user_id)
return member.user
except TelegramError:
return None
五、区分“发送者”和“转发来源”
当用户转发一条来自其他频道的消息时,from_user是转发者,forward_from_chat是原始频道。你在设计功能时,必须明确“提取消息发送者”的语义:你需要的是当前消息的发送者,还是消息内容的原始作者?
例如,机器人要统计“哪些用户转发了消息”,就需要看from_user;要追踪某条内容的传播路径,则需要看forward_from。根据业务场景正确选择字段,否则会得到错误的数据。
六、实战经验与注意事项
- 隐私限制:Telegram对机器人获取隐私信息有严格限制。只要你获取的是用户在Telegram中已经公开的信息(例如通过消息带出的ID和用户名),是合规的;但禁止利用机器人抓取用户手机号等私密信息。
- ID与用户名的区别:数字ID是永久的,用户名可以更改。因此,如果要以用户为唯一标识,请使用ID;如果展示@昵称,则用用户名。不要将用户名作为系统主键。
- 负数ID:频道和群组的ID是负数,普通用户ID是正数。在判断发送者是人还是“会说话的频道”时,往往用这个特征。
- 多语言环境:Java、Go、Node.js等都提供了对应的类型,但字段命名类似,注意检查空值即可。
- 在Webhook模式下的处理:Webhook同样返回的是Update对象,提取逻辑完全一样,不区分轮询与Webhook。
七、完整示例:提取并记录所有新消息发送者
如果你需要将每个发送者的ID和用户名记录到数据库,可以参考以下简化的SQLite存储逻辑:
import sqlite3
conn = sqlite3.connect('users.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, username TEXT, first_name TEXT, last_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
async def handle_message(update, context):
msg = update.effective_message
user = msg.from_user
if user:
c.execute("INSERT OR IGNORE INTO users (id, username, first_name) VALUES (?, ?, ?)",
(user.id, user.username, user.first_name))
conn.commit()
注意:INSERT OR IGNORE用于避免重复存储,但如果你希望更新用户名,则需要使用INSERT ... ON CONFLICT DO UPDATE。
八、总结
提取消息发送者的ID与用户名是Telegram机器人开发中的基础操作。关键在于理解from_user、sender_chat和转发相关的字段,并根据实际场景做取舍。建议开发者在自己的工具类中封装一个统一的提取函数,避免在业务代码中重复判断空值。另外,请时刻注意Telegram的隐私政策,只处理业务所必需的数据。
只要掌握了这些技巧,你就能轻松构建群组管理、用户统计、自动回复等高级功能。希望本文能为你扫清这些“小坑”,让你的机器人跑得更稳。