""" Управление постами в канале. Использует интерфейс Notifier — не привязан к Telegram или любому другому мессенджеру. Инициализируется один раз при старте бота через set_notifier(). Чтобы сменить канал: вызови set_notifier() с другим адаптером. Чтобы отправлять в несколько каналов: используй MultiNotifier (создай адаптер). """ from __future__ import annotations import logging import config import database from bot.keyboards import build_task_keyboard from bot.messages import build_full_text from bot.notifier import Notifier log = logging.getLogger(__name__) _notifier: Notifier | None = None def set_notifier(notifier: Notifier) -> None: """Регистрирует активный адаптер уведомлений. Вызывается при старте бота.""" global _notifier _notifier = notifier def get_notifier() -> Notifier: if _notifier is None: raise RuntimeError("Notifier not initialized. Call set_notifier() first.") return _notifier # ── Публичные операции с постами ────────────────────────────────────────────── def recreate_post( task_id: int, task_number: str, base_text: str, group_id: int = 0, closed: bool = False, new_event_lines: list[str] | None = None, ): """ Удаляет старый пост задачи и создаёт новый, накапливая события. Изменения (• ...) дедуплицируются по тексту. Комментарии (💬 ...) и описания (📝 ...) добавляются всегда. """ notifier = get_notifier() stored = database.get_stored_events(task_id) stored_set = set(stored) if new_event_lines: added = [] for line in new_event_lines: if not line: continue is_change = line.startswith("•") if is_change and line in stored_set: continue added.append(line) stored_set.add(line) stored = stored + added else: added = [] responsible = database.get_responsible(task_id) full_text = build_full_text(base_text, responsible, stored, new_count=len(added)) keyboard = build_task_keyboard({ "task_number": task_number, "group_id": group_id, "closed": closed, }) old = database.get_message_data(task_id) try: message_id = notifier.send(full_text, keyboard) database.save_message_id(task_id, message_id, task_number, group_id, base_text) if added: database.save_stored_events(task_id, stored) except Exception as e: log.error(f"recreate_post failed for task {task_id}: {e}") return if old: notifier.delete(old["message_id"]) def refresh_responsible(task_id: int, task_number: str): """Редактирует текст поста — обновляет блок «В работе».""" notifier = get_notifier() data = database.get_message_data(task_id) if not data or not data.get("base_text"): return responsible = database.get_responsible(task_id) events_lines = database.get_stored_events(task_id) full_text = build_full_text(data["base_text"], responsible, events_lines, new_count=0) keyboard = build_task_keyboard({ "task_number": task_number, "group_id": data.get("group_id") or 0, }) try: notifier.edit_text(data["message_id"], full_text, keyboard) except Exception as e: if not notifier.is_stale_error(e): log.warning(f"refresh_responsible failed: {e}") def send_gif_for_to(task_id: int): """ Отправляет GIF для заявок с ТО. Предварительно удаляет предыдущую гифку если была. Если GIF_URL пустой — ничего не делает. """ if not config.GIF_URL: return notifier = get_notifier() old_gif_id = database.get_gif_message_id(task_id) if old_gif_id: notifier.delete(old_gif_id) database.delete_gif_message_id(task_id) try: message_id = notifier.send_animation(config.GIF_URL, disable_notification=True) database.save_gif_message_id(task_id, message_id) log.info(f"GIF sent for task {task_id}, message_id={message_id}") except Exception as e: log.error(f"Failed to send GIF for task {task_id}: {e}")