- BrainTools - https://www.braintools.ru -
Решил вот написать не для продвижения ради, а для конструктивной обратной связи, чтобы продолжить пилить проект, т.к. сейчас выбираю, что же дальше и во что это может вырасти.
Сразу предупрежу: делал с ИИ, так что, если кого-то это триггерит, можно скипнуть статью.
Да, очередной бот, но тема мне близка и хотелось сделать что-то свое.
Что имеем 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 [1] — основная логика [2], хендлеры команд, FSM онбординга и настроек, планировщик APScheduler.
db.py [3] + models.sql — слой работы с SQLite и схема БД.
config.py [4] — чтение .env, токен бота засунул в итоге в переменные потом, словари ачивок и порогов.
discipline.db — сама база.
requirements.txt — зависимости.
images/ — в том числе приветственный экран / стартовая картинка.
Бот полностью на async/await: aiogram 3, FSM из коробки, асинхронный APScheduler.
Минимальный набор сущностей:
users:
id, tg_id
name
morning_time, checkin_time (строки HH:MM)
start_date
timezone (строка типа Europe/Moscow)
служебные поля: last_morning_sent, last_checkin_reminder_sent, minimal_mode.
focuses:
id
user_id
title (формулировка цели)
domain (работа/личное/здоровье)
is_active, started_at, ended_at
best_streak (лучший стрик по этой цели).
checkins:
id
user_id, focus_id
date (YYYY‑MM‑DD)
status (done, partial, fail).
achievements:
id
user_id, focus_id
level (номер уровня ачивки)
days (длина серии на момент получения)
created_at.
Такой набор позволяет:
быстро считать текущий и лучший стрик по активному фокусу;
строить простую недельную и долгую статистику;
выдавать ачивки за достижение порогов ACHIEVEMENT_THRESHOLDS
Онбординг сделан через FSM aiogram: каждое состояние отвечает за один шаг и хранит промежуточные данные.
Цепочка шагов:
/start → проверка, есть ли пользователь в БД.
Если новый:
имя;
утреннее время (morning_time);
вечернее время (checkin_time);
часовой пояс (выбор из фиксированного списка таймзон РФ);
домен (работа/личное/здоровье);
формулировка фокуса.
В FSM‑данных собираются все поля, в конце:
создаётся запись в focuses (активный фокус);
обновляются поля пользователя в users;
пользователь выводится на основной сценарий с кнопками чек‑ина.
Одна из самых нетривиальных для пет‑проекта частей — таймзоны и расписание.
Подход:
При онбординге пользователь выбирает таймзону вручную из списка преднастроенных (например, Europe/Moscow, Asia/Yekaterinburg и т.д.).
В БД в users.timezone сохраняется строка с именем таймзоны.
APScheduler крутится в UTC и раз в минуту выполняет две джобы:
send_morning_focus
send_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 (приватный).
Цикл: правки кода → локальный запуск бота → проверки онбординга и основных команд → git commit → git push.
На сервере (VDS Timeweb):
В каталоге проекта:
git pull origin main
pip install -r requirements.txt (при необходимости)
миграции схемы через models.sql (минимальные DDL).
Бот крутится как discipline-bot.service под systemd:
ExecStart=/root/discipline_bot/venv/bin/python /root/discipline_bot/bot.py [1]
перезапуск через systemctl restart discipline-bot.
Для удобства есть скрипт deploy.sh [6], который делает pull, обновляет зависимости и перезапускает сервис, что прям облегчило коммиты в последнее время, коих стало много.
До этого я использовал специализированный «бот‑хостинг», но столкнулся с кешированием, гонкой нескольких процессов и сложностью отладки, поэтому переехал на обычный VDS с голым терминалом — это оказалось проще и предсказуемее, и кажется более профессионально.
Сейчас бот живёт в проде, ежедневно шлёт уведомления. Из следующих шагов вижу:
миграцию с SQLite на PostgreSQL (для более сложной аналитики и параллельной нагрузки);
расширенную статистику по неделям и месяцам (retention, «процент идеальных недель», распределение по статусам);
доработку системы ачивок и уровней сложности;
экспорт данных и, возможно, лёгкую веб‑панель для просмотра прогресса с десктопа;
полноценный i18n и английскую локализацию, чтобы можно было смело нести бота на Reddit и англоязычные площадки.
Ссылка на бота: https://t.me/focuscompanion_bot. [7]
Вопрос к читателям Хабра: что бы вы обязательно добавили в такого рода «бот‑трекер дисциплины» с точки зрения [8] метрик и UX, или может по процессу? И как можно продвинуть.
Заранее всем большое спасибо! Буду рад любой обратной связи!
Автор: teaglaim
Источник [9]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/25245
URLs in this post:
[1] bot.py: http://bot.py
[2] логика: http://www.braintools.ru/article/7640
[3] db.py: http://db.py
[4] config.py: http://config.py
[5] Image: https://sourcecraft.dev/
[6] deploy.sh: http://deploy.sh
[7] https://t.me/focuscompanion_bot.: https://t.me/focuscompanion_bot.
[8] зрения: http://www.braintools.ru/article/6238
[9] Источник: https://habr.com/ru/articles/993178/?utm_source=habrahabr&utm_medium=rss&utm_campaign=993178
Нажмите здесь для печати.