- BrainTools - https://www.braintools.ru -

Telegram‑бот для (само)дисциплины на Python: aiogram 3, APScheduler и деплой на VDS

Решил вот написать не для продвижения ради, а для конструктивной обратной связи, чтобы продолжить пилить проект, т.к. сейчас выбираю, что же дальше и во что это может вырасти.
Сразу предупрежу: делал с ИИ, так что, если кого-то это триггерит, можно скипнуть статью.
Да, очередной бот, но тема мне близка и хотелось сделать что-то свое.

Что имеем as is – pет‑проект о том, как я с нуля собрал и выкатил в прод Telegram‑бота, который напоминает о фокусе дня, считает выполнения, дает ачивки, мягко мотивирует, работает по таймзонам и крутится на VDS под systemd.

Задача: один фокус в день без лишних сервисов

Я хотел решить личную проблему с дисциплиной: не сорваться на рутине и не забрасывать мелкие, но важные задачи вроде зарядки, пет‑проекта или учебы. Без новой аппки, регистраций и таск‑менеджеров — всё внутри Telegram.​

Из этого родилось простое требование к продукту:

  • один активный фокус на пользователя;

  • два касания в день: утром напомнить, вечером спросить, как прошёл день;

  • лёгкая игровая оболочка: стрики, ачивки, уровень цели;

  • минимум трения: старт по команде, дальше всё через бота.

Результат — бот @focuscompanion_bot, которым я сейчас сам пользуюсь. а также допиливаю по обратной связи.

Функционал с точки зрения пользователя

Сценарий работы:

  • При старте бот проводит онбординг: имя, время утреннего и вечернего уведомления, часовой пояс, домен (работа/личное/здоровье) и формулировка фокуса.

  • Каждое утро приходит сообщение с напоминанием о фокусе.

  • Вечером бот просит отметить результат по цели:
    ✅ сделано🌓 частично❌ не сделано.

  • По /week показывает срез по цели: для коротких целей — «недельный» вид с 7‑дневной полосой, для длинных — агрегированную статистику без привязки к 7 дням.

  • По /streak показывает текущую и лучшую серию.

  • /achievements — список полученных ачивок по всем целям.

  • /time/focus/settings/feedback — управление расписанием, целью, минимальным режимом (подсказали идею как раз) и обратной связью. чтобы пользователь мог сразу рассказать, что поправить.​

Telegram‑бот для (само)дисциплины на Python: aiogram 3, APScheduler и деплой на VDS - 1

Проект сознательно держу максимально простым, но с нормальной структурой.

Стек:

  • 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:

    • idtg_id

    • name

    • morning_timecheckin_time (строки HH:MM)

    • start_date

    • timezone (строка типа Europe/Moscow)

    • служебные поля: last_morning_sentlast_checkin_reminder_sentminimal_mode.

  • focuses:

    • id

    • user_id

    • title (формулировка цели)

    • domain (работа/личное/здоровье)

    • is_activestarted_atended_at

    • best_streak (лучший стрик по этой цели).​

  • checkins:

    • id

    • user_idfocus_id

    • date (YYYY‑MM‑DD)

    • status (donepartialfail).​

  • achievements:

    • id

    • user_idfocus_id

    • level (номер уровня ачивки)

    • days (длина серии на момент получения)

    • created_at.​

Такой набор позволяет:

  • быстро считать текущий и лучший стрик по активному фокусу;

  • строить простую недельную и долгую статистику;

  • выдавать ачивки за достижение порогов ACHIEVEMENT_THRESHOLDS

Онбординг и FSM

Онбординг сделан через FSM aiogram: каждое состояние отвечает за один шаг и хранит промежуточные данные.​​

Цепочка шагов:

  1. /start → проверка, есть ли пользователь в БД.

  2. Если новый:

    • имя;

    • утреннее время (morning_time);

    • вечернее время (checkin_time);

    • часовой пояс (выбор из фиксированного списка таймзон РФ);

    • домен (работа/личное/здоровье);

    • формулировка фокуса.

В FSM‑данных собираются все поля, в конце:

  • создаётся запись в focuses (активный фокус);

  • обновляются поля пользователя в users;

  • пользователь выводится на основной сценарий с кнопками чек‑ина.

Таймзоны и расписание

Одна из самых нетривиальных для пет‑проекта частей — таймзоны и расписание.

Подход:

  • При онбординге пользователь выбирает таймзону вручную из списка преднастроенных (например, Europe/MoscowAsia/Yekaterinburg и т.д.).

  • В БД в users.timezone сохраняется строка с именем таймзоны.

  • APScheduler крутится в UTC и раз в минуту выполняет две джобы:

    • send_morning_focus

    • send_daily_checkins.​​

Схема работы джобы:

  1. Берём всех пользователей, у кого last_morning_sent (или last_checkin_reminder_sent) не равен сегодняшней дате.

  2. Для каждого:

    • конвертируем now_utc в локальное время через pytz.timezone(user.timezone);

    • сравниваем локальное время HH:MM с сохранённым morning_time / checkin_time.

  3. Если «сейчас» совпадает с временем пользователя — шлём сообщение, отмечаем 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)
Telegram‑бот для (само)дисциплины на Python: aiogram 3, APScheduler и деплой на VDS - 2 [5]

Такое решение:

  • не плодит отдельные 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 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

www.BrainTools.ru

Rambler's Top100