Telegram机器人AttributeError修复求助:NoneType无from_user属性
问题描述
我正在开发一款Telegram托管服务机器人(PayEscrowBOT),功能逻辑如下:
- 用户发送
/start指令后,机器人发送带两个Inline Keyboard按钮的欢迎消息; - 点击“ℹ️ INSTRUCTIONS”按钮,欢迎消息会切换为带“⬅️ BACK”按钮的说明消息;
- 期望点击BACK按钮时切回原欢迎消息,但尝试后触发报错:
AttributeError: 'NoneType' object has no attribute 'from_user'
以下是完整代码及报错栈:
from telegram import InlineKeyboardMarkup, InlineKeyboardButton from telegram.ext import Updater, CommandHandler, CallbackQueryHandler # Ihr Bot-Token TOKEN = "Bot_Token" # Ein globaler Wert, um den Zustand des Bots zu verfolgen START, INSTRUCTIONS = range(2) def start(update, context): user = update.message.from_user # Text der Willkommensnachricht welcome_message = """⚜️ **PayEscrowBOT** ⚜️ v.1.1 #1 Automated Telegram Escrow Service Welcome to **PayEscrowBOT**™️. This bot provides a safe escrow service for your business on Telegram. Never get ripped again, The bot secures your funds throughout the deal. If you have any issues, kindly type /contact and an arbitrator will join the group chat within 24 hours. 🎟 **ESCROW FEE:** 5% if over $100 5$ if under $100 ☑️ **DEALS DONE:** 1200 🧑⚖️ **DISPUTES HANDLED:** 280 💬 **Declare the seller or buyer with** /seller or /buyer [BTC/LTC ADDRESS] (**Your BTC/LTC address =** [BTC/LTC ADDRESS]) 💡 Type /menu **to summon a menu with all bot's features**""" # Erstes Inline-Keyboard keyboard1 = [[InlineKeyboardButton("❓ WHAT IS ESCROW", callback_data="what_is_escrow"), InlineKeyboardButton("ℹ️ INSTRUCTIONS", callback_data="instructions")]] # Inline-Keyboard-Markup für das erste Keyboard reply_markup1 = InlineKeyboardMarkup(keyboard1) # Senden der Willkommensnachricht mit dem Inline-Keyboard update.message.reply_markdown(welcome_message, reply_markup=reply_markup1) # Setzen des Bots in den START-Zustand context.user_data["state"] = START def instructions(update, context): query = update.callback_query query.answer() # Nachricht mit den Anweisungen instructions_message = """💬 INSTRUCTIONS 1️⃣ Add the bot to a new Group Chat with buyer + seller 2️⃣ Declare the seller with /seller [BTC/LTC ADDRESS] Replace [BTC/LTC ADDRESS] with receive wallet 3️⃣ Declare the buyer with /buyer [BTC/LTC ADDRESS] Replace [BTC/LTC ADDRESS] with refund wallet 4️⃣ Generate a wallet with /generate 5️⃣ Buyer sends agreed amount to generated BTC wallet 6️⃣ Check balance with /balance 7️⃣ Seller releases product / service to buyer when balance is confirmed 8️⃣ When buyer is satisfied, release payment with /release The seller can also /refund the buyer. 9️⃣ If any issues, contact arbitrator with /contact ❗️ For video example, type /video ☎️ If you need help, type /contact [INVITE LINK TO GROUP] Replace [INVITE LINK TO GROUP] with the invite link of the group chat.""" # Inline-Keyboard für den Zurück-Button back_keyboard = [[InlineKeyboardButton("⬅️ BACK", callback_data="back")]] back_markup = InlineKeyboardMarkup(back_keyboard) # Senden der Anweisungsnachricht mit dem Zurück-Button query.edit_message_text(text=instructions_message, reply_markup=back_markup) # Setzen des Bots in den INSTRUCTIONS-Zustand context.user_data["state"] = INSTRUCTIONS def back(update, context): query = update.callback_query query.answer() if context.user_data["state"] == INSTRUCTIONS: # Zurück zur Willkommensnachricht mit den Optionen start(update, context) def button(update, context): query = update.callback_query query.answer() if query.data == "instructions": instructions(update, context) elif query.data == "back": start(update, context) else: query.edit_message_text(text=f"Du hast {query.data} ausgewählt") def main(): updater = Updater(token=TOKEN, use_context=True) dp = updater.dispatcher # Definieren Sie den /start-Handler dp.add_handler(CommandHandler("start", start)) # Definieren Sie den Callback-Handler für die Inline-Keyboards dp.add_handler(CallbackQueryHandler(button)) updater.start_polling() updater.idle() if __name__ == "__main__": main()
报错信息:
No error handlers are registered, logging exception. Traceback (most recent call last): File "/Users/philippalbrecht/.pyenv/versions/3.11.1/lib/python3.11/site-packages/telegram/ext/dispatcher.py", line 340, in process_update handler.handle_update(update, self, check, context) File "/Users/philippalbrecht/.pyenv/versions/3.11.1/lib/python3.11/site-packages/telegram/ext/handler.py", line 119, in handle_update return self.callback(update, context) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/philippalbrecht/Desktop/Telegram/Telegram_escrow", line 101, in button back(update, context) File "/Users/philippalbrecht/Desktop/Telegram/Telegram_escrow", line 92, in back start(update, context) File "/Users/philippalbrecht/Desktop/Telegram/Telegram_escrow", line 11, in start user = update.message.from_user ^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'from_user'
错误原因
点击BACK按钮触发的是回调查询(CallbackQuery),此时update.message为None——回调类型的更新不包含message对象,只有callback_query。而你直接调用依赖update.message的start函数,自然会触发属性不存在的错误。
修复方案
单独编写处理返回逻辑的函数,通过callback_query.edit_message_text直接修改当前消息内容为欢迎界面,避免调用依赖命令消息的start函数。同时提取重复内容为全局常量,减少代码冗余。
修改后的完整代码
from telegram import InlineKeyboardMarkup, InlineKeyboardButton from telegram.ext import Updater, CommandHandler, CallbackQueryHandler # 你的Bot Token TOKEN = "Bot_Token" # 状态常量 START, INSTRUCTIONS = range(2) # 复用欢迎消息和键盘定义,避免重复代码 WELCOME_MESSAGE = """⚜️ **PayEscrowBOT** ⚜️ v.1.1 #1 Automated Telegram Escrow Service Welcome to **PayEscrowBOT**™️. This bot provides a safe escrow service for your business on Telegram. Never get ripped again, The bot secures your funds throughout the deal. If you have any issues, kindly type /contact and an arbitrator will join the group chat within 24 hours. 🎟 **ESCROW FEE:** 5% if over $100 5$ if under $100 ☑️ **DEALS DONE:** 1200 🧑⚖️ **DISPUTES HANDLED:** 280 💬 **Declare the seller or buyer with** /seller or /buyer [BTC/LTC ADDRESS] (**Your BTC/LTC address =** [BTC/LTC ADDRESS]) 💡 Type /menu **to summon a menu with all bot's features**""" WELCOME_KEYBOARD = InlineKeyboardMarkup([ [InlineKeyboardButton("❓ WHAT IS ESCROW", callback_data="what_is_escrow"), InlineKeyboardButton("ℹ️ INSTRUCTIONS", callback_data="instructions")] ]) def start(update, context): # 处理/start命令,此时update包含message对象 user = update.message.from_user # 发送欢迎消息 update.message.reply_markdown(WELCOME_MESSAGE, reply_markup=WELCOME_KEYBOARD) # 设置状态为START context.user_data["state"] = START def instructions(update, context): query = update.callback_query query.answer() instructions_message = """💬 INSTRUCTIONS 1️⃣ Add the bot to a new Group Chat with buyer + seller 2️⃣ Declare the seller with /seller [BTC/LTC ADDRESS] Replace [BTC/LTC ADDRESS] with receive wallet 3️⃣ Declare the buyer with /buyer [BTC/LTC ADDRESS] Replace [BTC/LTC ADDRESS] with refund wallet 4️⃣ Generate a wallet with /generate 5️⃣ Buyer sends agreed amount to generated BTC wallet 6️⃣ Check balance with /balance 7️⃣ Seller releases product / service to buyer when balance is confirmed 8️⃣ When buyer is satisfied, release payment with /release The seller can also /refund the buyer. 9️⃣ If any issues, contact arbitrator with /contact ❗️ For video example, type /video ☎️ If you need help, type /contact [INVITE LINK TO GROUP] Replace [INVITE LINK TO GROUP] with the invite link of the group chat.""" back_keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ BACK", callback_data="back")]]) # 修改当前消息为说明内容 query.edit_message_text(text=instructions_message, reply_markup=back_keyboard) context.user_data["state"] = INSTRUCTIONS def back_to_welcome(update, context): query = update.callback_query query.answer() # 修改当前消息为欢迎内容和对应键盘 query.edit_message_text(text=WELCOME_MESSAGE, reply_markup=WELCOME_KEYBOARD, parse_mode='Markdown') context.user_data["state"] = START def button(update, context): query = update.callback_query query.answer() if query.data == "instructions": instructions(update, context) elif query.data == "back": back_to_welcome(update, context) else: query.edit_message_text(text=f"你选择了 {query.data}") def main(): updater = Updater(token=TOKEN, use_context=True) dp = updater.dispatcher dp.add_handler(CommandHandler("start", start)) dp.add_handler(CallbackQueryHandler(button)) updater.start_polling() updater.idle() if __name__ == "__main__": main()
核心修改点
- 将欢迎消息和键盘提取为全局常量,避免重复编写;
- 新增
back_to_welcome函数,专门处理BACK按钮回调,通过query.edit_message_text直接修改当前消息,不依赖update.message; - 在
button函数中,点击back时调用back_to_welcome而非start函数。
内容的提问来源于stack exchange,提问作者vonAlbrecht
相关产品推荐
相关产品推荐

