Конфликты хэндлеров в aiogram
Помоги разобраться с кодом пожалуйста. У меня возникает конфликт хэндлеров, а именно они вызваются в боте в неподходящее время, например при заполнении анкеты выполняются хэндлеры по порядку до функции form_city. За этой функцией должна следовать функция def form_sex но начинает выполняться функция view_profiles_by_gender из файла view_prifile.py. GPT говорит возможно, у вас где-то не сброшено состояние машины состояний (FSMContext), и обработчики начинают вызываться в неправильный момент. Но я так и не разобрался. Я использую aiogram 3.4.1
import asyncio
from aiogram import Bot, Dispatcher
import handlers
from dayvinchick.data.database import DataBase
from config_reader import config
async def main() -> None:
bot = Bot(config.bot_token.get_secret_value())
dp = Dispatcher()
db_users = DataBase("users_base.db", "users")
db_likes = DataBase("users_base.db", "likes")
await db_users.create_table()
await db_likes.create_table()
dp.include_routers(
handlers.start.router,
handlers.view_profile.router,
handlers.questionare.router,
handlers.create_profile.router
)
await bot.delete_webhook(drop_pending_updates=True)
await dp.start_polling(bot, db=db_users)
if __name__ == "__main__":
asyncio.run(main())
файл questionare.py
import aiohttp
import re
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from dayvinchick.keyboards.builders import form_btn
from dayvinchick.data.database import DataBase
from dayvinchick.utils.states import Form
from dayvinchick.utils.city import check_city
from dayvinchick.keyboards.reply import main
router = Router()
db = DataBase("users_base.db", "users") # Создание экземпляра класса DataBase
@router.message(F.text == "заполнить анкету")
async def my_form(message: Message, state: FSMContext):
#user_id = message.from_user.id
user_name = message.from_user.username
user_data = await db.get_user_data(user_name)
if user_data:
await message.answer("Такой пользователь уже существует!")
return
await state.set_state(Form.name)
await message.answer(
"Отлично, введи своё имя",
reply_markup=form_btn([message.from_user.first_name])
)
@router.message(Form.name)
async def form_name(message: Message, state: FSMContext):
name = message.text.strip()
# Проверяем, что имя состоит только из букв русского или латинского алфавита
if re.match("^[a-zA-Zа-яА-Я]+$", name):
await state.update_data(name=name)
await state.set_state(Form.age)
await message.answer("Теперь укажи свой возраст")
else:
await message.answer("Некорректный ввод! Имя должно содержать только буквы русского или латинского алфавита.")
@router.message(Form.age)
async def form_age(message: Message, state: FSMContext):
age_str = message.text.strip()
if age_str.isdigit():
age = int(age_str)
if 16 <= age <= 80:
await state.update_data(age=age)
await state.set_state(Form.city)
await message.answer("Теперь укажи свой город")
else:
await message.answer("Возраст должен быть от 16 до 80 лет. Попробуй ещё раз!")
else:
await message.answer("Попробуй ещё раз! Возраст должен быть целым числом.")
@router.message(Form.city)
async def form_city(message: Message, state: FSMContext):
city_name = message.text.strip()
if await check_city(city_name):
await state.update_data(city=city_name)
await state.set_state(Form.sex)
await message.answer(
"Теперь давай определимся с полом",
reply_markup=form_btn(["Парень", "Девушка"])
)
else:
await message.answer("Указанный город не найден. Попробуйте ещё раз!")
return
@router.message(Form.sex, F.text.casefold().in_(["парень", "девушка"]))
async def form_sex(message: Message, state: FSMContext):
await state.update_data(sex=message.text)
await state.set_state(Form.look_for)
await message.answer(
"Кого ты предпочитаешь искать?",
reply_markup=form_btn(["Парни", "Девушки", "Мне все равно"])
)
@router.message(Form.sex)
async def incorrect_form_sex(message: Message, state: FSMContext):
await message.answer("Выбери один вариант!")
@router.message(
Form.look_for,
F.text.casefold().in_(["девушки", "парни", "мне все равно"])
)
async def form_look_for(message: Message, state: FSMContext):
await state.update_data(look_for=message.text)
await state.set_state(Form.about)
await message.answer("Теперь расскажи о себе")
@router.message(Form.look_for)
async def incorrect_form_look_for(message: Message, state: FSMContext):
await message.answer("Выбери один вариант!")
@router.message(Form.about)
async def form_about(message: Message, state: FSMContext):
if len(message.text) < 5:
await message.answer("Введи что-нибудь поинтересней")
else:
await state.update_data(about=message.text)
await state.set_state(Form.photo)
await message.answer("Теперь отправь свое фото")
@router.message(Form.photo, F.photo)
async def form_photo(message: Message, state: FSMContext, db: DataBase):
photo_file_id = message.photo[-1].file_id
file_info = await message.bot.get_file(photo_file_id)
photo_url = f"https://api.telegram.org/file/bot{message.bot.token}/{file_info.file_path}"
async with aiohttp.ClientSession() as session:
async with session.get(photo_url) as resp:
photo_data = await resp.read()
data = await state.get_data()
#user_id = message.from_user.id
username = message.from_user.username
await state.clear()
frm_text = []
[frm_text.append(value) for _, value in data.items()]
await db.insert(username, frm_text, photo_data, photo_file_id)
#await db.insert(user_id, frm_text, photo_data)
await message.answer_photo(photo_file_id, "\n".join(map(str, frm_text)))
await message.answer("Выберите действие:", reply_markup=main)
@router.message(Form.photo, ~F.photo)
async def incorrect_form_photo(message: Message, state: FSMContext):
await message.answer("Отправь фото!")
файл view_profile.py
from aiogram import types
from aiogram import Router, F
from aiogram.fsm.context import FSMContext
from typing import Generator
from aiogram.fsm.state import State
from dayvinchick.utils.states import Form
from dayvinchick.data.database import DataBase
from dayvinchick.keyboards.inline import inline_kb
from dayvinchick.keyboards.reply import main, stat_menu, gender_menu, age_menu
from aiogram import Bot
from dayvinchick.config_reader import config
import logging
# Настройка логгера
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
router = Router()
bot = Bot(config.bot_token.get_secret_value())
db = DataBase("users_base.db", "users") # Создание экземпляра класса
db2 = DataBase("users_base.db", "likes")
#@router.message(F.text == "просмотр анкет")
#async def view_profiles(message: types.Message, state: FSMContext):
# profiles = await db.get_all_profiles() # Получаем все анкеты из базы данных
# if not profiles:
# await message.answer("В базе данных нет ни одной анкеты.")
# return
# Создаем генератор, который будет возвращать по одной анкете
# profilegenerator = (profile for profile in profiles)
# Получаем первую анкету
# profile = next(profilegenerator, None)
# Если анкета есть, то отправляем ее пользователю
# if profile:
# await send_profile(message, profile, profilegenerator, state) # Добавляем state в качестве аргумента
# else:
# await message.answer("В базе данных нет ни одной анкеты.")
@router.message(F.text == "просмотр анкет")
async def view_profiles(message: types.Message, state: FSMContext):
await message.answer("Выберите пол:", reply_markup=gender_menu)
@router.message(F.text == "Парень")
async def view_profiles_by_gender(message: types.Message, state: FSMContext):
gender = message.text
print(gender)
profiles = await db.get_profiles_by_gender(gender) # Замените эту строку вашим запросом к базе данных
print(profiles)
if not profiles:
await message.answer(f"В базе данных нет анкет с полом '{gender}'.")
return
profilegenerator = (profile for profile in profiles)
profile = next(profilegenerator, None)
if profile:
await send_profile(message, profile, profilegenerator, state)
else:
await message.answer(f"В базе данных нет анкет с полом '{gender}'.")
@router.message(F.text == "Девушка")
async def view_profiles_by_gender(message: types.Message, state: FSMContext):
gender = message.text
print(gender)
profiles = await db.get_profiles_by_gender(gender) # Замените эту строку вашим запросом к базе данных
print(profiles)
if not profiles:
await message.answer(f"В базе данных нет анкет с полом '{gender}'.")
return
profilegenerator = (profile for profile in profiles)
profile = next(profilegenerator, None)
if profile:
await send_profile(message, profile, profilegenerator, state)
else:
await message.answer(f"В базе данных нет анкет с полом '{gender}'.")
@router.message(State(Form.name))
async def send_profile(message: types.Message, profile: tuple, profilegenerator: Generator, state: FSMContext):
# Формируем текст анкеты
profilemessage = f"Имя: {profile[2]}\n" \
f"Возраст: {profile[3]}\n" \
f"Город: {profile[4]}\n" \
f"Пол: {profile[5]}\n" \
f"О себе: {profile[7]}\n"
# Отправляем текст анкеты
await message.answer(profilemessage, reply_markup=inline_kb)
# Отправляем фото
await message.answer_photo(profile[9]) # profile[9] содержит photofileid из базы данных
# Сохраняем текущую анкету и генератор в состояние
await state.update_data(profile=profile, profilegenerator=profilegenerator)
# Добавляем обработчик для кнопки "Статистика"
@router.message(F.text == "статистика")
async def show_stat_menu(message: types.Message):
await message.answer("Выберите вид статистики:", reply_markup=stat_menu)
@router.message(F.text == "полученные лайки")
async def view_likes_stat(message: types.Message):
user_name = message.from_user.username
if user_name:
user_data = await db.get_user_data(user_name)
if user_data:
await message.answer(f"Количество полученных лайков: {user_data['likes']}")
else:
await message.answer("Ошибка: Ваша анкета не найдена.")
else:
await message.answer("Ошибка: Не удалось получить информацию о вашем пользователе.")
@router.message(F.text == "отправленные лайки")
async def sent_likes_stat(message: types.Message, db: DataBase):
sender_username = message.from_user.username
if sender_username:
likes = await db.get_sent_likes(sender_username)
if likes:
response = "Отправленные лайки:\n"
for like in likes:
receiver_username = like[0]
receiver_user_name = like[1]
response += f"- [{receiver_user_name}](t.me/{receiver_username})\n"
await message.answer(response)
else:
await message.answer("У вас пока нет отправленных лайков.")
else:
await message.answer("Ошибка: Не удалось получить информацию о вашем пользователе.")
@router.message(F.text == "назад")
async def back_to_main_menu(message: types.Message):
await message.answer("Выберите действие:", reply_markup=main) # Отправляем главное меню
@router.callback_query(F.data == "write_message")
async def write_message_callback(query: types.CallbackQuery, state: FSMContext, db: DataBase):
await query.answer() # Ответим на запрос, чтобы Telegram не считал его просроченным
data = await state.get_data()
profile = data.get("profile")
if profile:
user_name = profile[1] # ID автора анкеты
author_data = await db.get_author_by_name(user_name) # Получаем данные об авторе анкеты
if author_data:
# Формируем URI для открытия чата с пользователем
username = author_data['user_name']
chat_uri = f"https://t.me/{username}"
await query.message.answer(f"Откройте чат с автором анкеты по ссылке: {chat_uri}")
else:
await query.message.answer("Информация об авторе анкеты не найдена.")
else:
await query.message.answer("Информация об анкете не найдена.")
@router.callback_query(F.data == "like")
async def likecallback(query: types.CallbackQuery, state: FSMContext, db: DataBase):
await query.answer() # Ответим на запрос, чтобы Telegram не считал его просроченным
# Получаем текущую анкету и генератор из состояния
data = await state.get_data()
profile = data.get("profile")
profilegenerator = data.get("profilegenerator")
# Увеличиваем количество лайков в базе данных для текущего профиля
await db.increment_likes(profile[1]) # Предположим, что profile[1] содержит идентификатор пользователя
# Добавляем запись о лайке в базу данных
sender_username = query.from_user.username # Используем имя пользователя, который поставил лайк
receiver_username = profile[1]
await db.insert_like(sender_username, receiver_username)
# Отправляем сообщение пользователю, что он поставил лайк
await query.message.answer(f"Вы поставили лайк {profile[2]}.")
# Получаем следующую анкету из генератора
profile = next(profilegenerator, None)
# Если анкета есть, то отправляем ее пользователю
if profile:
await send_profile(query.message, profile, profilegenerator, state)
else:
await query.message.answer("Вы просмотрели все анкеты в базе данных.")
@router.callback_query(F.data == "dislike")
async def dislikecallback(query: types.CallbackQuery, state: FSMContext):
await query.answer() # Ответим на запрос, чтобы Telegram не считал его просроченным
# Получаем текущую анкету и генератор из состояния
data = await state.get_data()
profile = data.get("profile")
profilegenerator = data.get("profilegenerator")
# Отправляем сообщение пользователю, что он поставил дизлайк
await query.message.answer(f"Вы поставили дизлайк {profile[2]}.")
# Получаем следующую анкету из генератора
profile = next(profilegenerator, None)
# Если анкета есть, то отправляем ее пользователю
if profile:
await send_profile(query.message, profile, profilegenerator, state)
else:
await query.message.answer("Вы просмотрели все анкеты в базе данных.")
@router.callback_query(F.data == "main_menu")
async def main_menu_callback(query: types.CallbackQuery):
await query.answer() # Ответим на запрос, чтобы Telegram не считал его просроченным
await query.message.answer("Выберите действие:", reply_markup=main) # Отправляем главное меню
await query.message.delete_reply_markup() # Скрываем inline клавиатуру