Skip to content
u2kdevPublic

About

🗓 Расписание TTPU в Ташкенте — PWA с офлайн-режимом (RU/EN) и Telegram-бот в одном процессе: группы, преподаватели, аудитории, «сейчас идёт и что дальше», утренний дайджест и напоминания. FastAPI + SQLite, фронтенд без сборки.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

🗓 TTPU Timetable

Расписание Турниского политехнического университета в Ташкенте

Веб-приложение (PWA) и Telegram-бот в одном процессе.
Открывается мгновенно, живёт без интернета, говорит на двух языках.

Python FastAPI aiogram SQLite PWA Build

✨ Возможности · 📸 Скриншоты · 🚀 Быстрый старт · ⚙️ Настройки · 🔌 API · 📴 Офлайн · 🔬 Данные · 🐳 Docker · 💬 Контакты


📸 Скриншоты

Главный экран

Расписание на неделю Расписание на день
Неделя — шесть дней, живые пары и цветовые рельсы по предметам День — таймлайн с временем и карточками занятий
Справочник групп Поиск
Справочник — группы, преподаватели, аудитории с числом пар Поиск — по всем разделам сразу, с клавиатуры одним нажатием

🌙 Тёмная тема

Главная в тёмной теме Неделя в тёмной теме

📱 Телефон

Главная Неделя День Справочник
Главная Неделя День Группы
Меню Тёмная тема Неделя в тёмной теме Офлайн
Меню Тёмная тема Неделя (тёмная) Без интернета

Последний кадр снят в режиме полёта: расписание отрисовано из кэша на устройстве.


✨ Возможности

🌐 Веб-приложение

🗂 Три среза Расписание по группам, преподавателям и аудиториям — на неделю или на день
⏱ «Сейчас идёт / Далее» Подсветка текущей и следующей пары, полоса прогресса, обратный отсчёт. Считается на клиенте — не требует перерисовки страницы
📴 Офлайн-режим Расписание скачивается на устройство и открывается без сети. Как это работает
🌍 Русский и английский Переключение мгновенное, без перезагрузки
🌗 Светлая и тёмная темы Плюс режим «Авто» по системной настройке
🔍 Поиск По группам, преподавателям и аудиториям; локально и с сервера
📲 Установка на телефон PWA: «Добавить на главный экран» — иконка, свой заголовок, полноэкранный режим
🎨 Дизайн без шаблонов Редакционная типографика, самохостные шрифты, никаких UI-библиотек

🤖 Telegram-бот

👥 Выбор группы С поиском и постраничным списком
📅 Быстрые срезы Сегодня, Завтра, Вся неделя, Ближайшая пара
👤 Преподаватели и аудитории Расписание любого преподавателя или аудитории
🌅 Утренний дайджест Пары на день в заданное время
⏰ Напоминание перед парой За N минут до начала
⚙️ Настройки в чате Уведомления включаются инлайн-кнопками, без веб-интерфейса

🏗 Как это устроено

flowchart LR
    A["🌐 EduPage<br/>ttpu.edupage.org"] -->|"JSON API<br/>POST /timetable/server"| B["⚙️ turin.sync<br/>парсер + планировщик"]
    B -->|"денормализованный<br/>SQLite"| C[("🗄️ data/timetable.db")]
    C --> D["🚀 FastAPI<br/>/api/*"]
    D --> E["📱 PWA<br/>offline-first · RU/EN"]
    D --> F["🤖 Telegram-бот<br/>дайджест · напоминания"]

    style A fill:#f0e6d2,stroke:#b4552b,color:#3a3226
    style C fill:#2f2a24,stroke:#b4552b,color:#f4f1ea
    style E fill:#e8efe6,stroke:#3f6b3f,color:#3a3226
    style F fill:#e3eef5,stroke:#2ca5e0,color:#3a3226
Loading

Один процесс делает всё: тянет расписание с EduPage, складывает в SQLite, отдаёт HTTP-API с веб-приложением и параллельно крутит Telegram-бота.

Ключевое решение — денормализованное хранилище: одна строка на (неделя, вид владельца ∈ class | teacher | room, владелец, день, пара). Благодаря этому один и тот же урок доступен и группе, и преподавателю, и аудитории — без JOIN-ов на чтении и без риска рассинхронизации.


🚀 Быстрый старт

Нужен Python 3.11+ (используется zoneinfo).

git clone https://github.com/u2kdev/turintime.git
cd turintime

python -m venv .venv
.venv\Scripts\activate          # Windows
# source .venv/bin/activate     # Linux / macOS

pip install -r requirements.txt
copy .env.example .env          # Windows   (cp .env.example .env на Linux/macOS)

Откройте .env и впишите токен бота — необязательно, без него веб-приложение работает полностью:

BOT_TOKEN=123456789:AA...       # токен от @BotFather
BOT_USERNAME=your_bot           # без @, нужен только для ссылок в интерфейсе

Запуск:

python main.py           # веб-сервер + бот
python main.py update    # только скачать расписание и выйти
🌐 Приложениеlocalhost:8000
📖 Документация APIlocalhost:8000/api/docs
💚 Проверка живостиlocalhost:8000/api/health

Первый запуск скачивает расписание с EduPage (несколько секунд) и складывает его в data/timetable.db. Дальше обновление идёт фоном каждые REFRESH_HOURS часов.

🤖 Настройка Telegram-бота

  1. Напишите @BotFather → /newbot → задайте имя и username.
  2. Скопируйте токен вида 123456789:AA... в BOT_TOKEN.
  3. Впишите username без @ в BOT_USERNAME — тогда в интерфейсе появится кнопка «Открыть бота».
  4. Перезапустите python main.py. В логе появится Telegram-бот → включён.

💡 Бот работает через long polling — домен, HTTPS и вебхук не нужны.

⚠️ Если запущено несколько экземпляров приложения, бот должен быть включён только на одном (RUN_BOT=false на остальных), иначе Telegram вернёт конфликт polling.

🛠 Вспомогательные скрипты (нужны только при работе над дизайном)
python scripts/fetch_fonts.py         # скачивает woff2 в web/assets/fonts
python scripts/make_icons.py          # рисует иконки PWA в web/icons
python tests/shoot_web.py             # снимает скриншоты экранов
python scripts/export_screenshots.py  # собирает галерею для этого README

Для скриншотов нужен Playwright:

pip install playwright
playwright install chromium

⚙️ Настройки

Все параметры читаются из .env. Полный список с комментариями — в .env.example.

Переменная По умолчанию Назначение
BOT_TOKEN — Токен бота от @BotFather. Пусто → бот выключен
BOT_USERNAME — Имя бота без @; из него собирается ссылка https://t.me/<username>
BASE_URL http://localhost:8000 Внешний адрес приложения (ссылки, PWA)
HOST / PORT 0.0.0.0 / 8000 Где слушает сервер
ADMIN_TOKEN — Токен для POST /api/refresh. Пусто → доступ только с localhost
EDUPAGE_ORIGIN https://ttpu.edupage.org Источник расписания
SCHOOL_NAME / SCHOOL_SHORT TTPU Название в интерфейсе
TIMEZONE Asia/Tashkent Часовой пояс для «сейчас» и рассылок
REFRESH_HOURS 6 Как часто перекачивать расписание
REFRESH_ON_START true Обновлять при старте сервера
RUN_BOT true Выключить бота, оставив веб-приложение
DAILY_DEFAULT_TIME 07:30 Время утреннего дайджеста по умолчанию
REMINDER_MINUTES 15 За сколько минут предупреждать о паре

🔌 API

Базовый префикс — /api, формат — JSON. {kind} — одно из class, teacher, room. {id} — идентификатор EduPage, например *98 (IT1-26); URL-кодируется.

Метод Путь Описание
GET /api/health Жив ли сервис и готова ли база
GET /api/meta Сводка: школа, время сервера, статистика, ссылка на бота
GET /api/weeks Список учебных недель
GET /api/directory Группы, преподаватели, аудитории, предметы, периоды, «популярные»
GET /api/search?q= Поиск по справочнику
GET /api/schedule/{kind}/{id} Расписание на неделю. ?week=<номер> или ?day=ГГГГ-ММ-ДД
GET /api/now/{kind}/{id} Текущая и ближайшая пара
GET /api/offline/{kind}/{id} Компактный пакет всех недель для офлайн-кэша
POST /api/refresh Ручное обновление (нужен заголовок X-Admin-Token)
GET /api/refresh/status Состояние последнего обновления
Пример ответа /api/now/class/*98
{
  "now": "2026-10-19T12:26:43+05:00",
  "today": { "idx": 0, "date": "2026-10-19", "ru": "Понедельник", "count": 1 },
  "current": {
    "key": "*557",
    "period": 3,
    "start": "12:00",
    "end": "13:20",
    "subject": "English for Academic Purposes",
    "short": "EAP",
    "color": "#99FF33",
    "teachers": [{ "name": "FAYZIYEVA GULASAL", "short": "G.FAYZIYEVA" }],
    "rooms": [{ "name": "C405", "short": "C405" }]
  },
  "next": { "short": "DELS (lec)", "start": "12:00", "end": "13:20" },
  "current_left_minutes": 53,
  "progress": 0.334
}

📴 Как работает офлайн

Задача: открыв приложение один раз с интернетом, дальше видеть расписание даже без сети.

СлойЧто кэшируетсяГде лежит
1️⃣ Оболочка HTML, CSS, JS, шрифты, иконки Cache Storage (service worker)
2️⃣ Справочник Группы, преподаватели, аудитории, метаданные localStorage
3️⃣ Расписание Все недели выбранной сущности localStorage — ключ ttpu.bundle.v1.class.<id>
4️⃣ Страховка Ответы /api/offline/* Cache Storage, стратегия network-first
  1. Оболочка. Service worker (web/sw.js) кладёт статику в Cache Storage — повторный запуск отдаётся из кэша мгновенно.
  2. Данные. Справочник и метаданные сохраняются в localStorage. Расписание группы — целиком, во все доступные недели — кладётся туда после первого открытия или по кнопке «Сохранить офлайн».
  3. Без сети. Интерфейс читает localStorage: рисует сохранённое расписание и показывает плашку «Офлайн · данные от …». Блоки «Сейчас идёт» и «Далее» считаются локально, поэтому тоже работают.
  4. Страховка. Запросы /api/offline/* идут по стратегии network-first с откатом в Cache Storage — на случай, если localStorage очистили, а сеть пропала.

Проверка вручную: откройте приложение, выберите группу, включите режим полёта и перезагрузите страницу — расписание останется на месте.


🔬 Откуда берутся данные

Парсер работает напрямую с публичным JSON-API EduPage — без Playwright и браузера, в отличие от классических скрейперов.

Все запросы — POST на {EDUPAGE_ORIGIN}/timetable/server/<endpoint> с заголовком Content-Type: application/json; charset=UTF-8 и телом:

{ "__args": [null, 2026], "__gsh": "00000000" }
Endpoint __args Что возвращает
ttviewer.js?__func=getTTViewerData [null, <год начала учебного года>] Список учебных недель (tt_num, даты, default_num)
regulartt.js?__func=regularttGetData [null, "<tt_num>"] Таблицы одной недели: уроки, группы, преподаватели, аудитории, предметы, периоды
⚠️ Подводные камни, на которые легко наступить
  • Форма application/x-www-form-urlencoded возвращает {"reload": true} — нужен именно JSON.
  • datefrom учебной недели — воскресенье; dateto = datefrom + 6. Учебные дни — понедельник…суббота, поэтому «понедельник» недели = datefrom + 1 день.
  • Дни кодируются битовой маской; в одной ячейке может быть несколько подгрупп — например, суббота разбита на Group 1 / Group 2 с разными аудиториями.
  • Список недель меняется: прошедшие недели EduPage убирает. Приложение всегда показывает то, что отдаёт источник.

🐳 Docker

docker compose up --build -d

Приложение — http://localhost:8000, данные (SQLite) — в томе ttpu-data.

Через docker run
docker build -t ttpu-timetable .
docker run -d --name ttpu -p 8000:8000 \
  -e BOT_TOKEN=123456789:AA... \
  -e BOT_USERNAME=your_bot \
  -v ttpu-data:/app/data \
  ttpu-timetable

🧪 Проверки

Скрипты в tests/ не требуют pytest и запускаются как есть.

python tests/check_parser.py     # разбор одной недели напрямую из EduPage
python tests/test_api.py         # все /api/* через TestClient
python tests/test_bot.py         # тексты, клавиатуры, «ближайшая пара», хранилище
python tests/shoot_web.py        # скриншоты экранов (нужен playwright)

🧰 Технологии

Слой Выбор Почему
HTTP FastAPI + uvicorn Асинхронность, автодокументация из коробки
Источник httpx Тот же async-стек, без блокирующих вызовов
Хранилище SQLite (stdlib) Ноль зависимостей и настройки, файл рядом с приложением
Бот aiogram 3 Long polling, инлайн-клавиатуры, FSM
Фронтенд Vanilla JS, без сборки Открывается мгновенно, нечего собирать и обновлять
Стили CSS custom properties Токены дизайн-системы, темы одним переключателем
Шрифты Literata, Manrope, JetBrains Mono Самохостные woff2, 194 КБ на девять файлов
Офлайн Service Worker + Cache Storage + localStorage Нативный механизм браузера, без библиотек
📁 Структура проекта
main.py                     точка входа (uvicorn + бот)
turin/
  config.py                 настройки из .env
  edupage.py                клиент и парсер EduPage
  db.py                     схема SQLite, запросы, справочник
  sync.py                   скачивание, запись в базу, планировщик
  schedule.py               «сейчас / далее», сетка дня, офлайн-пакет
  api.py                    FastAPI: /api/*, отдача SPA
  bot/
    runner.py               запуск polling, регистрация команд
    handlers.py             команды и callback-и
    keyboards.py            инлайн-клавиатуры
    texts.py                шаблоны сообщений
    notifier.py             утренний дайджест и напоминания
    storage.py              пользователи и подписки (SQLite)
web/
  index.html                разметка всех экранов
  assets/app.css            дизайн-система
  assets/app.js             маршрутизация, офлайн-кэш, i18n, «сейчас/далее»
  assets/fonts/             самохостные woff2
  sw.js                     service worker
  manifest.webmanifest      PWA-манифест
scripts/                    генераторы шрифтов, иконок и галереи
tests/                      проверки и скриншоты
docs/screenshots/           галерея для этого README
data/                       база расписания и пользователей бота (git-ignored)

💬 Контакты

Вопросы, идеи и баг-репорты — в Telegram:

Telegram


Расписание принадлежит Турнискому политехническому университету в Ташкенте. Приложение только отображает открытые данные EduPage.

Сделано с ♥ для студентов TTPU

About

🗓 Расписание TTPU в Ташкенте — PWA с офлайн-режимом (RU/EN) и Telegram-бот в одном процессе: группы, преподаватели, аудитории, «сейчас идёт и что дальше», утренний дайджест и напоминания. FastAPI + SQLite, фронтенд без сборки.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages