Напоминалки: мастер создания, список и удаление в 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
|
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}).")
|
||||||
|
|||||||
Reference in New Issue
Block a user