Напоминалки: фоновый планировщик отправки в срок

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kenik
2026-09-02 21:49:28 +03:00
parent 86f8115de1
commit 5d88b8899e
+32
View File
@@ -1956,8 +1956,40 @@ class VKBotApplication:
time.sleep(0.4) # троттлинг против лимитов VK time.sleep(0.4) # троттлинг против лимитов VK
self.send_message(admin_id, f"✅ Рассылка завершена. Доставлено получателям: {sent} из {len(recipients)}.") self.send_message(admin_id, f"✅ Рассылка завершена. Доставлено получателям: {sent} из {len(recipients)}.")
def _check_due_reminders(self):
"""Проверяет и рассылает напоминания, чьё время наступило.
Вызывается фоновым потоком `_reminders_loop`, но выделена отдельно,
чтобы её можно было дёргать напрямую из тестов без запуска потока."""
now = datetime.now()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
for r in db.get_due_reminders(now_str):
targets = self._all_admin_ids() if r["recipients"] == "all" else r["recipients"]
body = f"⏰ Напоминание:\n{r['text']}"
for uid in targets:
try:
self.send_message(uid, body)
except Exception as e:
logger.error(f"Не удалось отправить напоминание {r['id']} пользователю {uid}: {e}")
if r["recurrence"] == "once":
db.deactivate_reminder(r["id"])
else:
anchor = datetime.strptime(r["remind_at"], "%Y-%m-%d %H:%M:%S")
next_dt = compute_next_occurrence(anchor, r["recurrence"], now)
db.update_reminder_next_at(r["id"], next_dt.strftime("%Y-%m-%d %H:%M:%S"))
def _reminders_loop(self):
"""Фоновый цикл: раз в REMINDER_CHECK_INTERVAL_SEC секунд проверяет и шлёт напоминания.
Обрыв на одной итерации (например, сбой отправки) не должен останавливать цикл."""
while True:
try:
self._check_due_reminders()
except Exception as e:
logger.error(f"Ошибка обработки напоминаний: {e}")
time.sleep(REMINDER_CHECK_INTERVAL_SEC)
def run(self): def run(self):
"""Запуск бесконечного цикла прослушивания событий VK LongPoll.""" """Запуск бесконечного цикла прослушивания событий VK LongPoll."""
threading.Thread(target=self._reminders_loop, daemon=True).start()
if not self.longpoll: if not self.longpoll:
logger.warning("Бот запущен в режиме ЭМУЛЯЦИИ (без валидных токенов VK API).") logger.warning("Бот запущен в режиме ЭМУЛЯЦИИ (без валидных токенов VK API).")
print("Симуляция работы бота в консоли. Напишите 'выход' для завершения.") print("Симуляция работы бота в консоли. Напишите 'выход' для завершения.")