Files
muha-bot/handlers/group.py
T
2026-07-09 23:34:34 +03:00

803 lines
17 KiB
Python

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"
" 📅 <b>РАСПИСАНИЕ</b>\n"
"╚════════════════════╝\n\n"
f"🎓 <b>{group['groupName']}</b>\n"
f"📆 <b>{days[selected_day.weekday()]}, "
f"{selected_day.strftime('%d.%m.%Y')}</b>\n\n"
)
if not lessons:
return text + "🎉 На этот день занятий нет."
separator = "━━━━━━━━━━━━━━━━━━━━\n"
for lesson in lessons:
text += separator
number = lesson.get("номерЗанятия", "-")
text += (
f"🟧 <b>{number} занятие</b>\n"
f"🕐 <b>{lesson.get('начало', '?')}"
f"{lesson.get('конец', '?')}</b>\n\n"
)
text += (
f"📚 <b>{lesson.get('дисциплина', '-')}</b>\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(
"📚 <b>Выберите свою группу</b>",
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"✅ Группа <b>{group['groupName']}</b> сохранена."
)
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"✅ Группа <b>{group['groupName']}</b> сохранена."
)
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(
"🎓 <b>Группа сохранена!</b>\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"🎓 Ваша группа: <b>{saved_group['group_name']}</b>"
)
@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"✅ Подгруппа <b>{subgroup}</b> сохранена!"
)
await callback.message.answer(
"Выберите действие:",
reply_markup=main_keyboard()
)