803 lines
17 KiB
Python
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()
|
|
) |