Напоминалки: мастер создания, список и удаление в bot.py
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,8 @@ except ImportError:
|
||||
vk_api = None
|
||||
|
||||
from database import DatabaseManager
|
||||
from reminders import parse_reminder_datetime, compute_next_occurrence
|
||||
from datetime import datetime
|
||||
import config
|
||||
import mailer
|
||||
|
||||
@@ -47,6 +49,16 @@ BAN_WINDOW_SEC = 60
|
||||
BAN_SEC = 600
|
||||
# Сколько секунд кэшировать список рантайм-админов из БД
|
||||
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
|
||||
|
||||
@@ -460,6 +472,52 @@ class VKBotApplication:
|
||||
"(админов из .env и супер-админов отсюда снять нельзя).")
|
||||
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):
|
||||
"""Периодически выметает протухшие записи анти-флуда, чтобы словари не росли бесконечно."""
|
||||
now = time.time()
|
||||
@@ -590,6 +648,21 @@ class VKBotApplication:
|
||||
elif text_lower in ["ссылки 🔗", "ссылки", "ссылка"]:
|
||||
self._send_links_admin(user_id)
|
||||
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 ["уведомления: вкл 🔔", "уведомления: выкл 🔕", "уведомления", "уведомления 🔔", "уведомления 🔕"]:
|
||||
self._toggle_admin_notifications(user_id)
|
||||
return
|
||||
@@ -1623,6 +1696,84 @@ class VKBotApplication:
|
||||
db.update_user_state(user_id, "FREE_CHAT")
|
||||
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_-состояние — сброс
|
||||
self._admin_ctx.pop(user_id, None)
|
||||
db.update_user_state(user_id, "FREE_CHAT")
|
||||
@@ -1829,7 +1980,6 @@ class VKBotApplication:
|
||||
logger.info("Бот успешно запущен в режиме прослушивания LongPoll...")
|
||||
# Старт-пинг администратору — лёгкий сигнal «бот жив»
|
||||
try:
|
||||
from datetime import datetime
|
||||
ts = datetime.now().strftime("%d.%m.%Y %H:%M")
|
||||
for admin_id in getattr(config, "ADMIN_IDS", []):
|
||||
self.send_message(admin_id, f"✅ Бот Кибершколы БАЗА запущен и на связи ({ts}).")
|
||||
|
||||
Reference in New Issue
Block a user