Files
bot_vk_ikp_prodagi/docs/superpowers/specs/2026-09-02-admin-reminders-design.md

122 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Настраиваемые напоминалки для админов — дизайн
Дата: 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, дата
следующего срабатывания, периодичность, текст (обрезанный превью), получатели.
Плюс инструкция: «напоминалка добавить» / «напоминалка удалить <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, текстовой командой «напоминалка удалить <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 февраля);
- логика «пропущенного» напоминания при простое (одна отправка + пересчёт в будущее).