Skip to content

Repository files navigation

workshop-bot

Telegram-бот, через который мастера бронируют общую мастерскую под свои мастер-классы. Двойных броней не бывает, между МК есть буфер.

aiogram 3, SQLite, APScheduler. Брони лежат в SQLite, в Google Calendar уходит зеркало. Крутится в docker compose на VPS.

Команды

Команда Кто Что делает
/book мастер, личка бронь: дата, длительность, время, название
/my мастер свои брони: отмена, передача другому мастеру
/week мастер, личка или чат занятость на 7 дней: брони и события календаря (📌), имя мастера ведёт в его профиль
/cancel мастер прервать /book
/start все, личка мастеру справка, постороннему его ID
/help все, личка справка
/id все свой ID или ID чата
/addmaster <id> [имя] админ добавить мастера; в чате можно ответом на его сообщение
/delmaster <id> админ убрать мастера, оставшиеся брони покажет с кнопками отмены
/masters админ мастера: имя, @тег, ID
/all админ, личка то же на весь срок бронирования; отмена и передача любой брони, событию из календаря можно назначить мастера
/manual админ инструкция для мастеров, чтобы переслать или закрепить в чате

Админы из ADMIN_IDS считаются мастерами. Брони и отмены анонсируются в чат мастеров, напоминания идут мастеру в личку и в чат (в чат с 23:00 до 08:00 без звука).

Бронировать можно круглосуточно, в том числе через полночь. Дата брони считается по началу. Длительность: кнопкой или текстом (9, 2,5, 2:30), от 15 минут до 24 часов. Над кнопками времени бот показывает, чем занят день: чужие брони и события календаря, с учётом перерыва.

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

Локально, сборка из исходников:

cp .env.example .env                  # вписать BOT_TOKEN
mkdir -p data secrets
sudo chown -R 10001 data              # контейнер работает от uid 10001
echo '{}' > secrets/google-sa.json    # заглушка, пока GCAL_CALENDAR_ID пуст
docker compose up -d --build
docker compose logs -f

Дальше в Telegram:

  1. Написать боту /start, полученный ID вписать в ADMIN_IDS, выполнить docker compose up -d.
  2. Добавить бота в чат мастеров, там /id, значение вписать в MASTERS_CHAT_ID, снова docker compose up -d. Privacy mode выключать не нужно.
  3. Мастеров добавить через /addmaster (проще всего ответом на сообщение мастера в чате).

Историю, публичную ссылку и темы в чате лучше включить до /id: при превращении в супергруппу ID меняется на -100.... Если это случится позже, бот перейдёт на новый ID сам и напишет админам, что вписать в .env.

Чтобы бота не добавляли в чужие группы: @BotFather, /setjoingroups, Disable.

Конфигурация

Всё через .env, шаблон в .env.example.

Переменная По умолчанию Назначение
BOT_TOKEN нет, обязательна токен от @BotFather
ADMIN_IDS пусто Telegram ID админов через запятую
MASTERS_CHAT_ID пусто чат для анонсов и напоминаний; если пусто, в чат ничего не постится
GCAL_CALENDAR_ID пусто ID календаря Google; если пусто, календарь выключен
TZ Asia/Novosibirsk часовой пояс
BUFFER_MINUTES 30 пауза между МК, 0-720
SLOT_STEP_MINUTES 30 шаг сетки начала: делитель суток от 15 до 240
DURATIONS_MINUTES 60,90,120,180,240,300,360,480,540,600,720 кнопки длительности, 15-1440 минут
DAYS_AHEAD 21 на сколько дней вперёд показывать даты, 1-90
REMIND_BEFORE_MINUTES 1440,60 за сколько минут напоминать; пустое значение отключает
LOG_LEVEL INFO уровень логов
BOT_IMAGE workshop-bot:latest образ для compose; на сервере указывает на GHCR
DB_PATH /data/bot.db задаётся в compose.yml; вне контейнера data/bot.db
GOOGLE_SA_FILE /run/secrets/google_sa задаётся в compose.yml, ключ берётся из secrets/google-sa.json

BOT_IMAGE читает сам compose (подстановка в image:), остальное уходит в контейнер через env_file.

Google Calendar

  1. В console.cloud.google.com создать проект, включить Google Calendar API.
  2. IAM & Admin, Service Accounts: создать аккаунт без ролей, выпустить JSON-ключ, сохранить как secrets/google-sa.json.
  3. sudo chown 10001 secrets/google-sa.json && sudo chmod 400 secrets/google-sa.json
  4. В настройках календаря дать email сервис-аккаунта право "Внесение изменений в мероприятия".
  5. "Идентификатор календаря" вписать в GCAL_CALENDAR_ID, выполнить docker compose up -d.

В логе должно появиться Google Calendar: включён (...) и Google Calendar: чтение календаря проверено.

Как это работает:

  • Ручные события календаря занимают время (с тем же буфером) и видны в /week с пометкой 📌. События со статусом "свободен" не учитываются.
  • Если календарь не отвечает или не читается, бронировать бот не даёт. При ошибке настройки пишет админам в личку.
  • Если событие не создалось сразу, его досоздаст фоновая задача (раз в 5 минут). ID события вычисляется из брони, так что дублей не будет.
  • Если событие брони удалить в календаре руками, бронь в течение 5 минут отменится, в чат и мастеру в личку уйдёт сообщение.
  • Событию, созданному руками, админ может назначить мастера (/all, кнопка под списком). Оно становится обычной бронью с напоминаниями и отменой через /my, само событие остаётся и переименовывается.

Отменой считается только явная пометка Google "удалено". Когда события нет совсем (например, сменили календарь в .env), бот создаёт его заново. Если за один проход пропало больше 5 событий, бот ничего не отменяет и пишет админам: руками столько не удаляют. Перенос и переименование события бот игнорирует, время меняется отменой и новой бронью.

CI/CD и деплой

.github/workflows/ci.yml, запускается на push в main, теги v*, pull request и вручную.

  • test: ruff check bot tests и pytest -q на Python 3.12.
  • image: после test собирает образ (linux/amd64) и пушит в ghcr.io/<owner>/<repo>. На pull request только сборка, без пуша. Авторизация через встроенный GITHUB_TOKEN, секреты заводить не нужно.

Теги образа: latest с default-ветки, sha-<short> всегда, с git-тега v1.2.3 ещё 1.2.3 и 1.2 (latest при этом не двигается). Имя образа в GHCR всегда в нижнем регистре.

Dependabot раз в неделю обновляет actions и патчи базового образа. Python-зависимости обновляются руками через make lock, потому что lock собирается в контейнере.

На сервере нужны compose.yml, .env, data/, secrets/ и, по желанию, Makefile ради make pull и make backup. build: из compose там не используется, образ берётся из BOT_IMAGE. В .env:

BOT_IMAGE=ghcr.io/<owner>/<repo>:latest

Обновление (то же самое делает make pull):

docker compose pull && docker compose up -d

Пакет в GHCR после первого пуша приватный. Либо сделать его публичным (Package settings, Change visibility), либо один раз залогиниться на сервере с PAT со scope read:packages:

docker login ghcr.io -u <user>

Релиз:

git tag v1.2.3 && git push origin v1.2.3

Для отката или фиксации версии вписать в BOT_IMAGE тег 1.2.3 или sha-<short> вместо latest.

Эксплуатация

docker compose logs -f --tail=100

После правки .env нужен docker compose up -d, а не restart: он не перечитывает .env. Что настройки подхватились, видно в логе по строке Запущен @...; админы: [...]; чат мастеров: ....

data/bot.db работает в WAL, поэтому cp при живом боте даст битую копию. make backup снимает копию через sqlite backup API (контейнер должен быть запущен) и кладёт в backups/bot-YYYY-MM-DD.db, каталог задаётся через BACKUP_DIR. Удобно держать в cron и забирать копии с сервера:

0 4 * * * make -C /opt/workshop-bot backup BACKUP_DIR=/backup

Восстановление только при остановленном боте, одной цепочкой:

docker compose stop bot \
  && sudo rm -f data/bot.db-wal data/bot.db-shm \
  && sudo cp backups/bot-2026-10-01.db data/bot.db \
  && sudo chown 10001 data/bot.db \
  && docker compose start bot
Симптом Причина Что делать
Не стартует, Ошибка настройки: ... неверное значение в .env в сообщении названа переменная; поправить, docker compose up -d
база ... недоступна на запись, readonly database файлы в data не принадлежат uid 10001 (базу копировали) sudo chown -R 10001 data
... не читается как JSON-ключ сервис-аккаунта GCAL_CALENDAR_ID задан, а в secrets/google-sa.json заглушка или нет прав положить ключ, chown 10001; либо очистить GCAL_CALENDAR_ID
Админам: "Чат мастеров ... недоступен: chat not found" неверный MASTERS_CHAT_ID, у супергруппы он с -100 взять ID через /id в самом чате
Админам: "Не могу писать в чат мастеров ... bot was kicked" бота удалили из чата вернуть бота в чат
Админам: "Google Calendar не читается (HTTP 403/404 ...)" неверный ID календаря или календарь не расшарен проверить GCAL_CALENDAR_ID и доступ сервис-аккаунта
Брони есть, в календаре пусто календарь расшарен только на просмотр дать право "Внесение изменений в мероприятия"; брони досинхронизируются сами

Разработка

pip install -r requirements-dev.txt
make test     # pytest: слоты, БД, календарь на моках, сценарии с фейковым Bot API
make lint     # ruff check bot tests
make lock     # перегенерировать requirements.lock в python:3.12-slim-bookworm

Образ собирается из requirements.lock (точные версии), в requirements.txt только верхний уровень. После make lock прогнать тесты.

Логика брони и отмены живёт в bot/services.py, хендлеры тонкие. Фоновые задачи (синхронизация календаря раз в 5 минут, напоминания раз в минуту) в bot/tasks.py.

Миграций схемы нет. Перед добавлением колонки прочитать комментарий над SCHEMA в bot/db.py.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages