change callback aueries and handlers sequence

This commit is contained in:
o.vodianov 2025-08-10 23:28:28 +04:00
parent 3c87cc575c
commit c59ccf1908
12 changed files with 84 additions and 71 deletions

View File

@ -7,6 +7,7 @@ class Settings(BaseSettings):
BOT_TOKEN: str BOT_TOKEN: str
DATABASE_URL: str DATABASE_URL: str
ADMIN_API_KEY: str ADMIN_API_KEY: str
LOGGING_LEVEL: str
settings = Settings() settings = Settings()

View File

@ -11,9 +11,10 @@ def setup_logging():
Конфигурирует консольный и файловый логгеры с ротацией. Конфигурирует консольный и файловый логгеры с ротацией.
""" """
# Получаем уровень логирования из настроек, по умолчанию INFO # Получаем уровень логирования из настроек, по умолчанию INFO
level_name = settings.log_level.upper() if hasattr(settings, 'log_level') else 'INFO' level_name = settings.LOGGING_LEVEL if hasattr(settings, 'LOGGING_LEVEL') else 'INFO'
log_level = getattr(logging, level_name, logging.INFO) log_level = getattr(logging, level_name, logging.INFO)
# Форматтер для всех хэндлеров # Форматтер для всех хэндлеров
formatter = logging.Formatter( formatter = logging.Formatter(
fmt="%(asctime)s %(levelname)s [%(name)s] %(message)s", fmt="%(asctime)s %(levelname)s [%(name)s] %(message)s",

View File

@ -174,4 +174,5 @@ def get_verify_handler():
], ],
}, },
fallbacks=[], fallbacks=[],
allow_reentry=True,
) )

View File

@ -5,11 +5,11 @@ from telegram.ext import (
CommandHandler, CommandHandler,
MessageHandler, MessageHandler,
filters, filters,
CallbackQueryHandler
) )
from docbot.handlers.utils.cancel_handler import get_cancel_handler from docbot.handlers.utils.cancel_handler import get_cancel_handler
from docbot.services.doctors_service import add_payment_link from docbot.services.doctors_service import add_payment_link
from docbot.handlers.utils.utils import cleanup_session
from core.enums.dialog_helpers import ConfirmationMessage from core.enums.dialog_helpers import ConfirmationMessage
from core.logging import logger from core.logging import logger
from core.utils import is_valid_url from core.utils import is_valid_url
@ -28,11 +28,11 @@ async def send_info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
[ [
InlineKeyboardButton( InlineKeyboardButton(
ConfirmationMessage.PROCEED.value, ConfirmationMessage.PROCEED.value,
callback_data="accepted" callback_data="pay:accepted"
), ),
InlineKeyboardButton( InlineKeyboardButton(
ConfirmationMessage.DECLINE.value, ConfirmationMessage.DECLINE.value,
callback_data="declined" callback_data="pay:declined"
), ),
] ]
] ]
@ -51,7 +51,7 @@ async def send_info(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
async def ask_payment_link(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def ask_payment_link(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
user_id = context.user_data['user_id'] user_id = context.user_data['user_id']
await update.message.reply_text( await update.callback_query.message.reply_text(
text="Введите адрес вашей платёжной ссылки, пожалуйста." text="Введите адрес вашей платёжной ссылки, пожалуйста."
) )
logger.info(f"Ask user {user_id} to enter their payment link.") logger.info(f"Ask user {user_id} to enter their payment link.")
@ -67,7 +67,7 @@ async def ask_payment_system_api(update: Update, context: ContextTypes.DEFAULT_T
if is_valid_url(payment_link): if is_valid_url(payment_link):
logger.info(f"Payment link {payment_link} provided by user {user_id} is valid.") logger.info(f"Payment link {payment_link} provided by user {user_id} is valid.")
await update.message.reply_text( await update.message.reply_text(
text="Введите API ключ, который указан у вас личном кабинете плтёжной системы, пожалуйста." text="Введите API ключ, который указан у вас личном кабинете платёжной системы, пожалуйста."
) )
return ASK_PAYMENT_SYSTEM_API return ASK_PAYMENT_SYSTEM_API
else: else:
@ -106,8 +106,7 @@ def get_add_payment_method_handler() -> ConversationHandler:
entry_points=[add_payment_method()], entry_points=[add_payment_method()],
states={ states={
ASK_PAYMENT_LINK: [ ASK_PAYMENT_LINK: [
MessageHandler(filters.TEXT & ~filters.COMMAND, CallbackQueryHandler(ask_payment_link, pattern="^(pay:accepted)$")
ask_payment_link)
], ],
ASK_PAYMENT_SYSTEM_API: [ ASK_PAYMENT_SYSTEM_API: [
MessageHandler(filters.TEXT & ~filters.COMMAND, MessageHandler(filters.TEXT & ~filters.COMMAND,
@ -121,4 +120,5 @@ def get_add_payment_method_handler() -> ConversationHandler:
fallbacks=[get_cancel_handler()], fallbacks=[get_cancel_handler()],
name="add_payment_method", name="add_payment_method",
persistent=True, persistent=True,
allow_reentry=True,
) )

View File

@ -212,5 +212,6 @@ def get_register_doctor_first_stage_handler() -> ConversationHandler:
}, },
fallbacks=[get_cancel_handler()], fallbacks=[get_cancel_handler()],
name="register_doctor_conversation_first_stage", # для тестов/логирования name="register_doctor_conversation_first_stage", # для тестов/логирования
persistent=True, # если используете хранение состояний persistent=True,
allow_reentry=True, # если используете хранение состояний
) )

View File

@ -43,11 +43,11 @@ keyboard = [
[ [
InlineKeyboardButton( InlineKeyboardButton(
"Записаться на консультацию", "Записаться на консультацию",
callback_data="proceed_with_consultation" callback_data="consult:proceed_with_consultation"
), ),
InlineKeyboardButton( InlineKeyboardButton(
"Частые вопросы", "Частые вопросы",
callback_data="frequent_questions" callback_data="consult:frequent_questions"
), ),
] ]
] ]
@ -58,11 +58,11 @@ async def accept_personal_data_agreement(update: Update, context: ContextTypes.D
[ [
InlineKeyboardButton( InlineKeyboardButton(
ConfirmationMessage.PROCEED.value, ConfirmationMessage.PROCEED.value,
callback_data="accepted" callback_data="consult:accepted"
), ),
InlineKeyboardButton( InlineKeyboardButton(
ConfirmationMessage.DECLINE.value, ConfirmationMessage.DECLINE.value,
callback_data="declined" callback_data="consult:declined"
), ),
] ]
] ]
@ -94,7 +94,7 @@ async def receive_patient_aceptance(update: Update, context: ContextTypes.DEFAUL
user_data = context.user_data user_data = context.user_data
await update.callback_query.answer() await update.callback_query.answer()
if update.callback_query.data == "accepted": if update.callback_query.data == "consult:accepted":
user_data['accepted'] = True user_data['accepted'] = True
user_id = user_data['telegram_id'] user_id = user_data['telegram_id']
await create_patient(telegram_id=user_id, terms_acceptance=True) # Создаем пациента в БД await create_patient(telegram_id=user_id, terms_acceptance=True) # Создаем пациента в БД
@ -118,11 +118,11 @@ async def choose_consultation_type(update: Update, context: ContextTypes.DEFAULT
[ [
InlineKeyboardButton( InlineKeyboardButton(
"Первичная консультация", "Первичная консультация",
callback_data="initial_reception" callback_data="consult:initial_reception"
), ),
InlineKeyboardButton( InlineKeyboardButton(
"Повторная консультация", "Повторная консультация",
callback_data="readmission" callback_data="consult:readmission"
), ),
] ]
] ]
@ -181,7 +181,7 @@ async def receive_patient_phone(update: Update, context: ContextTypes.DEFAULT_TY
[ [
InlineKeyboardButton( InlineKeyboardButton(
"Изменить", "Изменить",
callback_data="back" callback_data="consult:back"
), ),
] ]
] ]
@ -199,7 +199,7 @@ async def receive_patient_phone(update: Update, context: ContextTypes.DEFAULT_TY
[ [
InlineKeyboardButton( InlineKeyboardButton(
"Изменить", "Изменить",
callback_data="back" callback_data="consult:back"
), ),
] ]
] ]
@ -251,11 +251,11 @@ async def receive_consultation_date(update: Update, context: ContextTypes.DEFAUL
[ [
InlineKeyboardButton( InlineKeyboardButton(
"Изменить", "Изменить",
callback_data="back" callback_data="consult:back"
), ),
InlineKeyboardButton( InlineKeyboardButton(
"Продолжить", "Продолжить",
callback_data="proceed" callback_data="consult:proceed"
), ),
] ]
] ]
@ -304,23 +304,24 @@ def get_consultation_handler() -> ConversationHandler:
return ConversationHandler( return ConversationHandler(
entry_points=[consultation_handler()], entry_points=[consultation_handler()],
states={ states={
SEND_ACKNOWLEDGEMENT_INFO: [CallbackQueryHandler(receive_patient_aceptance)], SEND_ACKNOWLEDGEMENT_INFO: [CallbackQueryHandler(receive_patient_aceptance, pattern=r"^consult:(accepted|declined)$")],
PROCEED_WITH_CONSULTATION: [CallbackQueryHandler(choose_consultation_type)], PROCEED_WITH_CONSULTATION: [CallbackQueryHandler(choose_consultation_type, pattern=r"^consult:proceed$")],
SELECT_CONSULTATION_TYPE: [CallbackQueryHandler(enter_patient_phone)], SELECT_CONSULTATION_TYPE: [CallbackQueryHandler(enter_patient_phone, pattern=r"^consult:(initial_reception|readmission)$")],
ENTER_PATIENT_PHONE: [MessageHandler(filters=filters.TEXT & ~filters.COMMAND, callback=receive_patient_phone)], ENTER_PATIENT_PHONE: [MessageHandler(filters=filters.TEXT & ~filters.COMMAND, callback=receive_patient_phone)],
ENTER_DOCTOR_NUMBER: [ ENTER_DOCTOR_NUMBER: [
MessageHandler(filters.TEXT & ~filters.COMMAND, receive_doctor_number), MessageHandler(filters.TEXT & ~filters.COMMAND, receive_doctor_number),
CallbackQueryHandler(enter_patient_phone, pattern="^" + "back" + "$") CallbackQueryHandler(enter_patient_phone, pattern="^consult:back$")
], ],
ENTER_CONSULTATION_DATE: [MessageHandler(filters=filters.TEXT & ~filters.COMMAND, callback=receive_consultation_date)], ENTER_CONSULTATION_DATE: [MessageHandler(filters=filters.TEXT & ~filters.COMMAND, callback=receive_consultation_date)],
PAY_CONSULTATION: [ PAY_CONSULTATION: [
CallbackQueryHandler(pay_consultation, pattern="^" + "proceed" + "$"), CallbackQueryHandler(pay_consultation, pattern="^consult:proceed$"),
CallbackQueryHandler(receive_doctor_number, pattern="^" + "back" + "$") CallbackQueryHandler(receive_doctor_number, pattern="^consult:back$")
], ],
ERROR_PHONE_NUMBER: [CallbackQueryHandler(enter_patient_phone, pattern="^" + "back" + "$")], ERROR_PHONE_NUMBER: [CallbackQueryHandler(enter_patient_phone, pattern="^consult:back$")],
STOPPING: [get_start_handler()], STOPPING: [get_start_handler()],
}, },
fallbacks=[get_cancel_handler()], fallbacks=[get_cancel_handler()],
name="consultation_dialog", name="consultation_dialog",
persistent=True, persistent=True,
allow_reentry=True,
) )

View File

@ -64,6 +64,7 @@ def get_send_form_handler() -> ConversationHandler:
], ],
}, },
fallbacks=[get_cancel_handler()], fallbacks=[get_cancel_handler()],
name="send_form_conversation", # для тестов/логирования name="send_form_conversation",
persistent=True, # если используете хранение состояний persistent=True,
allow_reentry=True,
) )

View File

@ -1,6 +1,6 @@
from telegram import Update from telegram import Update
from telegram.constants import ParseMode
from telegram.ext import ContextTypes, CommandHandler from telegram.ext import ContextTypes, CommandHandler
from core.logging import logger
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@ -8,12 +8,13 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
Отправляет приветствие и краткую инструкцию по работе с ботом. Отправляет приветствие и краткую инструкцию по работе с ботом.
""" """
text = ( text = (
"👋 Добро пожаловать в DocBot!\n\n" "👋 <b>Добро пожаловать в DocBot!</b>\n"
"Используйте команду /consultation, чтобы произвести запись на консультацию, если вы пациент.\n" "Используйте команду /consultation, чтобы произвести запись на консультацию, если вы пациент.\n\n"
"Используйте команду /register, если вы врач и у вас есть реферальный код." "Используйте команду /register, если вы врач и у вас есть реферальный код.\n\n"
"Используйте команду /help, чтобы увидеть список доступных вам команд."
) )
await context.bot.send_message(chat_id=update.effective_chat.id, text=text) await context.bot.send_message(chat_id=update.effective_chat.id, text=text, parse_mode=ParseMode.HTML)
def get_start_handler() -> CommandHandler: def get_start_handler() -> CommandHandler:

View File

@ -1,4 +1,4 @@
from telegram import Update from telegram import Update, ReplyKeyboardRemove
from telegram.ext import ( from telegram.ext import (
ContextTypes, ContextTypes,
ConversationHandler, ConversationHandler,
@ -7,7 +7,10 @@ from telegram.ext import (
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
await update.message.reply_text("❌ Отменено.") await update.message.reply_text(
"❌ Отменено.",
reply_markup=ReplyKeyboardRemove()
)
return ConversationHandler.END return ConversationHandler.END

View File

@ -1,5 +1,8 @@
from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove from telegram import Update
from telegram.ext import ContextTypes, CommandHandler from telegram.ext import (
ContextTypes, CommandHandler, ConversationHandler
)
from telegram.constants import ParseMode
from docbot.services.admins_service import get_admin_info from docbot.services.admins_service import get_admin_info
from docbot.services.doctors_service import get_doctor from docbot.services.doctors_service import get_doctor
@ -25,21 +28,20 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
) )
# (При необходимости в будущем можно добавить другие admin-команды) # (При необходимости в будущем можно добавить другие admin-команды)
await update.message.reply_text(text, parse_mode="Markdown") await update.message.reply_text(text, parse_mode="Markdown")
return
# 2) Проверим, зарегистрирован ли врач # 2) Проверим, зарегистрирован ли врач
if await get_doctor(user_id): elif await get_doctor(user_id):
text = ( text = (
"👨‍⚕️ *Меню доктора*:\n\n" "👨‍⚕️ <b>Меню доктора</b>:\n"
"/start Запустить бота\n" "/start Запустить бота\n"
"/sessions Посмотреть список пациентов (или текущих сессий)\n" "/payment_methods Настроить платёжные способы\n"
"/help Показать это меню\n" "/help Показать это меню\n"
) )
await update.message.reply_text(text, parse_mode="Markdown") await update.message.reply_text(text, parse_mode=ParseMode.HTML)
return
# 3) Иначе — это пациент (незарегистрированный/обычный пользователь) # 3) Иначе — это пациент (незарегистрированный/обычный пользователь)
else:
text = ( text = (
"👤 *Меню пациента*:\n\n" "👤 *Меню пациента*:\n\n"
"/start Запустить бота\n" "/start Запустить бота\n"
@ -49,6 +51,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
) )
await update.message.reply_text(text, parse_mode="Markdown") await update.message.reply_text(text, parse_mode="Markdown")
return ConversationHandler.END
def get_help_handler() -> CommandHandler: def get_help_handler() -> CommandHandler:

View File

@ -26,21 +26,21 @@ def main():
.concurrent_updates(concurrent_updates=False) .concurrent_updates(concurrent_updates=False)
.build() .build()
) )
app.add_handler(get_start_handler(), group=0)
app.add_handler(get_cancel_handler(), group=0)
app.add_handler(get_help_handler(), group=0)
app.add_handler(get_consultation_handler(), group=1)
app.add_handler(get_send_form_handler(), group=2)
app.add_handler(get_doctors_handler(), group=3)
app.add_handler(get_register_doctor_first_stage_handler(), group=1)
app.add_handler(get_referral_handlers(), group=3)
app.add_handler(get_verify_handler(), group=3)
app.add_handler(get_unknown_handler(), group=0)
app.add_handler(get_add_payment_method_handler(), group=1)
app.add_error_handler(get_start_handler())
app.add_handlers
logger.debug("Все хэндлеры зарегистрированы, запускаем polling") app.add_handler(get_start_handler())
app.add_handler(get_cancel_handler())
app.add_handler(get_help_handler())
app.add_handler(get_consultation_handler())
app.add_handler(get_send_form_handler())
app.add_handler(get_doctors_handler())
app.add_handler(get_register_doctor_first_stage_handler())
app.add_handler(get_referral_handlers())
app.add_handler(get_verify_handler())
app.add_handler(get_add_payment_method_handler())
app.add_handler(get_unknown_handler())
logger.info("Все хэндлеры зарегистрированы, запускаем polling")
app.run_polling() app.run_polling()