7.6 KiB
Настраиваемые напоминалки для админов — дизайн
Дата: 2026-09-02
Проблема
Админам нужно, чтобы бот сам напоминал о разовых или периодических событиях (например, об окончании аренды домена/сервера): дата и время, текст, кому из админов отправить, периодичность (разово / каждую неделю / каждый месяц / каждый год).
Права доступа
Создавать, просматривать и удалять напоминания может любой админ (из ADMIN_IDS
или добавленный в рантайме, см. _is_admin в bot.py). Список
напоминаний — общий для всех админов (как общая доска задач), отдельного разграничения
«моё / чужое» нет.
Хранение данных
Новая таблица SQLite reminders (создаётся в Database._init_db() в
database.py, по образцу существующих admins/custom_faq):
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).
Список (_send_reminders_admin): для каждого активного напоминания — id, дата
следующего срабатывания, периодичность, текст (обрезанный превью), получатели.
Плюс инструкция: «напоминалка добавить» / «напоминалка удалить ».
Добавление — пошаговый мастер состояний (по образцу ADMIN_FAQ_ADD в
bot.py):
ADMIN_REMINDER_DATE— вводДД.ММ.ГГГГ ЧЧ:ММ, валидация формата и что дата не в прошлом;ADMIN_REMINDER_TEXT— текст напоминания;ADMIN_REMINDER_RECIPIENTS— нумерованный список текущих админов + пункт «0 — все админы»; можно ввести несколько номеров через запятую (например «1,3»);ADMIN_REMINDER_PERIOD— кнопки «Разово / Каждую неделю / Каждый месяц / Каждый год»;- подтверждение и запись в БД (
add_reminder), возврат к списку напоминаний.
Отмена мастера на любом шаге — существующим паттерном («отмена»/кнопка отмены →
get_cancel_admin_keyboard).
Удаление — по id, текстовой командой «напоминалка удалить », как удаление
админа (db.delete_reminder).
Сознательное упрощение (YAGNI): редактирования существующего напоминания нет — только добавить/удалить, как сейчас устроено управление списком админов. Чтобы поправить напоминание — удалить и создать заново.
Изменяемые файлы
- database.py — таблица
reminders+ CRUD-методы. - bot.py — мастер
ADMIN_REMINDER_*, обработчики команд, фоновый поток-планировщик, запуск потока вrun(). - keyboards.py — кнопка «⏰ Напоминалки» в админ-клавиатуре, клавиатура выбора периодичности.
Тестирование
Юнит-тесты в tests/ (по образцу tests/test_database.py):
- CRUD напоминаний в БД;
- расчёт
compute_nextдля всех 3 периодичностей, включая крайние случаи (31 число, 29 февраля); - логика «пропущенного» напоминания при простое (одна отправка + пересчёт в будущее).