diff --git a/bot/notifier.py b/bot/notifier.py index 77c46b7..99b1c3a 100644 --- a/bot/notifier.py +++ b/bot/notifier.py @@ -12,9 +12,12 @@ """ from __future__ import annotations +import logging from abc import ABC, abstractmethod from dataclasses import dataclass +log = logging.getLogger(__name__) + # ── Агностичное представление клавиатуры ────────────────────────────────────── @@ -101,39 +104,48 @@ class TelegramChannelNotifier(Notifier): # ── Реализация интерфейса ────────────────────────────────────────────────── def send(self, text: str, keyboard: Keyboard | None = None) -> int: + log.info("Telegram send_message → channel %s", self._channel_id) msg = self._bot.send_message( self._channel_id, text, parse_mode="Markdown", disable_web_page_preview=True, reply_markup=self._to_markup(keyboard), ) + log.info("Telegram send_message OK, message_id=%s", msg.message_id) return msg.message_id def edit_text(self, message_id: int, text: str, keyboard: Keyboard | None = None): + log.info("Telegram edit_message_text → message_id=%s", message_id) self._bot.edit_message_text( text, self._channel_id, message_id, parse_mode="Markdown", disable_web_page_preview=True, reply_markup=self._to_markup(keyboard), ) + log.info("Telegram edit_message_text OK, message_id=%s", message_id) def edit_keyboard(self, message_id: int, keyboard: Keyboard | None): + log.info("Telegram edit_message_reply_markup → message_id=%s", message_id) self._bot.edit_message_reply_markup( self._channel_id, message_id, reply_markup=self._to_markup(keyboard), ) def delete(self, message_id: int): + log.info("Telegram delete_message → message_id=%s", message_id) try: self._bot.delete_message(self._channel_id, message_id) - except Exception: - pass + log.info("Telegram delete_message OK, message_id=%s", message_id) + except Exception as e: + log.warning("Telegram delete_message failed (message_id=%s): %s", message_id, e) def send_animation(self, url: str, disable_notification: bool = True) -> int: + log.info("Telegram send_animation → channel %s", self._channel_id) msg = self._bot.send_animation( self._channel_id, url, disable_notification=disable_notification, ) + log.info("Telegram send_animation OK, message_id=%s", msg.message_id) return msg.message_id def is_stale_error(self, exc: Exception) -> bool: diff --git a/bot/poster.py b/bot/poster.py index 169f3f3..70600b7 100644 --- a/bot/poster.py +++ b/bot/poster.py @@ -50,7 +50,8 @@ def recreate_post( Изменения (• ...) дедуплицируются по тексту. Комментарии (💬 ...) и описания (📝 ...) добавляются всегда. """ - notifier = get_notifier() + notifier = get_notifier() + stored = database.get_stored_events(task_id) stored_set = set(stored) diff --git a/gunicorn.conf.py b/gunicorn.conf.py index 86ad7a1..4d1c2d9 100644 --- a/gunicorn.conf.py +++ b/gunicorn.conf.py @@ -38,10 +38,17 @@ access_log_format = '%(h)s "%(r)s" %(s)s %(b)s %(D)sµs' def on_starting(server): """Инициализация до форка воркеров (выполняется в мастер-процессе).""" + import logging import config import database from intradesk import reference + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", + handlers=[logging.StreamHandler()], + ) + os.makedirs(os.path.dirname(config.DB_FILE), exist_ok=True) database.init_db() reference.seed_static() diff --git a/web/server.py b/web/server.py index 4679742..1ae85a8 100644 --- a/web/server.py +++ b/web/server.py @@ -1,6 +1,7 @@ import json import logging import os +import queue import threading from flask import Flask, Response, jsonify, render_template, request, stream_with_context, abort @@ -19,6 +20,24 @@ from intradesk.handler import handle_webhook log = logging.getLogger(__name__) app = Flask(__name__, template_folder=os.path.join(os.path.dirname(__file__), "templates")) +# ── Очередь вебхуков (последовательная обработка) ───────────────────────────── + +_webhook_queue: queue.Queue = queue.Queue() + + +def _webhook_worker(): + while True: + data = _webhook_queue.get() + try: + handle_webhook(data) + except Exception: + log.exception("Unhandled error in webhook worker") + finally: + _webhook_queue.task_done() + + +threading.Thread(target=_webhook_worker, daemon=True, name="webhook-worker").start() + # ── Авторизация ──────────────────────────────────────────────────────────────── @@ -97,7 +116,7 @@ def webhook(): if not data: return jsonify({"error": "Invalid JSON"}), 400 - threading.Thread(target=handle_webhook, args=(data,), daemon=True).start() + _webhook_queue.put(data) return jsonify({"ok": True}), 200