Напоминалки: фоновый планировщик отправки в срок
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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("Симуляция работы бота в консоли. Напишите 'выход' для завершения.")
|
||||||
|
|||||||
Reference in New Issue
Block a user