Привет, Хабр!
Решил вот написать не продвижения ради, а для конструктивной обратной связи, чтобы продолжить пилить проект, т.к. сейчас выбираю, что же дальше и во что это может вырасти.
Сразу предупрежу: делал с ИИ, так что, если кого-то это триггерит, можно скипнуть статью.
Да, очередной бот, но тема мне близка и хотелось сделать что-то свое.
Что имеем as is - pет‑проект о том, как я с нуля собрал и выкатил в прод Telegram‑бота, который напоминает о фокусе дня, считает выполнения, дает ачивки, мягко мотивирует, работает по таймзонам и крутится на VDS под systemd.
Задача: один фокус в день без лишних сервисов
Я хотел решить личную проблему с дисциплиной: не сорваться на рутине и не забрасывать мелкие, но важные задачи вроде зарядки, пет‑проекта или учебы. Без новой аппки, регистраций и таск‑менеджеров — всё внутри Telegram.
Из этого родилось простое требование к продукту:
один активный фокус на пользователя;
два касания в день: утром напомнить, вечером спросить, как прошёл день;
лёгкая игровая оболочка: стрики, ачивки, уровень цели;
минимум трения: старт по команде, дальше всё через бота.
Результат — бот @focuscompanion_bot, которым я сейчас сам пользуюсь. а также допиливаю по обратной связи.
Функционал с точки зрения пользователя
Сценарий работы:
При старте бот проводит онбординг: имя, время утреннего и вечернего уведомления, часовой пояс, домен (работа/личное/здоровье) и формулировка фокуса.
Каждое утро приходит сообщение с напоминанием о фокусе.
Вечером бот просит отметить результат по цели:
✅ сделано,🌓 частично,❌ не сделано.По
/weekпоказывает срез по цели: для коротких целей — «недельный» вид с 7‑дневной полосой, для длинных — агрегированную статистику без привязки к 7 дням.По
/streakпоказывает текущую и лучшую серию./achievements— список полученных ачивок по всем целям./time,/focus,/settings,/feedback— управление расписанием, целью, минимальным режимом (подсказали идею как раз) и обратной связью. чтобы пользователь мог сразу рассказать, что поправить.

Проект сознательно держу максимально простым, но с нормальной структурой.
Стек:
Python 3.12
aiogram 3 (асинхронный Telegram‑фреймворк)
SQLite в качестве хранилища (пока)
APScheduler для периодических задач
systemd‑service для запуска на VDS Timeweb
Файлы проекта:
bot.py— основная логика, хендлеры команд, FSM онбординга и настроек, планировщик APScheduler.db.py+models.sql— слой работы с SQLite и схема БД.config.py— чтение.env, токен бота засунул в итоге в переменные потом, словари ачивок и порогов.discipline.db— сама база.requirements.txt— зависимости.images/— в том числе приветственный экран / стартовая картинка.
Бот полностью на async/await: aiogram 3, FSM из коробки, асинхронный APScheduler.
Модель данных
Минимальный набор сущностей:
users:id,tg_idnamemorning_time,checkin_time(строкиHH:MM)start_datetimezone(строка типаEurope/Moscow)служебные поля:
last_morning_sent,last_checkin_reminder_sent,minimal_mode.
focuses:iduser_idtitle(формулировка цели)domain(работа/личное/здоровье)is_active,started_at,ended_atbest_streak(лучший стрик по этой цели).
checkins:iduser_id,focus_iddate(YYYY‑MM‑DD)status(done,partial,fail).
achievements:iduser_id,focus_idlevel(номер уровня ачивки)days(длина серии на момент получения)created_at.
Такой набор позволяет:
быстро считать текущий и лучший стрик по активному фокусу;
строить простую недельную и долгую статистику;
выдавать ачивки за достижение порогов
ACHIEVEMENT_THRESHOLDS
Онбординг и FSM
Онбординг сделан через FSM aiogram: каждое состояние отвечает за один шаг и хранит промежуточные данные.
Цепочка шагов:
/start→ проверка, есть ли пользователь в БД.Если новый:
имя;
утреннее время (
morning_time);вечернее время (
checkin_time);часовой пояс (выбор из фиксированного списка таймзон РФ);
домен (работа/личное/здоровье);
формулировка фокуса.
В FSM‑данных собираются все поля, в конце:
создаётся запись в
focuses(активный фокус);об��овляются поля пользователя в
users;пользователь выводится на основной сценарий с кнопками чек‑ина.
Таймзоны и расписание
Одна из самых нетривиальных для пет‑проекта частей — таймзоны и расписание.
Подход:
При онбординге пользователь выбирает таймзону вручную из списка преднастроенных (например,
Europe/Moscow,Asia/Yekaterinburgи т.д.).В БД в
users.timezoneсохраняется строка с именем таймзоны.APScheduler крутится в UTC и раз в минуту выполняет две джобы:
send_morning_focussend_daily_checkins.
Схема работы джобы:
Берём всех пользователей, у кого
last_morning_sent(илиlast_checkin_reminder_sent) не равен сегодняшней дате.Для каждого:
конвертируем
now_utcв локальное время черезpytz.timezone(user.timezone);сравниваем локальное время
HH:MMс сохранённымmorning_time/checkin_time.
Если «сейчас» совпадает с временем пользователя — шлём сообщение, отмечаем
last_*_sent = today.
from datetime import datetime from pytz import timezone as pytz_timezone async def send_daily_checkins(): now_utc = datetime.now(pytz_timezone("UTC")) today_str_utc = now_utc.strftime("%Y-%m-%d") # Берём всех, кому ещё не слали вечернее сообщение сегодня users = await get_users_for_evening(today_str_utc) if not users: return ids_to_mark: list[int] = [] for user in users: tg_id = user["tg_id"] user_id = user["id"] tz_name = user["timezone"] or "Europe/Moscow" user_tz = pytz_timezone(tz_name) now_user = now_utc.astimezone(user_tz) today_str = now_user.strftime("%Y-%m-%d") checkin_time_local = (user["checkin_time"] or "").replace(":", "") if not checkin_time_local: continue current_time_local = now_user.strftime("%H%M") if current_time_local != checkin_time_local: continue status = await get_today_checkin_status(user_id, today_str) if status: # уже есть чек-ин — просто шлём краткий summary text = get_summary_text(status, user.get("name")) await bot.send_message(tg_id, text) else: # нет чек-ина — шлём кнопки done/partial/fail text = "Как прошёл день по твоему фокусу?" await bot.send_message(tg_id, text, reply_markup=checkin_kb) ids_to_mark.append(user_id) if ids_to_mark: await mark_evening_sent(ids_to_mark, today_str_utc)
Такое решение:
не плодит отдельные cron‑задачи под каждого;
работает одинаково для разных регионов РФ;
остаётся простым для отладки.
Статистика и ачивки
Чек‑ин за день — это запись в checkins с status done/partial/fail.
Дальше поверх этого:
Стрик: считаю количество подряд идущих дней с
doneилиpartial, начиная от последней даты с чек‑ином назад. Частичный день не ломает серию.Недельная статистика:
для коротких целей (≤7 дней) показываю:
7‑дневную ленту статусов (эмодзи);
прогресс‑бар из 10 блоков, который использует формулу
done + partial * 0.5;текстовое резюме недели.
для длинных целей (>7 дней) — только агрегаты по всем дням:
сколько всего дней по цели;
сколько из них
done/partial/fail;без привязки к «неделе» и «7 дням».
Система ачивок:
есть массив порогов
ACHIEVEMENT_THRESHOLDS = [3, 7, 14, 30, ...];при каждом обновлении стрика вычисляется уровень
get_achievement_level(streak);если достигнут новый уровень и он выше сохранённого для данного
focus_id— добавляется запись вachievements.
Деплой: GitHub → Timeweb VDS → systemd
Разработка и деплой у меня организованы так.
Локально:
Работа в репозитории GitHub (приватный).
Цикл: правки кода → локальный запуск бота → проверки онбординга и основных команд →
git commit→git push.
На сервере (VDS Timeweb):
В каталоге проекта:
git pull origin mainpip install -r requirements.txt(при необходимости)миграции схемы через
models.sql(минимальные DDL).
Бот крутится как
discipline-bot.serviceпод systemd:ExecStart=/root/discipline_bot/venv/bin/python /root/discipline_bot/bot.pyперезапуск через
systemctl restart discipline-bot.
Для удобства есть скрипт
deploy.sh, который делает pull, обновляет зависимости и перезапускает сервис, что прям облегчило коммиты в последнее время, коих стало много.
До этого я использовал специализированный «бот‑хостинг», но столкнулся с кешированием, гонкой нескольких процессов и сложностью отладки, поэтому переехал на обычный VDS с голым терминалом — это оказалось проще и предсказуемее, и кажется более профессионально.
Что дальше
Сейчас бот живёт в проде, ежедневно шлёт уведомления. Из следующих шагов вижу:
миграцию с SQLite на PostgreSQL (для более сложной аналитики и параллельной нагрузки);
расширенную статистику по неделям и месяцам (retention, «процент идеальных недель», распределение по статусам);
доработку системы ачивок и уровней сложности;
экспорт данных и, возможно, лёгкую веб‑панель для просмотра прогресса с десктопа;
полноценный i18n и английскую локализацию, чтобы можно было смело нести бота на Reddit и англоязычные площадки.
Ссылка на бота: https://t.me/focuscompanion_bot.
Вопрос к читателям Хабра: что бы вы обязательно добавили в такого рода «бот‑трекер дисциплины» с точки зрения метрик и UX, или может по процессу? И как можно продвинуть.
Заранее всем большое спасибо! Буду рад любой обратной связи!
