如何用Telethon实现Telegram多用户调查机器人的进度追踪?
嗨,作为刚接触Telethon和机器人开发的新手,遇到多用户状态追踪的问题太正常了!我来给你梳理几个实用的方案,从易到难,你可以根据自己的需求选:
适合小型测试机器人或者临时场景,不需要额外依赖,直接用Python字典就能搞定核心逻辑。我们可以把**聊天ID(用户私人ID或群组ID)**作为键,对应的值存储该用户/群组当前的调查状态,比如所属调查ID、当前投票序号、已发送消息ID列表等。
举个简单的代码示例:
# 全局字典,用来存所有用户的调查状态 user_survey_states = {} # 初始化用户的调查状态(用户开始调查时调用) async def init_survey_state(chat_id, survey_id): user_survey_states[chat_id] = { "survey_id": survey_id, "current_step": 0, # 当前要发送的投票序号,从0开始计数 "sent_message_ids": [], # 存已发送消息的ID,方便后续编辑/删除 "last_active": datetime.now() # 记录最后活动时间,方便清理过期状态 } # 处理用户回复,更新状态并发送下一条投票 async def handle_user_response(chat_id, response_msg): # 先判断用户是否在参与调查 if chat_id not in user_survey_states: await client.send_message(chat_id, "请先发送/start_survey开始调查哦~") return state = user_survey_states[chat_id] # 这里可以加逻辑:记录用户当前回复的答案,比如存到数据库或文件 # 更新当前步骤 state["current_step"] += 1 state["last_active"] = datetime.now() # 获取下一条投票内容(假设你有一个get_survey_question方法,根据调查ID和步骤返回内容) next_question = get_survey_question(state["survey_id"], state["current_step"]) if next_question: # 发送下一条投票,并把消息ID存到状态里 sent_msg = await client.send_message( chat_id, next_question["text"], buttons=next_question["buttons"] ) state["sent_message_ids"].append(sent_msg.id) else: # 调查结束,发送感谢语并清理状态 await client.send_message(chat_id, "调查完成!感谢你的参与😊") del user_survey_states[chat_id]
这个方案的优点是零成本快速实现,缺点是机器人重启后所有状态会丢失,所以只适合测试或临时使用。
如果你的机器人需要长期运行,或者用户量较大,一定要用数据库存储状态,避免重启丢失数据。新手可以先从轻量的SQLite入手,不需要额外安装服务,Python自带支持。
我们可以创建一个专门的表来存储用户调查状态,字段大概是这样:
| 字段名 | 类型 | 说明 |
|---|---|---|
| chat_id | INTEGER | 用户/群组ID(主键) |
| survey_id | TEXT | 当前参与的调查ID |
| current_step | INTEGER | 当前投票序号 |
| sent_messages | TEXT | 已发送消息ID列表(存JSON) |
| last_active | DATETIME | 最后活动时间 |
简单的SQLite操作示例:
import sqlite3 import json from datetime import datetime # 初始化数据库连接 conn = sqlite3.connect("survey_bot.db") cursor = conn.cursor() # 创建状态表(第一次运行时执行) cursor.execute(''' CREATE TABLE IF NOT EXISTS user_survey_states ( chat_id INTEGER PRIMARY KEY, survey_id TEXT NOT NULL, current_step INTEGER NOT NULL DEFAULT 0, sent_messages TEXT NOT NULL DEFAULT '[]', last_active DATETIME NOT NULL ) ''') conn.commit() # 初始化用户状态 def init_survey_state_db(chat_id, survey_id): cursor.execute(''' INSERT OR REPLACE INTO user_survey_states (chat_id, survey_id, current_step, last_active) VALUES (?, ?, 0, ?) ''', (chat_id, survey_id, datetime.now())) conn.commit() # 更新用户状态(比如用户回复后) def update_survey_state_db(chat_id, new_step, new_msg_id=None): # 先获取已有的消息ID列表 cursor.execute('SELECT sent_messages FROM user_survey_states WHERE chat_id = ?', (chat_id,)) result = cursor.fetchone() if not result: return sent_messages = json.loads(result[0]) if new_msg_id: sent_messages.append(new_msg_id) # 更新数据库 cursor.execute(''' UPDATE user_survey_states SET current_step = ?, sent_messages = ?, last_active = ? WHERE chat_id = ? ''', (new_step, json.dumps(sent_messages), datetime.now(), chat_id)) conn.commit()
这个方案的优点是状态永久保存,机器人重启后用户的调查进度不会丢失;缺点是需要写一些数据库操作的代码,但SQLite对新手非常友好,上手很快。
Telethon自带了Conversation类,专门用来处理一对一的对话流程,它会自动帮你区分不同用户的对话状态,不需要自己手动维护字典。不过这个方案更适合步骤固定的简单调查,如果你的调查是完全自定义、步骤数量不固定的,灵活性不如前两种方案。
示例代码:
from telethon import events, Conversation # 定义对话状态码,对应不同的调查步骤 START, QUESTION_1, QUESTION_2, SURVEY_END = range(4) @client.on(events.NewMessage(pattern='/start_survey')) async def start_survey(event): # 创建对话,设置超时时间(比如300秒没回复就结束) async with Conversation(event, timeout=300) as conv: # 发送第一个问题 await conv.send_message("Q1:你喜欢喝咖啡还是茶?", buttons=[["咖啡", "茶"]]) # 等待用户回复 response1 = await conv.get_response() # 记录用户答案(这里可以存到数据库) # 发送第二个问题 await conv.send_message("Q2:你每天喝几杯?", buttons=[["1杯", "2-3杯", "4杯以上"]]) response2 = await conv.get_response() # 记录答案 # 调查结束 await conv.send_message("调查完成!感谢你的参与😊")
这个方案的优点是不需要自己手动管理用户状态,Telethon会自动处理多用户的对话隔离;缺点是步骤固定,动态自定义调查的话不太方便,而且超时时间需要设置合理。
- 群组场景的特殊处理:如果是在群组中做调查,每个用户的进度可能不一样,这时候状态的键应该用
(chat_id, user_id)(群组ID+用户ID),而不是单纯的chat_id,避免不同用户的状态互相覆盖。 - 清理过期状态:不管用哪种方案,都要定期清理长期不活跃的用户状态(比如超过24小时没回复的),避免内存或数据库资源浪费。
- 消息追踪的用处:存储已发送的消息ID,后续可以用
client.edit_message修改消息内容,或者用client.delete_messages清理调查相关的消息,提升用户体验。
作为新手,建议先从内存字典的方案开始尝试,熟悉逻辑后再过渡到数据库方案,这样更容易上手哦!
内容的提问来源于stack exchange,提问作者Rehman Ali

