# Настраиваемые напоминалки для админов — дизайн Дата: 2026-09-02 ## Проблема Админам нужно, чтобы бот сам напоминал о разовых или периодических событиях (например, об окончании аренды домена/сервера): дата и время, текст, кому из админов отправить, периодичность (разово / каждую неделю / каждый месяц / каждый год). ## Права доступа Создавать, просматривать и удалять напоминания может **любой админ** (из `ADMIN_IDS` или добавленный в рантайме, см. `_is_admin` в [bot.py](../../../bot.py)). Список напоминаний — общий для всех админов (как общая доска задач), отдельного разграничения «моё / чужое» нет. ## Хранение данных Новая таблица SQLite `reminders` (создаётся в `Database._init_db()` в [database.py](../../../database.py), по образцу существующих `admins`/`custom_faq`): ```sql CREATE TABLE IF NOT EXISTS reminders ( id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT NOT NULL, remind_at TEXT NOT NULL, -- "YYYY-MM-DD HH:MM:SS", исходное (якорное) время next_at TEXT NOT NULL, -- "YYYY-MM-DD HH:MM:SS", следующее срабатывание recurrence TEXT NOT NULL, -- 'once' | 'weekly' | 'monthly' | 'yearly' recipients TEXT NOT NULL, -- JSON: список vk_id, либо строка "all" created_by INTEGER NOT NULL, created_at TEXT NOT NULL, active INTEGER NOT NULL DEFAULT 1 ); ``` `remind_at` — неизменное якорное время первого срабатывания (день месяца/год для пересчёта периодов), `next_at` — то, что реально проверяет планировщик и что двигается вперёд после каждой отправки. CRUD-методы в `Database` (по аналогии с `add_admin`/`remove_admin`/`get_admins`): `add_reminder(...)`, `get_active_reminders()`, `get_due_reminders(now_str)`, `update_reminder_next_at(id, next_at)`, `deactivate_reminder(id)`, `delete_reminder(id)`. ## Планировщик Отдельный фоновый поток `threading.Thread(target=self._reminders_loop, daemon=True)`, запускается в `run()` рядом с уже существующими фоновыми потоками (прогрев медиа, рассылка). Цикл: ``` while True: now = datetime.now() # локальное серверное время, как везде в проекте (без TZ-логики) for r in db.get_due_reminders(now): отправить текст всем recipients (self.send_message) if r.recurrence == 'once': db.deactivate_reminder(r.id) else: next_at = compute_next(r.remind_at, r.recurrence, after=now) db.update_reminder_next_at(r.id, next_at) time.sleep(60) ``` `compute_next` считает **следующее срабатывание строго после now**, отталкиваясь от якорной даты `remind_at` (а не от предыдущего `next_at`) — так периодичность не дрейфует из-за клампинга коротких месяцев: - `weekly`: `remind_at + 7*N дней`, ближайшее значение `> now`; - `monthly`: тот же день месяца, что и в `remind_at`; если в целевом месяце такого дня нет — последний день этого месяца (31.01 → 28/29.02 → 31.03, а не 28.03); - `yearly`: тот же день/месяц; 29 февраля в невисокосный год → 28 февраля. **Пропуск при простое бота**: если бот был выключен и время срабатывания уже прошло — при первом же тике после старта напоминание досылается один раз, после чего сразу пересчитывается следующее *будущее* срабатывание (не досылается «пачка» пропущенных периодов). ## UI — мастер в админ-панели Новая кнопка «⏰ Напоминалки» в `get_admin_keyboard` ([keyboards.py](../../../keyboards.py)). **Список** (`_send_reminders_admin`): для каждого активного напоминания — id, дата следующего срабатывания, периодичность, текст (обрезанный превью), получатели. Плюс инструкция: «напоминалка добавить» / «напоминалка удалить ». **Добавление** — пошаговый мастер состояний (по образцу `ADMIN_FAQ_ADD` в [bot.py](../../../bot.py)): 1. `ADMIN_REMINDER_DATE` — ввод `ДД.ММ.ГГГГ ЧЧ:ММ`, валидация формата и что дата не в прошлом; 2. `ADMIN_REMINDER_TEXT` — текст напоминания; 3. `ADMIN_REMINDER_RECIPIENTS` — нумерованный список текущих админов + пункт «0 — все админы»; можно ввести несколько номеров через запятую (например «1,3»); 4. `ADMIN_REMINDER_PERIOD` — кнопки «Разово / Каждую неделю / Каждый месяц / Каждый год»; 5. подтверждение и запись в БД (`add_reminder`), возврат к списку напоминаний. Отмена мастера на любом шаге — существующим паттерном («отмена»/кнопка отмены → `get_cancel_admin_keyboard`). **Удаление** — по id, текстовой командой «напоминалка удалить », как удаление админа (`db.delete_reminder`). **Сознательное упрощение (YAGNI)**: редактирования существующего напоминания нет — только добавить/удалить, как сейчас устроено управление списком админов. Чтобы поправить напоминание — удалить и создать заново. ## Изменяемые файлы - [database.py](../../../database.py) — таблица `reminders` + CRUD-методы. - [bot.py](../../../bot.py) — мастер `ADMIN_REMINDER_*`, обработчики команд, фоновый поток-планировщик, запуск потока в `run()`. - [keyboards.py](../../../keyboards.py) — кнопка «⏰ Напоминалки» в админ-клавиатуре, клавиатура выбора периодичности. ## Тестирование Юнит-тесты в `tests/` (по образцу `tests/test_database.py`): - CRUD напоминаний в БД; - расчёт `compute_next` для всех 3 периодичностей, включая крайние случаи (31 число, 29 февраля); - логика «пропущенного» напоминания при простое (одна отправка + пересчёт в будущее).