Напоминалки: мастер создания, список и удаление в bot.py

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-09-02 21:44:23 +03:00
parent 0e162ac9a1
commit 86f8115de1
+151 -1
View File
@@ -24,6 +24,8 @@ except ImportError:
vk_api = None vk_api = None
from database import DatabaseManager from database import DatabaseManager
from reminders import parse_reminder_datetime, compute_next_occurrence
from datetime import datetime
import config import config
import mailer import mailer
@@ -47,6 +49,16 @@ BAN_WINDOW_SEC = 60
BAN_SEC = 600 BAN_SEC = 600
# Сколько секунд кэшировать список рантайм-админов из БД # Сколько секунд кэшировать список рантайм-админов из БД
ADMINS_CACHE_TTL = 30 ADMINS_CACHE_TTL = 30
# Как часто фоновый поток проверяет, не пора ли отправить напоминание
REMINDER_CHECK_INTERVAL_SEC = 60
REMINDER_PERIOD_LABELS = {
"once": "разово",
"weekly": "каждую неделю",
"monthly": "каждый месяц",
"yearly": "каждый год",
}
REMINDER_PERIOD_BY_LABEL = {v: k for k, v in REMINDER_PERIOD_LABELS.items()}
from keyboards import * # все get_*_keyboard from keyboards import * # все get_*_keyboard
@@ -460,6 +472,52 @@ class VKBotApplication:
"(админов из .env и супер-админов отсюда снять нельзя).") "(админов из .env и супер-админов отсюда снять нельзя).")
self._send_admins_panel(user_id) self._send_admins_panel(user_id)
def _all_admin_ids(self) -> list:
"""Полный список текущих админов: супер-админы + .env + добавленные в рантайме,
без дублей, отсортирован (используется для «все админы» и нумерации в мастере)."""
ids = set(getattr(config, "SUPER_ADMIN_IDS", []))
ids |= set(getattr(config, "ADMIN_IDS", []))
ids |= self._runtime_admins()
return sorted(ids)
def _send_reminders_admin(self, user_id: int):
"""Раздел «Напоминалки»: список активных + инструкции."""
rows = db.get_active_reminders()
listing = ""
for r in rows:
dt = datetime.strptime(r["next_at"], "%Y-%m-%d %H:%M:%S")
who = "всем админам" if r["recipients"] == "all" else \
", ".join(f"id{uid}" for uid in r["recipients"])
period = REMINDER_PERIOD_LABELS.get(r["recurrence"], r["recurrence"])
short = (r["text"][:50] + "") if len(r["text"]) > 50 else r["text"]
listing += (f"🔹 [{r['id']}] {dt.strftime('%d.%m.%Y %H:%M')} ({period}) → {who}\n"
f" «{short}»\n")
if not listing:
listing = "Активных напоминаний нет.\n"
kb = VkKeyboard(inline=True)
kb.add_button(" Добавить", color=VkKeyboardColor.PRIMARY)
kb.add_line()
kb.add_button("В админку ↩️", color=VkKeyboardColor.SECONDARY)
self.send_message(
user_id,
"⏰ Напоминалки\n\n"
f"{listing}\n"
"Чтобы удалить: напоминалка удалить <id>\n"
"Чтобы добавить: нажмите «➕ Добавить»",
keyboard=kb.get_keyboard()
)
def _handle_reminder_delete(self, user_id: int, text: str):
arg = text[len("напоминалка удалить"):].strip()
if not arg.isdigit():
self.send_message(user_id, "⚠️ Укажите номер: напоминалка удалить 3")
return
if db.delete_reminder(int(arg)):
self.send_message(user_id, f"✅ Напоминание №{arg} удалено.")
else:
self.send_message(user_id, f"⚠️ Напоминание №{arg} не найдено.")
self._send_reminders_admin(user_id)
def _cleanup_antiflood(self): def _cleanup_antiflood(self):
"""Периодически выметает протухшие записи анти-флуда, чтобы словари не росли бесконечно.""" """Периодически выметает протухшие записи анти-флуда, чтобы словари не росли бесконечно."""
now = time.time() now = time.time()
@@ -590,6 +648,21 @@ class VKBotApplication:
elif text_lower in ["ссылки 🔗", "ссылки", "ссылка"]: elif text_lower in ["ссылки 🔗", "ссылки", "ссылка"]:
self._send_links_admin(user_id) self._send_links_admin(user_id)
return return
elif text_lower in ["⏰ напоминалки", "напоминалки"]:
self._send_reminders_admin(user_id)
return
elif text_lower == " добавить":
db.update_user_state(user_id, "ADMIN_REMINDER_DATE")
self.send_message(
user_id,
"⏰ Новое напоминание.\nВведите дату и время: ДД.ММ.ГГГГ ЧЧ:ММ\n"
"Например: 15.03.2027 09:00",
keyboard=get_cancel_admin_keyboard()
)
return
elif text_lower.startswith("напоминалка удалить"):
self._handle_reminder_delete(user_id, text_clean)
return
elif text_lower in ["уведомления: вкл 🔔", "уведомления: выкл 🔕", "уведомления", "уведомления 🔔", "уведомления 🔕"]: elif text_lower in ["уведомления: вкл 🔔", "уведомления: выкл 🔕", "уведомления", "уведомления 🔔", "уведомления 🔕"]:
self._toggle_admin_notifications(user_id) self._toggle_admin_notifications(user_id)
return return
@@ -1623,6 +1696,84 @@ class VKBotApplication:
db.update_user_state(user_id, "FREE_CHAT") db.update_user_state(user_id, "FREE_CHAT")
return return
# ---------- Напоминалки ----------
if state == "ADMIN_REMINDER_DATE":
dt = parse_reminder_datetime(text)
if not dt:
self.send_message(user_id, "⚠️ Формат: ДД.ММ.ГГГГ ЧЧ:ММ (например, 15.03.2027 09:00)",
keyboard=get_cancel_admin_keyboard())
return
if dt <= datetime.now():
self.send_message(user_id, "⚠️ Дата и время должны быть в будущем.",
keyboard=get_cancel_admin_keyboard())
return
self._admin_ctx[user_id] = {"remind_at": dt.strftime("%Y-%m-%d %H:%M:%S")}
db.update_user_state(user_id, "ADMIN_REMINDER_TEXT")
self.send_message(user_id, "✏️ Введите текст напоминания:", keyboard=get_cancel_admin_keyboard())
return
if state == "ADMIN_REMINDER_TEXT":
if not text.strip():
self.send_message(user_id, "⚠️ Текст не может быть пустым.", keyboard=get_cancel_admin_keyboard())
return
self._admin_ctx.setdefault(user_id, {})["text"] = text.strip()
admins = self._all_admin_ids()
self._admin_ctx[user_id]["admins_list"] = admins
listing = "\n".join(f"{i + 1}. vk.com/id{a}" for i, a in enumerate(admins))
db.update_user_state(user_id, "ADMIN_REMINDER_RECIPIENTS")
self.send_message(
user_id,
"👥 Кому отправлять напоминание?\n"
f"{listing}\n\n"
"Введите номер (например, 2), несколько через запятую (1,3) или 0 — все админы.",
keyboard=get_cancel_admin_keyboard()
)
return
if state == "ADMIN_REMINDER_RECIPIENTS":
ctx = self._admin_ctx.get(user_id, {})
admins = ctx.get("admins_list", [])
raw = text.strip()
if raw == "0":
recipients = "all"
else:
picks = [p.strip() for p in raw.split(",") if p.strip()]
if not picks or not all(p.isdigit() for p in picks):
self.send_message(user_id, "⚠️ Введите номера через запятую или 0 для всех админов.",
keyboard=get_cancel_admin_keyboard())
return
indices = [int(p) for p in picks]
if any(i < 1 or i > len(admins) for i in indices):
self.send_message(user_id, "⚠️ Есть номер вне списка. Проверьте и введите снова.",
keyboard=get_cancel_admin_keyboard())
return
recipients = sorted({admins[i - 1] for i in indices})
ctx["recipients"] = recipients
db.update_user_state(user_id, "ADMIN_REMINDER_PERIOD")
self.send_message(user_id, "🔁 Периодичность напоминания:", keyboard=get_reminder_period_keyboard())
return
if state == "ADMIN_REMINDER_PERIOD":
recurrence = REMINDER_PERIOD_BY_LABEL.get(text_lower)
if not recurrence:
self.send_message(user_id, "⚠️ Выберите период кнопкой ниже.",
keyboard=get_reminder_period_keyboard())
return
ctx = self._admin_ctx.pop(user_id, {})
remind_at = ctx.get("remind_at")
rtext = ctx.get("text")
recipients = ctx.get("recipients")
if not (remind_at and rtext and recipients is not None):
db.update_user_state(user_id, "FREE_CHAT")
self.send_message(user_id, "⚠️ Что-то пошло не так, начните заново кнопкой «➕ Добавить».")
self._send_reminders_admin(user_id)
return
db.add_reminder(rtext, remind_at, recurrence, recipients, user_id)
db.update_user_state(user_id, "FREE_CHAT")
self.send_message(user_id, "✅ Напоминание создано.")
self._send_reminders_admin(user_id)
return
# Неизвестное ADMIN_-состояние — сброс # Неизвестное ADMIN_-состояние — сброс
self._admin_ctx.pop(user_id, None) self._admin_ctx.pop(user_id, None)
db.update_user_state(user_id, "FREE_CHAT") db.update_user_state(user_id, "FREE_CHAT")
@@ -1829,7 +1980,6 @@ class VKBotApplication:
logger.info("Бот успешно запущен в режиме прослушивания LongPoll...") logger.info("Бот успешно запущен в режиме прослушивания LongPoll...")
# Старт-пинг администратору — лёгкий сигнal «бот жив» # Старт-пинг администратору — лёгкий сигнal «бот жив»
try: try:
from datetime import datetime
ts = datetime.now().strftime("%d.%m.%Y %H:%M") ts = datetime.now().strftime("%d.%m.%Y %H:%M")
for admin_id in getattr(config, "ADMIN_IDS", []): for admin_id in getattr(config, "ADMIN_IDS", []):
self.send_message(admin_id, f"✅ Бот Кибершколы БАЗА запущен и на связи ({ts}).") self.send_message(admin_id, f"✅ Бот Кибершколы БАЗА запущен и на связи ({ts}).")