- Python 96.7%
- Shell 3.2%
- Dockerfile 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| db | ||
| deploy | ||
| docs | ||
| handlers | ||
| keyboards | ||
| middlewares | ||
| scripts | ||
| services | ||
| tests | ||
| utils | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| bot.py | ||
| config.py | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| pytest.ini | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| SECURITY.md | ||
HomeBot
Телеграм-бот (aiogram 3) для группы друзей: каждый отмечает свои рабочие дни в интерактивном календаре, а бот показывает, кто сегодня дома и когда лучше всего собраться. Опционально — модуль слежки за Steam-профилями (кто что купил и сколько наиграл), красивыми сводками.
Хранение — SQLite (после перезапуска ничего не теряется), лёгкий, без внешних сервисов. Работает на скромном железе (проверено на LXC с 256 МБ RAM).
Модульность: оба блока независимы и включаются флагами в .env
(ENABLE_WORKCAL, ENABLE_STEAM). Нужен только график — выключи Steam; нужен
только Steam-трекер — выключи график. По умолчанию включены оба.
Возможности
График / кто дома:
- 📅
/my— личный интерактивный календарь. Клик по дате ставит отметку; кнопка «🔁 Отмечаю» переключает тип: 🔴 рабочий день или 🟩 отпуск. Черновик хранится в SQLite, поэтому не теряется при перезапуске; «✅ Готово» публикует изменения в общий график. - 🏠
/home— кто сегодня дома, кто работает. Выводится картинкой: аватарки (или цветной круг с инициалом, если фото скрыто), статусы «дома/отпуск/на работе». - 🗓
/collect [N]— сводка на N дней (по умолчанию 7). Тоже картинкой: сетка «люди × дни» с подсветкой лучшего дня (при >12 участниках или >14 днях — автоматически текстом). - 👤
/who @ник— чужой график (просмотр). - 📊
/stats— статистика за месяц: кто чаще всех дома. - 🧩
/shift— расставить смены по шаблону (2/2, 1/3, будни…) на 90 дней вперёд. - ✏️
/rename, 🛑/cancel, ❓/help.
Каждое утро (по умолчанию 09:00, AUTOPOST_HOUR) бот автопостит в группу «кто
сегодня дома» — той же картинкой, что и /home. В сводку попадают только те,
чей график явно подтверждён на ближайшие 7 дней; остальным напоминает обновить график
(пинги — в подписи под картинкой).
Для владельца бота (см. «Доступ» ниже — только владелец):
- 🩺
/status— глобальный статус: какие модули включены, все настройки и интервалы, мини-сводка последних Steam-проверок. - ⚙️
/settings— тумблеры: утренний автопост, доступ к/stats, сводки картинками (выключил —/home//collect/автопост снова текстом). - 🗂
/backup— бэкап базы в личку (+ авто раз в сутки,BACKUP_HOUR). - 📥
/backupsub//backupunsub//backupwho— управление получателями авто-бэкапа (без поиска числовых id). Пусто → бэкап уходит владельцу. - 🚫
/forget @ник— убрать ушедшего из графиков. - 🆔
/id— узнать chat id и свой user id (напр. для.env).
База в бэкапе содержит Telegram id, имена, графики и Steam-данные. Получатели — только явные (
BACKUP_ADMIN_IDS//backupsub) или владелец; «всем админам чата» бот её не рассылает.
Steam-модуль (опционально):
- 🆕 Впервые обнаруженные ботом игры — карточкой с обложкой сразу («купил игру» / «добавил игру» — различает платные и free-to-play, показывает цену и теги Онлайн/Кооп/Одиночная).
- 🎁 Вишлист — анонс, когда кто-то добавляет игру в желаемое («X добавил в вишлист …»); если её хочет ещё кто-то из списка — подсказывает, кто. На карточке покупки: метка «✅ из вишлиста», у кого она в вишлисте, у кого уже куплена, и для не-одиночных — зов «🤝 может, вместе поиграете?». Игры 18+ помечаются 🔞 (по данным стора).
- 📊 Наигранное — красивой картинкой-сводкой: каждый день в 21:00 и по воскресеньям за неделю (аватары, иконки игр, время по каждой игре).
- Шлёт в один заданный чат (личка или группа) — куда включишь командой
/steamhere.
Требования
- Python 3.10+ (цветные кнопки Bot API 9.4 требуют свежий aiogram).
- Токен бота от @BotFather.
- (для Steam-модуля) ключ Steam Web API и SteamID64 отслеживаемых профилей.
Быстрый старт
git clone <URL-репозитория> workcal-bot && cd workcal-bot
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # заполни значения (см. ниже)
python bot.py
Разработка и выкладка
- Проверки запускаются командой
python -m pytest -q. Для CI дополнительно используютсяcompileall,pip check,pip-audit, Ruff, Bandit и Mypy. - Рабочая production-ветка —
main. Изменения попадают туда только через pull request с успешным CI. Экспериментальная Telegram Mini App остаётся в отдельной веткеminiapp_experimentи не разворачивается вместе с ботом. - Production работает нативно через systemd, не через Docker. Docker-файлы оставлены только для локальной упаковки; актуальная схема выкладки и отката — в docs/deployment.md.
Настройка (.env)
Все секреты и параметры — в .env (он в .gitignore, в репозиторий не попадает).
Скопируй .env.example → .env и заполни:
| Переменная | Обяз. | Описание / где взять |
|---|---|---|
BOT_TOKEN |
✅ | Токен бота. @BotFather → /newbot. |
ENABLE_WORKCAL |
Модуль графика/«кто дома» (true/false, по умолчанию true). |
|
ENABLE_STEAM |
Модуль Steam-трекера (true/false, по умолчанию true). |
|
TIMEZONE |
Часовой пояс, напр. Europe/Moscow. Влияет на «сегодня» и время автопостов. |
|
DB_PATH |
Путь к SQLite-базе (создаётся сама). По умолчанию workcal.db. |
|
BACKUP_HOUR |
Час авто-бэкапа (0–23). | |
AUTOPOST_HOUR |
Час утреннего автопоста «кто дома» (0–23). | |
OWNER_IDS |
Владелец(ы) бота — Telegram id через запятую. Только им админ/Steam-команды. Пусто → TOFU (см. ниже). | |
BACKUP_ADMIN_IDS |
Кому слать авто-бэкап — Telegram id через запятую. Пусто → /backupsub, иначе владельцу. |
|
ALLOWED_CHAT_ID |
Рабочая группа (её chat id, обычно -100…). Задан → в других группах бот молчит. Пусто → закрепится, когда владелец добавит бота в группу. |
|
STEAM_API_KEY |
для Steam | Ключ Steam Web API: https://steamcommunity.com/dev/apikey |
STEAM_ACCOUNTS |
для Steam | SteamID64 через запятую (см. ниже). |
Если STEAM_API_KEY и STEAM_ACCOUNTS пустые — Steam-модуль просто выключен,
а бот-график работает как обычно.
Куда прописывать Steam-аккаунты и ключ
-
Ключ API → в
.env, переменнаяSTEAM_API_KEY. Получить: https://steamcommunity.com/dev/apikey (нужен аккаунт Steam; в поле «домен» можно указать любой). -
Аккаунты → в
.env, переменнаяSTEAM_ACCOUNTS— список через запятую. Каждый элемент — один из форматов (можно смешивать):- SteamID64 — 17-значное число:
76561198000000001; - логин (vanity) — короткое имя из ссылки
.../id/:gabelogannewell; - ссылка на профиль —
https://steamcommunity.com/id/gabelogannewellилиhttps://steamcommunity.com/profiles/76561198000000001.
Логины и ссылки бот сам разрезолвит в SteamID64 на старте. Опционально метка через двоеточие (ник всё равно берётся из Steam):
STEAM_ACCOUNTS=76561198000000001,gabelogannewell:Гейб⚠️ У отслеживаемых профилей «Игровые данные» должны быть публичными (Настройки приватности Steam), иначе API не отдаёт список игр, время и вишлист. У кого приватно — просто не попадёт в сводки/анонсы (тихо).
- SteamID64 — 17-значное число:
-
Включить доставку: напиши боту
/steamhereв том чате (личка или группа), куда должны приходить Steam-сводки.
Команды Steam-модуля
| Команда | Что делает |
|---|---|
/steamhere |
Слать Steam-сводки в этот чат (личка или группа). |
/steamoff |
Выключить доставку сводок. |
/steamsettings |
Тумблеры (новые игры / вишлист / ежедневная / недельная), период опроса и время сводок. |
/steamstatus |
Статус: какие аккаунты отслеживаются, последние проверки (успех/ошибка), приватность. |
/steamdiag |
Диагностика приватности, пропавших игр и последнего monotonic-снимка. |
/steamnow |
Проверить новые игры прямо сейчас. |
/steamreport |
Превью дневной сводки (накопленное с последнего сброса; счётчик не трогает). |
Расписание (настраивается в /steamsettings, по умолчанию):
- 🆕 анонс новых игр — проверка каждые 15 мин (период меняется: 5–120 мин);
- 🎁 анонс вишлиста — проверка каждые 30 мин (период меняется: 5–120 мин);
- 📊 ежедневная сводка — 21:00;
- 📅 недельная сводка — воскресенье 21:00.
Каждый из четырёх пунктов можно независимо выключить; периоды опроса и
время/день сводок меняются кнопками. Время — по TIMEZONE бота.
Дополнительный переключатель «В день недельной — только недельная» убирает
двойную отправку: если недельная отключена, ежедневная снова приходит обычно.
Меню команд строится по включённым модулям: выключишь
ENABLE_WORKCAL— команд графика в меню нет; выключишьENABLE_STEAM— нет steam-команд. Плюс steam-команды показываются только в личке с ботом, в групповом меню их нет (чтобы посторонние не жали/steamhere); при этом набрать их вручную в группе по-прежнему можно.
Как считается игровое время
Бот не полагается на видимый статус Online/Invisible. При каждом опросе он
сравнивает абсолютный счётчик Steam playtime_forever с максимальным ранее
подтверждённым значением и накапливает только положительную разницу отдельно для
дня и недели.
- уменьшившийся или временно устаревший счётчик не создаёт повторные минуты;
- исчезнувшая из API Private Game не удаляется из истории;
- вернувшаяся игра сравнивается с прежним максимумом, а не считается новой;
- Steam API не раскрывает дату покупки: анонс означает первое обнаружение игры ботом в библиотеке, а не подтверждённую дату получения лицензии;
- покупка с нулевым временем не попадает в отчёт «кто играл»;
- накопление очищается только после успешной доставки плановой сводки;
/steamreportпоказывает превью в том чате, где вызвана команда, и ничего не сбрасывает; плановые сводки по-прежнему идут в чат из/steamhere.- резкое сокращение библиотеки помечается как частичный ответ и не выдаётся за достоверное «не играл»; ограниченный журнал наблюдений помогает разбирать сбои;
- если хеш иконки из
GetOwnedGamesустарел, бот пробует изображения Steam Store, проверяет содержимое и использует ограниченный положительный/отрицательный кэш. - аватары профилей кэшируются в SQLite, перепроверяются раз в сутки и при временном сбое Steam берутся из последней валидной копии.
- перед анонсом новой игры Store-метаданные запрашиваются до трёх раз; без них неполная карточка не отправляется, а анонс безопасно повторяется следующим опросом. Через 30 минут ожидания отправляется базовая карточка с явным предупреждением, поэтому закрытое приложение не блокирует алерт навсегда. Успех кэшируется на 3 часа, неудача — только на 5 минут.
Статус Invisible сам по себе не скрывает playtime_forever. Если /steamdiag
показывает исчезнувшие игры, обычно причина — Steam Private Game, приватность
Game Details или временно неполный ответ API. Offline Mode может задержать
синхронизацию времени до следующего подключения Steam.
Доступ (одна группа, один владелец)
Бот рассчитан на одну доверенную группу и одного владельца. Настройка — только в личке; в группу добавляют уже готового бота, команд в ней вводить не нужно.
- Владелец — тот, кому доступны админ- и Steam-команды. Задаётся
OWNER_IDSв.env(Telegram id через запятую) или, если пусто, назначается по TOFU: первый, кто напишет боту/startв личку, становится владельцем (запоминается в базе). Свой id — команда/idв личке. - Рабочая группа — задаётся
ALLOWED_CHAT_IDв.envили закрепляется автоматически: когда владелец добавляет бота в группу, тот тихо запоминает её как рабочую (подтверждение приходит владельцу в личку, в саму группу бот ничего не пишет). После этого в любой другой группе бот молчит — «увести» его нельзя. - Обычные команды (
/my,/home,/collect, …) — в рабочей группе всем участникам; в личке — только тем, кто уже зарегистрирован из группы. Посторонним в личке бот не отвечает. Участники регистрируются молча при первой команде — отдельный/startне обязателен. - Админ/Steam-команды — только владельцу.
Типичный порядок: владелец пишет боту /start в личку (становится владельцем и
настраивает Steam/автопост), затем добавляет бота в группу — она закрепляется
сама. env всегда главнее: если OWNER_IDS/ALLOWED_CHAT_ID заданы, TOFU и
авто-закрепление не нужны.
Только Steam-модуль (ENABLE_WORKCAL=false): участникам делать ничего не
нужно — бот лишь публикует Steam-сводки в заданный чат. Владельца в этом режиме
удобнее задать через OWNER_IDS в .env.
Работа в группе
- У ботов в группах включён privacy mode — бот видит только команды (
/…) и ответы на свои сообщения. Боту этого хватает. - Каждый участник должен один раз написать боту (
/startили любую команду), чтобы попасть в список. - Для удаления чужих команд и авто-деактивации ушедших боту нужны права администратора группы (разрешение «Удаление сообщений»).
Авто-уборка чата
Чтобы не засорять чат, бот сам удаляет свои сообщения: быстрые ответы — через
5 минут, календарь после «Готово» — через ~30 сек, план /collect — до конца
суток. Очередь удаления хранится в БД и переживает перезапуск (лимит Telegram —
сообщения не старше 48 часов).
Деплой
Есть три варианта. Все скрипты берут адрес сервера из переменных окружения
REMOTE (напр. user@host) и REMOTE_DIR.
1. Нативно (systemd + venv) — легковесно, рекомендуется
REMOTE=user@your-server ./deploy/deploy-native.sh
Ставит python3-venv, rsync, шрифт для баннеров, разворачивает venv, создаёт
непривилегированного пользователя workcal, ставит systemd-сервис workcal-bot
(бежит от workcal, не от root; с базовым hardening — ProtectSystem=strict,
NoNewPrivileges, PrivateTmp и т.д.) и запускает. Юнит —
deploy/workcal-bot.service.
2. Деплой одной командой git push (после нативного)
REMOTE=user@your-server ./deploy/setup-git-deploy.sh # один раз на сервере
git remote add deploy user@your-server:/opt/workcal-bot.git
git push deploy main # выкладывает код, ставит зависимости, перезапускает сервис
.env и база на сервере при этом не трогаются (они untracked).
Пуш защищён гейтом: сначала тесты нового кода гоняются на сервере во
временном каталоге — при падении деплой отменяется и прод не трогается; после
рестарта хук проверяет здоровье сервиса и при краше автоматически откатывает
на предыдущий коммит. Плюс .gitlab-ci.yml запускает те же тесты на каждый пуш
в GitLab (нужен настроенный Runner).
3. Docker
docker compose up -d --build # локально, БД в ./data
REMOTE=user@your-server ./deploy.sh # на сервер по SSH
.envпереносится на сервер только если его там ещё нет — прод-секреты не затираются. Токен и ключи держи в.env, не коммить их.
Тесты
pip install -r requirements-dev.txt
pytest
Структура
bot.py — точка входа, polling, меню команд, фоновые задачи
config.py — настройки из .env (в т.ч. STEAM_ACCOUNTS)
db/ — схема, миграции, доступ к данным (SQLite)
db/state.py — типизированные ключи app_state (repo.state.get/set)
keyboards/ — интерактивный календарь и date-picker
handlers/ — start, calendar, reports, shifts, admin, steam, core, errors
services/scheduling.py — логика «кто дома», сбор, шаблоны смен
services/backup.py — снимок БД и рассылка бэкапа
services/digest.py — утренний автопост «кто дома»
services/home_*.py — баннеры «кто дома»/«сбор» (Pillow) и сборка их данных
services/avatars.py — аватарки Telegram с суточным кешем
services/steam*.py — Steam API, трекер, рендер сводок-картинок (Pillow)
assets/ — маска знака Steam для баннеров
utils/ — авто-удаление, планировщик, даты, доступ, форматирование
middlewares/ — регистрация пользователей, ограничение по группе
deploy/ — native/git/docker деплой, systemd-юнит, git-хук с гейтом
tests/ — pytest
Лицензия
MIT — см. LICENSE.