Python Telegram Bot中ConversationHandler在Webhook模式下无法正常工作的问题排查
问题分析
你的核心问题出在Webhook模式下每次请求都新建Dispatcher实例,导致ConversationHandler的会话状态无法持久化。
在Polling模式中,Dispatcher是长期运行的单例,会话状态会保存在它的存储中;但在你的Webhook代码里,每个POST请求都会调用setup()创建新的Dispatcher,之前的会话状态完全丢失,所以ConversationHandler无法跟踪用户当前处于哪个状态,自然不会触发后续的状态函数。
另外,你的回调函数里还有两个小错误,也会导致点击按钮无响应:
- 处理CallbackQuery时没有调用
query.answer(),Telegram会认为回调未处理,按钮会一直处于加载状态。 - 在CallbackQueryHandler对应的函数中错误地使用
update.message,而不是从update.callback_query中获取数据和操作消息。
解决方案
1. 全局初始化Dispatcher,避免每次请求重建
修改你的代码,在应用启动时就创建一次Dispatcher并注册所有处理程序,而不是每次请求都新建。
首先,调整全局变量和初始化逻辑:
from telegram.ext import Dispatcher, MemoryStorage # 全局初始化Bot和Dispatcher bot = telegram.Bot(token=TOKEN) storage = MemoryStorage() # 开发用,生产环境建议用持久化存储比如RedisStorage dispatcher = Dispatcher(bot, storage=storage, workers=1) def setup_dispatcher(): """全局注册所有处理程序""" conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start)], states={ NEW_PROJECT: [CallbackQueryHandler(project_name)], PROJECT_NAME: [MessageHandler(Filters.regex(".*"), store_name_maybe_project_type)], PROJECT_TYPE: [CallbackQueryHandler(store_type_maybe_admin)] }, fallbacks=[CommandHandler('cancel', cancel)], ) dispatcher.add_handler(conv_handler) # 可以添加其他handler # 应用启动时注册处理程序 setup_dispatcher()
然后修改respond函数,直接使用全局的Dispatcher处理更新:
@app.route(f"/{TOKEN}", methods=["POST"]) def respond(): update = telegram.Update.de_json(request.get_json(force=True), bot) dispatcher.process_update(update) return "ok"
2. 修复CallbackQuery处理函数的错误
修正project_name函数:
必须调用query.answer(),并且使用query.message来回复消息:
def project_name(update, context): query = update.callback_query # 必须调用answer(),告知Telegram回调已处理 query.answer() # 使用query.message来回复,而不是update.message query.message.reply_text(text="Okay, Please enter your project name:") return PROJECT_NAME
修正store_type_maybe_admin函数:
同样需要调用query.answer(),并且从query.data获取回调数据(而不是错误地用update.message.text):
def store_type_maybe_admin(update, context): query = update.callback_query query.answer() # 从callback_query中获取项目类型 project_type = query.data keyboard = [[InlineKeyboardButton("Done", callback_data="done")]] reply_markup = InlineKeyboardMarkup(keyboard) query.message.reply_text(f"Make a private {project_type} and make this bot the admin", reply_markup=reply_markup) return ConversationHandler.END
3. 验证Webhook配置
确保你的set_webhook路由正确设置了Webhook地址,并且服务器可以被Telegram访问(公网IP、正确的端口,HTTPS等,Telegram要求Webhook必须用HTTPS)。
为什么这样能解决问题?
- 全局Dispatcher:确保会话状态(比如用户当前处于哪个Conversation状态)被持久化存储在
MemoryStorage(或其他存储)中,每次请求处理时都能读取到之前的状态。 - CallbackQuery的正确处理:调用
query.answer()是Telegram API的要求,否则会导致按钮无响应;同时正确使用update.callback_query的属性来获取数据和操作消息,避免报错。
内容的提问来源于stack exchange,提问作者Palash Singh Raghuwanshi




