Telegram Bot ConversationHandler异常及流程跳过问题求助
问题背景
使用python-telegram-bot开发Telegram机器人,功能是从Reddit获取视频,经moviepy编辑后,通过ConversationHandler向用户请求额外输入,目前遇到两个问题:
- 触发
AttributeError: 'NoneType' object has no attribute 'reply_text',调用update.message.reply_text("test")时出现; - 调用
update.effective_message.reply_text询问用户后,机器人未等待回复直接进入下一个视频上传循环。
相关代码片段
对话启动函数
async def start_editor_questions(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: """Starts the conversation by sending the video and asking the first question.""" output_filename = context.user_data.get('output_filename') await context.bot.send_video(chat_id=update.effective_chat.id, video=open(output_filename, 'rb')) await update.effective_message.reply_text("What height do you want? (h1/h2)") return HEIGHT
主函数
if __name__ == '__main__': application = Application.builder().token('YOUR_TOKEN').build() # Register handlers start_handler = CommandHandler('start', start) application.add_handler(start_handler) application.add_handler(CallbackQueryHandler(button_handler)) conv_handler = ConversationHandler( entry_points=[CommandHandler("start", start_editor_questions)], states={ HEIGHT: [MessageHandler(filters.TEXT & ~filters.COMMAND, height_response)], TRIM: [MessageHandler(filters.TEXT & ~filters.COMMAND, trim_response)], WATERMARK_COLOR: [MessageHandler(filters.TEXT & ~filters.COMMAND, watermark_color_response)], WATERMARK_OPACITY: [MessageHandler(filters.TEXT & ~filters.COMMAND, watermark_opacity_response)], TITLE: [MessageHandler(filters.TEXT & ~filters.COMMAND, title_response)], TITLE_COLOR: [MessageHandler(filters.TEXT & ~filters.COMMAND, title_color_response)], CAPTION: [MessageHandler(filters.TEXT & ~filters.COMMAND, caption_response)], }, fallbacks=[CommandHandler("cancel", cancel)], ) application.add_handler(conv_handler) application.run_polling(allowed_updates=Update.ALL_TYPES)
视频处理调用函数
async def process_account_videos(update: Update, context: ContextTypes.DEFAULT_TYPE, account_name, link, remaining_posts): print("process_account_videos") limit = 1 after = None retrieved_list_file = 'retrieved_list.csv' file_path = retrieved_list_file required_columns = ['time', 'accountname', 'special_name', 'after_retr'] if remaining_posts == 0: return if not os.path.exists(file_path): df = pd.DataFrame(columns=required_columns) df.to_csv(file_path, index=False) print(f"Created new CSV file: {file_path}") else: df = pd.read_csv(file_path) missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: print(f"Warning: Missing columns {missing_columns}. Recreating the file.") df = pd.DataFrame(columns=required_columns) df.to_csv(file_path, index=False) print(f"Recreated CSV file: {file_path}") retrieved_list = pd.read_csv(retrieved_list_file) account_data = retrieved_list[retrieved_list['accountname'] == account_name] i = 1 while loop_number < 3: account_data = retrieved_list[retrieved_list['accountname'] == account_name] check = False if not account_data.empty: check = True account_data['time'] = pd.to_datetime(account_data['time'], errors='coerce') account_data = account_data.sort_values(by='time', ascending=False) special_name_list = account_data['special_name'].values after = account_data.iloc[0]['after_retr'] url = f"{link}.json?limit={limit}" if after: url += f"&after={after}" print(url) response = requests.get(url, headers={"User-Agent": "username"}) if response.status_code == 200: data = response.json() posts = data['data']['children'] account_folder = f"./downloads/{account_name}" os.makedirs(account_folder, exist_ok=True) output_folder = f"./downloads/{account_name}/output" os.makedirs(output_folder, exist_ok=True) for post in posts: if check is True: if post['data']['name'] in special_name_list: continue if post['data']['secure_media'] is None or post['data']['over_18'] is False: new_row = { 'time': pd.Timestamp.now(), 'accountname': account_name, 'special_name': post['data']['name'], 'after_retr': data['data'].get("after") } retrieved_list = pd.concat([retrieved_list, pd.DataFrame([new_row])], ignore_index=True) retrieved_list.to_csv(retrieved_list_file, index=False) continue video_url = post['data']['secure_media']['reddit_video']['fallback_url'] base_video_url = video_url.split("/DASH_")[0] audio_url = f"{base_video_url}/DASH_AUDIO_128.mp4" audio_response = requests.get(audio_url, stream=True) if audio_response.status_code != 200: audio_url = f"{base_video_url}/DASH_audio.mp4" audio_response = requests.get(audio_url, stream=True) video_filename = os.path.join(account_folder, f"video{i}.mp4") with open(video_filename, "wb") as f: video_response = requests.get(video_url, stream=True) f.write(video_response.content) if audio_response.status_code == 200: audio_filename = os.path.join(account_folder, f"audio{i}.mp3") with open(audio_filename, "wb") as f: f.write(audio_response.content) output_filename = os.path.join(output_folder, f"output{i}.mp4") # Using subprocess to merge video and audio subprocess.run(["ffmpeg", "-i", video_filename, "-i", audio_filename, "-c", "copy", output_filename]) else: output_filename = os.path.join(output_folder, f"output{i}.mp4") os.rename(video_filename, output_filename) new_row = { 'time': pd.Timestamp.now(), 'accountname': account_name, 'special_name': post['data']['name'], 'after_retr': data['data'].get("after") } retrieved_list = pd.concat([retrieved_list, pd.DataFrame([new_row])], ignore_index=True) retrieved_list.to_csv(retrieved_list_file, index=False) # Increment the loop_number after each iteration loop_number += 1 i += 1 context.user_data['account_name'] = account_name context.user_data['output_filename'] = output_filename await start_editor_questions(update, context) output_folder = context.user_data.get('output_folder') caption_name = context.user_data.get('caption') await upload(output_folder, caption_name) # Implement this function according to your needs if loop_number >= remaining_posts: break else: print(f"Failed to connect to Reddit. Status code: {response.status_code}") break
问题1:AttributeError 原因及解决
原因
update.message为None时调用reply_text会触发该错误。这种情况通常发生在非消息类型的更新(比如回调查询触发的逻辑),此时update对象没有有效message属性,只有update.callback_query.message存在。- 在
process_account_videos中直接调用start_editor_questions时,若当前update来自回调查询,update.message会是空值,导致调用reply_text失败。
解决方法
- 统一使用
context.bot.send_message替代reply_text,明确指定聊天ID,不依赖update.message:await context.bot.send_message(chat_id=update.effective_chat.id, text="What height do you want? (h1/h2)") - 若必须使用
reply_text,先检查有效消息是否存在:if update.effective_message: await update.effective_message.reply_text("What height do you want? (h1/h2)") else: await context.bot.send_message(chat_id=update.effective_chat.id, text="What height do you want? (h1/h2)") - 确保调用
start_editor_questions时,update对象来自有效消息上下文(比如用户发送消息触发的逻辑)。
问题2:对话跳过等待输入的原因及解决
原因
ConversationHandler的工作逻辑是:只有当入口点被Telegram更新事件触发(比如用户发送/start命令),才会进入对话状态并等待用户回复。- 直接调用
start_editor_questions只是执行了发送消息的逻辑,并没有让ConversationHandler接管对话状态,函数返回的HEIGHT状态无法被识别,导致机器人直接继续后续的上传循环。 - 额外问题:代码中
loop_number变量未初始化,会导致循环逻辑异常。
解决方法
- 不要直接调用对话状态函数,通过
ConversationHandler的入口点触发对话:- 处理完视频后,用
context.bot.send_message提示用户发送特定命令(比如/edit)启动编辑对话,同时将视频信息存入context.user_data。
- 处理完视频后,用
- 整合视频处理逻辑到对话流程中:
- 在
ConversationHandler中新增前置状态,用于完成Reddit视频的获取和处理,完成后再进入提问环节。
- 在
- 初始化
loop_number变量(比如loop_number = 0),修复循环逻辑异常。
内容的提问来源于stack exchange,提问作者Nicolas Hoechli
相关产品推荐
相关产品推荐

