from datetime import date, timedelta from aiogram import Router, F from aiogram.filters import Command from aiogram.types import Message, CallbackQuery from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.fsm.context import FSMContext from states import GroupState from api import api from database import save_group, get_group, update_subgroup import math from keyboards import main_keyboard from keyboards import subgroup_keyboard from keyboards import ( groups_keyboard, date_keyboard, calendar_keyboard, main_keyboard, start_keyboard, ) router = Router() DAYS = ( "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс", ) def format_date(day: date) -> str: return f"{day.strftime('%d.%m')} ({DAYS[day.weekday()]})" def build_keyboard(group_id: int, current_day: date): kb = InlineKeyboardBuilder() kb.button( text="⬅️", callback_data=f"day:{group_id}:{(current_day - timedelta(days=1)).isoformat()}" ) kb.button( text=format_date(current_day), callback_data="ignore" ) kb.button( text="➡️", callback_data=f"day:{group_id}:{(current_day + timedelta(days=1)).isoformat()}" ) kb.adjust(3) return kb.as_markup() async def build_groups_keyboard(page: int = 0): groups = await api.get_groups() groups.sort( key=lambda g: g["groupName"] ) PER_PAGE = 30 start = page * PER_PAGE end = start + PER_PAGE current = groups[start:end] kb = InlineKeyboardBuilder() for group in current: kb.button( text=group["groupName"], callback_data=f"select:{group['groupID']}" ) kb.adjust(3) pages = math.ceil(len(groups) / PER_PAGE) if page > 0: kb.button( text="◀️", callback_data=f"groups_page:{page-1}" ) kb.button( text=f"{page+1}/{pages}", callback_data="ignore" ) if page < pages - 1: kb.button( text="▶️", callback_data=f"groups_page:{page+1}" ) kb.adjust(3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3) return kb.as_markup() def build_schedule( schedule: dict, group: dict, selected_day: date ) -> str: lessons = [ lesson for lesson in schedule.get("rasp", []) if lesson.get("дата", "")[:10] == selected_day.isoformat() ] lessons.sort( key=lambda lesson: ( lesson.get("номерЗанятия", 0), lesson.get("начало", "") ) ) days = ( "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье" ) text = ( "╔════════════════════╗\n" " 📅 РАСПИСАНИЕ\n" "╚════════════════════╝\n\n" f"🎓 {group['groupName']}\n" f"📆 {days[selected_day.weekday()]}, " f"{selected_day.strftime('%d.%m.%Y')}\n\n" ) if not lessons: return text + "🎉 На этот день занятий нет." separator = "━━━━━━━━━━━━━━━━━━━━\n" for lesson in lessons: text += separator number = lesson.get("номерЗанятия", "-") text += ( f"🟧 {number} занятие\n" f"🕐 {lesson.get('начало', '?')} — " f"{lesson.get('конец', '?')}\n\n" ) text += ( f"📚 {lesson.get('дисциплина', '-')}\n" ) teacher = lesson.get("преподаватель") if teacher: text += f"👨‍🏫 {teacher}\n" aud = lesson.get("аудитория") if aud: text += f"🏫 Аудитория: {aud}\n" subgroup = lesson.get("номерПодгруппы") if subgroup: text += f"👥 Подгруппа: {subgroup}\n" text += "\n" return text async def send_schedule( message: Message, group: dict, selected_day: date ): schedule = await api.get_schedule( group["groupID"], selected_day ) text = build_schedule( schedule, group, selected_day ) LIMIT = 4000 if len(text) <= LIMIT: await message.answer( text, reply_markup=build_keyboard( group["groupID"], selected_day ) ) return parts = [] current = "" for line in text.split("\n"): if len(current) + len(line) + 1 > LIMIT: parts.append(current) current = "" current += line + "\n" if current: parts.append(current) for i, part in enumerate(parts): await message.answer( part, reply_markup=( build_keyboard( group["groupID"], selected_day ) if i == len(parts) - 1 else None ) ) @router.message(Command("find_group")) @router.message( F.text.in_( { "🔍 Выбрать группу", "🔍 Сменить группу" } ) ) async def find_group(message: Message): await message.answer( "📚 Выберите свою группу", reply_markup=await build_groups_keyboard(0) ) @router.message( GroupState.waiting_group ) async def search_group( message: Message, state: FSMContext ): query = message.text.strip() groups = await api.search_group_by_name(query) if not groups: await message.answer( "❌ Группа не найдена.\nПопробуйте ещё раз." ) return if len(groups) == 1: group = groups[0] await message.answer( "✅ Группа сохранена.", reply_markup=main_keyboard() ) await state.clear() await message.answer( f"✅ Группа {group['groupName']} сохранена." ) await message.answer( "📅 Выберите дату:", reply_markup=date_keyboard() ) return kb = InlineKeyboardBuilder() for group in groups[:10]: kb.button( text=f"{group['groupName']} (курс {group['course']})", callback_data=f"select:{group['groupID']}" ) kb.adjust(1) await message.answer( "Выберите группу:", reply_markup=kb.as_markup() ) async def search_group(message: Message): query = message.text.strip() groups = await api.search_group_by_name(query) if not groups: await message.answer( "❌ Группа не найдена." ) return if len(groups) == 1: group = groups[0] await save_group( message.from_user.id, group["groupID"], group["groupName"] ) await message.answer( f"✅ Группа {group['groupName']} сохранена." ) await send_schedule( message, group, date.today() ) return kb = InlineKeyboardBuilder() for group in groups[:10]: kb.button( text=f"{group['groupName']} (курс {group['course']})", callback_data=f"select:{group['groupID']}" ) kb.adjust(1) await message.answer( "Найдено несколько групп.\nВыберите нужную:", reply_markup=kb.as_markup() ) # ========================== # ВЫБОР ГРУППЫ # ========================== @router.callback_query( F.data.startswith("select:") ) async def select_group( callback: CallbackQuery, state: FSMContext ): group_id = int( callback.data.split(":")[1] ) group = await api.get_group_by_id( group_id ) if group is None: await callback.answer( "Группа не найдена.", show_alert=True ) return await save_group( callback.from_user.id, group["groupID"], group["groupName"] ) await state.clear() await callback.message.edit_text( "🎓 Группа сохранена!\n\n" "Теперь выберите свою подгруппу:", reply_markup=subgroup_keyboard() ) await callback.answer() @router.callback_query(F.data.startswith("groups_page:")) async def groups_page(callback: CallbackQuery): page = int(callback.data.split(":")[1]) await callback.message.edit_reply_markup( reply_markup=await build_groups_keyboard(page) ) await callback.answer() @router.message( F.text.regexp(r"^\d{1,2}[./]\d{1,2}([./]\d{2,4})?$") ) async def schedule_by_date(message: Message): saved_group = await get_group( message.from_user.id ) if not saved_group: await message.answer( "❌ Сначала выберите группу." ) return text = message.text.strip().replace("/", ".") try: parts = text.split(".") if len(parts) == 2: selected_date = date( year=date.today().year, month=int(parts[1]), day=int(parts[0]), ) else: year = int(parts[2]) if year < 100: year += 2000 selected_date = date( year=year, month=int(parts[1]), day=int(parts[0]), ) except ValueError: await message.answer( "❌ Некорректная дата." ) return group = await api.get_group_by_id( saved_group["group_id"] ) if group is None: await message.answer( "❌ Группа не найдена." ) return await send_schedule( message, group, selected_date ) # ========================== # ПУСТАЯ КНОПКА # ========================== @router.callback_query(F.data == "ignore") async def ignore_callback(callback: CallbackQuery): await callback.answer() # ========================== # ПЕРЕКЛЮЧЕНИЕ ДНЕЙ # ========================== @router.callback_query(F.data.startswith("day:")) async def change_day(callback: CallbackQuery): try: _, group_id, day = callback.data.split(":") selected_date = date.fromisoformat(day) group = await api.get_group_by_id( int(group_id) ) if group is None: await callback.answer( "Группа не найдена.", show_alert=True ) return schedule = await api.get_schedule( group["groupID"], selected_date ) text = build_schedule( schedule, group, selected_date ) if len(text) > 4096: await callback.answer( "Расписание слишком большое. Используйте ввод даты.", show_alert=True ) return await callback.message.edit_text( text, reply_markup=build_keyboard( group["groupID"], selected_date ) ) await callback.answer() except Exception as e: await callback.answer( str(e), show_alert=True ) @router.callback_query(F.data == "date:today") async def date_today(callback: CallbackQuery): saved = await get_group(callback.from_user.id) if not saved: await callback.answer("Сначала выберите группу") return group = await api.get_group_by_id(saved["group_id"]) schedule = await api.get_schedule( group["groupID"], date.today() ) text = build_schedule( schedule, group, date.today() ) await callback.message.edit_text( text, reply_markup=build_keyboard( group["groupID"], date.today() ) ) @router.callback_query(F.data == "date:tomorrow") async def date_tomorrow(callback: CallbackQuery): tomorrow = date.today() + timedelta(days=1) saved_group = await get_group(callback.from_user.id) if not saved_group: await callback.answer("Сначала выберите группу.", show_alert=True) return group = await api.get_group_by_id(saved_group["group_id"]) await callback.message.edit_text( build_schedule( await api.get_schedule(group["groupID"], tomorrow), group, tomorrow ), reply_markup=build_keyboard(group["groupID"], tomorrow) ) await callback.answer() @router.callback_query(F.data == "date:input") async def input_date(callback: CallbackQuery): await callback.message.answer( "Введите дату в формате:\n\n" "01.09.2026" ) await callback.answer() @router.callback_query(F.data == "date:calendar") async def open_calendar(callback: CallbackQuery): today = date.today() await callback.message.edit_reply_markup( reply_markup=calendar_keyboard( today.year, today.month ) ) await callback.answer() @router.callback_query(F.data.startswith("month:")) async def change_month(callback: CallbackQuery): _, year, month = callback.data.split(":") await callback.message.edit_reply_markup( reply_markup=calendar_keyboard( int(year), int(month) ) ) await callback.answer() @router.callback_query(F.data.startswith("calendar:")) async def calendar_select(callback: CallbackQuery): _, year, month, day = callback.data.split(":") selected_day = date( int(year), int(month), int(day) ) saved_group = await get_group( callback.from_user.id ) if not saved_group: await callback.answer( "Сначала выберите группу.", show_alert=True ) return group = await api.get_group_by_id( saved_group["group_id"] ) schedule = await api.get_schedule( group["groupID"], selected_day ) text = build_schedule( schedule, group, selected_day ) await callback.message.edit_text( text, reply_markup=build_keyboard( group["groupID"], selected_day ) ) await callback.answer() @router.callback_query(F.data == "calendar_today") async def calendar_today(callback: CallbackQuery): today = date.today() saved_group = await get_group( callback.from_user.id ) if not saved_group: return group = await api.get_group_by_id( saved_group["group_id"] ) schedule = await api.get_schedule( group["groupID"], today ) text = build_schedule( schedule, group, today ) await callback.message.edit_text( text, reply_markup=build_keyboard( group["groupID"], today ) ) await callback.answer() @router.message(F.text == "📅 Расписание") async def schedule_menu(message: Message): await message.answer( "📅 Выберите дату:", reply_markup=date_keyboard() ) @router.message(F.text == "🔄 Группа") async def change_group(message: Message): await message.answer( "Введите номер группы:", reply_markup=start_keyboard() ) @router.message(F.text == "⚙️ Моя группа") async def my_group(message: Message): saved_group = await get_group(message.from_user.id) if not saved_group: await message.answer( "❌ У вас ещё не выбрана группа.\n\nНажмите «🔍 Выбрать группу»." ) return await message.answer( f"🎓 Ваша группа: {saved_group['group_name']}" ) @router.callback_query(F.data.startswith("sub:")) async def select_subgroup(callback: CallbackQuery): subgroup = callback.data.split(":")[1] await update_subgroup( callback.from_user.id, subgroup ) await callback.answer( "Подгруппа сохранена!" ) await callback.message.edit_text( f"✅ Подгруппа {subgroup} сохранена!" ) await callback.message.answer( "Выберите действие:", reply_markup=main_keyboard() )