Python-telegram-bot inlineKeyboardButton вовзращает ошибку при нажатии
Использую Python Telegram Bot последней версии, логика такая, что есть рандомный пользователь который может написать в бота и сформировать определенную заявку, где в зависимости от "Проекта" она может перенаправляться различным Approver. Интересно, что если тот кто пишет в бота и является Approver1 то в рамках его чата с ботом все работает исправно ( не срабатывает условие правда elif decision == "approve4":, но с этим я смогу разобраться), а если же пишет рандомный пользователь и формирует заявку, то Approver 1 получает её и при нажатии на Approve я получаю ошибку:
Traceback (most recent call last):
File "/home/bot/.local/lib/python3.10/site-packages/telegram/ext/_application.py", line 1197, in __create_task_callback
return await coroutine # type: ignore[misc]
File "/home/bot/.local/lib/python3.10/site-packages/telegram/ext/_handlers/basehandler.py", line 157, in handle_update
return await self.callback(update, context)
File "/home/bot/qubot/newbot.py", line 223, in keyboard_callback
await proceed_with_decision(update, context, state)
File "/home/bot/qubot/newbot.py", line 259, in proceed_with_decision
approval_message = f"Согласующий {next_stage - APPROVAL1 + 1}\n\n" + context.user_data['final_response']
KeyError: 'final_response'
Вот мой код, возможно неправильно обрабатываю в handler-ах нажатие на кнопки, но не могу понять в чём же проблема:
import logging
import sqlite3
from datetime import datetime
import pytz
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, ConversationHandler, MessageHandler, filters
from telegram.constants import ParseMode
from telegram.request import HTTPXRequest
# Enable logging
logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
# Define states for the conversation
CHOOSING, CONFIRMATION, AMOUNT, DESCRIPTION, SUMM, APPROVAL1, APPROVAL2, APPROVAL3, APPROVAL4, FINAL_APPROVAL, REJECTION_COMMENT = range(11)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
if update.message.chat.type != "private":
return ConversationHandler.END
keyboard = [
[
InlineKeyboardButton("Проект ?️", callback_data="project"),
InlineKeyboardButton("Цех ?", callback_data="workshop"),
InlineKeyboardButton("Офис ?", callback_data="office"),
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
welcome_message = (
"Вас приветствует telegram бот MDA distribution! ?\n"
"Заполните, пожалуйста, вашу заявку. Для начала выберите направление:\n"
)
await update.message.reply_text(welcome_message, reply_markup=reply_markup)
return CHOOSING
async def project_choice(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
query = update.callback_query
await query.answer()
context.user_data['project'] = query.data
keyboard = [
[InlineKeyboardButton("1️⃣ ? Внутрискладская транспортировка", callback_data="warehouse_transport")],
[InlineKeyboardButton("2️⃣ ? Доставка материалов", callback_data="materials")],
[InlineKeyboardButton("3️⃣ ? Доставка материалов по межгороду", callback_data="materials_intercity")],
[InlineKeyboardButton("4️⃣ ? Курьерские услуги", callback_data="courier_services")],
[InlineKeyboardButton("5️⃣ ? Транспортные услуги", callback_data="transport_services")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.message.edit_text("Выберите статью:", reply_markup=reply_markup)
return CONFIRMATION
async def confirmation(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
query = update.callback_query
await query.answer()
context.user_data['category'] = query.data
await query.message.edit_text("Введите название сделки:")
return AMOUNT
async def amount_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
context.user_data['deal_text'] = update.message.text
await update.message.reply_text("Введите сумму сделки:")
return DESCRIPTION
async def description_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
amount_text = update.message.text
try:
context.user_data['amount'] = format_amount(amount_text)
except ValueError:
await update.message.reply_text("Вы не ввели сумму ( принимаются только числа), напишите ОК и попробуйте снова")
return AMOUNT
await update.message.reply_text("Введите описание сделки:")
return SUMM
async def summarize_input(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
context.user_data['description'] = update.message.text
project = CALLBACK_TRANSLATIONS.get(context.user_data.get('project'), 'Not provided')
category = CALLBACK_TRANSLATIONS.get(context.user_data.get('category'), 'Not provided')
deal_text = context.user_data.get('deal_text', 'Not provided')
amount = context.user_data.get('amount', 'Not provided')
description = context.user_data.get('description', 'Not provided')
dt = format_datetime(update.message.date)
final_response = (
f"<b>Заявка от:</b> <i>{update.message.from_user.first_name}</i>\n"
f"<b>Дата:</b> <i>{dt}</i>\n"
f"<b>Направление:</b> <i>{project}</i>\n"
f"<b>Статья:</b> <i>{category}</i>\n"
f"<b>Наименование сделки:</b> <i>{deal_text}</i>\n"
f"<b>Сумма:</b> <i>{amount}</i>\n"
f"<b>Описание:</b> <i>{description}</i>"
)
context.user_data['final_response'] = final_response
approval_message = "Согласующий 1\n\n" + final_response
approval_keyboard = [
[InlineKeyboardButton("✅ Принять", callback_data="approve1")],
[InlineKeyboardButton("? Отклонить", callback_data="reject1")],
]
approval_markup = InlineKeyboardMarkup(approval_keyboard)
logger.info("Context user data before sending approval message: %s", context.user_data)
logger.info("Sending approval message to approver %s", APPROVER1_ID)
await context.bot.send_message(chat_id=APPROVER1_ID, text=approval_message, reply_markup=approval_markup, parse_mode=ParseMode.HTML)
return APPROVAL1
async def keyboard_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
decision = query.data
await query.answer()
await query.edit_message_reply_markup(reply_markup=None) # Hide buttons after approval
# Logging context.user_data for debugging
logger.info("Context user data in keyboard_callback: %s", context.user_data)
state = None
if decision.startswith("approve"):
state = int(decision.replace("approve", "")) + APPROVAL1
elif decision.startswith("reject"):
context.user_data['approver'] = query.from_user.id
await query.message.reply_text("Введите комментарий для отказа:")
state = REJECTION_COMMENT
if state is not None:
context.user_data['decision'] = decision
await proceed_with_decision(update, context, state)
async def proceed_with_decision(update: Update, context: ContextTypes.DEFAULT_TYPE, state: int) -> int:
decision = context.user_data.get('decision', None)
project = context.user_data.get('project', None)
next_approver = None
next_stage = None
if state == REJECTION_COMMENT:
await update.message.reply_text("Введите комментарий для отказа:")
return REJECTION_COMMENT
if decision == "approve1":
if project == "workshop":
next_approver = APPROVER2_ID
next_stage = APPROVAL2
elif project == "project":
next_approver = APPROVER3_ID
next_stage = APPROVAL3
else:
next_approver = APPROVER4_ID
next_stage = APPROVAL4
elif decision == "approve4":
amount = int(context.user_data.get('amount', '0').replace(" ", "").replace("₸", "").strip())
if amount >= 100000:
next_approver = APPROVER5_ID
next_stage = FINAL_APPROVAL
else:
final_response = context.user_data['final_response'] + "\n\n✅ ЗАЯВКА ПРИНЯТА"
await context.bot.send_message(chat_id=GROUP_CHAT_ID, text=final_response, parse_mode=ParseMode.HTML)
return ConversationHandler.END
if next_approver and next_stage:
approval_message = f"Согласующий {next_stage - APPROVAL1 + 1}\n\n" + context.user_data['final_response']
approval_keyboard = [
[InlineKeyboardButton("✅ Принять", callback_data=f"approve{next_stage - APPROVAL1 + 1}")],
[InlineKeyboardButton("? Отклонить", callback_data=f"reject{next_stage - APPROVAL1 + 1}")],
]
approval_markup = InlineKeyboardMarkup(approval_keyboard)
logger.info("Sending approval message to approver %s", next_approver)
await context.bot.send_message(chat_id=next_approver, text=approval_message, reply_markup=approval_markup, parse_mode=ParseMode.HTML)
return next_stage
async def rejection_comment(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
comment_text = update.message.text
context.user_data['rejection_comment'] = comment_text
approver = context.user_data['approver']
final_response = context.user_data['final_response'] + f"\n\n? ЗАЯВКА ОТКЛОНЕНА\n\n<b>Комментарий:</b> <i>{comment_text}</i>"
await context.bot.send_message(chat_id=GROUP_CHAT_ID, text=final_response, parse_mode=ParseMode.HTML)
await context.bot.send_message(chat_id=approver, text="Комментарий для отказа принят и заявка отправлена в группу.", parse_mode=ParseMode.HTML)
# Save the rejected request to the database with the comment
context.user_data['comment'] = comment_text
logger.info("Saving request to database with context user data: %s", context.user_data)
save_request(context.user_data)
return ConversationHandler.END
def error_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
logger.error(msg="Exception while handling an update:", exc_info=context.error)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Используйте /start для начала работы с ботом.")
def main() -> None:
init_db()
request = HTTPXRequest(connect_timeout=60.0, read_timeout=60.0)
application = Application.builder().token("token").request(request).concurrent_updates(True).build()
conv_handler = ConversationHandler(
entry_points=[CommandHandler("start", start)],
states={
CHOOSING: [CallbackQueryHandler(project_choice, block=False)],
CONFIRMATION: [CallbackQueryHandler(confirmation, block=False)],
AMOUNT: [MessageHandler(filters.TEXT & ~filters.COMMAND, amount_input)],
DESCRIPTION: [MessageHandler(filters.TEXT & ~filters.COMMAND, description_input)],
SUMM: [MessageHandler(filters.TEXT & ~filters.COMMAND, summarize_input, block=False)],
REJECTION_COMMENT: [MessageHandler(filters.TEXT & ~filters.COMMAND, rejection_comment, block=False)],
},
fallbacks=[],
)
application.add_error_handler(error_handler)
application.add_handler(conv_handler)
application.add_handler(CommandHandler("help", help_command, block=False))
application.add_handler(CallbackQueryHandler(keyboard_callback, block=False))
application.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()