initial
This commit is contained in:
@@ -0,0 +1,803 @@
|
||||
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()
|
||||
)
|
||||
Reference in New Issue
Block a user