144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from aiogram import Bot
|
|
|
|
from bot.ha_client import HaClient
|
|
from bot.storage import LinkedUser, Storage
|
|
|
|
if TYPE_CHECKING:
|
|
pass
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
NOTICE_ROLES = frozenset({"notice", "character"})
|
|
TG_MAX_LEN = 4096
|
|
|
|
|
|
def split_telegram_message(text: str, limit: int = TG_MAX_LEN) -> list[str]:
|
|
if len(text) <= limit:
|
|
return [text]
|
|
chunks: list[str] = []
|
|
remaining = text
|
|
while remaining:
|
|
if len(remaining) <= limit:
|
|
chunks.append(remaining)
|
|
break
|
|
split_at = remaining.rfind("\n", 0, limit)
|
|
if split_at <= 0:
|
|
split_at = limit
|
|
chunks.append(remaining[:split_at])
|
|
remaining = remaining[split_at:].lstrip("\n")
|
|
return chunks
|
|
|
|
|
|
async def send_text(bot: Bot, chat_id: int, text: str) -> None:
|
|
for chunk in split_telegram_message(text):
|
|
await bot.send_message(chat_id, chunk)
|
|
|
|
|
|
async def advance_cursors(
|
|
storage: Storage,
|
|
client: HaClient,
|
|
user: LinkedUser,
|
|
) -> None:
|
|
sessions = await client.list_sessions()
|
|
for session in sessions:
|
|
session_id = int(session["id"])
|
|
after_id = await storage.get_last_message_id(user.telegram_id, session_id)
|
|
page = await client.get_messages(session_id, after_id=after_id or None, limit=100)
|
|
messages = page.get("messages") or []
|
|
max_id = after_id
|
|
for message in messages:
|
|
msg_id = int(message["id"])
|
|
max_id = max(max_id, msg_id)
|
|
if max_id > after_id:
|
|
await storage.set_last_message_id(user.telegram_id, session_id, max_id)
|
|
|
|
|
|
async def sync_notices_for_user(
|
|
bot: Bot,
|
|
storage: Storage,
|
|
ha_base_url: str,
|
|
user: LinkedUser,
|
|
*,
|
|
send: bool = True,
|
|
) -> None:
|
|
client = HaClient(ha_base_url, user.api_token)
|
|
|
|
try:
|
|
reminders = await client.get_reminders_snapshot()
|
|
pomodoro = await client.get_pomodoro_status()
|
|
except Exception:
|
|
logger.exception("Failed to fetch notify seq for telegram_id=%s", user.telegram_id)
|
|
reminders = {}
|
|
pomodoro = {}
|
|
|
|
reminder_seq = int(reminders.get("notify_seq") or 0)
|
|
pomodoro_seq = int((pomodoro.get("cycle") or {}).get("chat_notify_seq") or 0)
|
|
|
|
sessions = await client.list_sessions()
|
|
pending: list[tuple[int, str]] = []
|
|
|
|
for session in sessions:
|
|
session_id = int(session["id"])
|
|
after_id = await storage.get_last_message_id(user.telegram_id, session_id)
|
|
try:
|
|
page = await client.get_messages(session_id, after_id=after_id or None, limit=100)
|
|
except Exception:
|
|
logger.exception(
|
|
"Failed to fetch messages session_id=%s telegram_id=%s",
|
|
session_id,
|
|
user.telegram_id,
|
|
)
|
|
continue
|
|
|
|
messages = page.get("messages") or []
|
|
max_id = after_id
|
|
for message in messages:
|
|
msg_id = int(message["id"])
|
|
max_id = max(max_id, msg_id)
|
|
role = str(message.get("role") or "")
|
|
if role in NOTICE_ROLES and send:
|
|
content = str(message.get("content") or "").strip()
|
|
if content:
|
|
pending.append((msg_id, content))
|
|
|
|
if max_id > after_id:
|
|
await storage.set_last_message_id(user.telegram_id, session_id, max_id)
|
|
|
|
if send:
|
|
pending.sort(key=lambda item: item[0])
|
|
for _, content in pending:
|
|
try:
|
|
await send_text(bot, user.telegram_id, content)
|
|
except Exception:
|
|
logger.exception("Failed to send notice to telegram_id=%s", user.telegram_id)
|
|
|
|
await storage.update_seq(
|
|
user.telegram_id,
|
|
reminder_seq=reminder_seq,
|
|
pomodoro_seq=pomodoro_seq,
|
|
)
|
|
|
|
|
|
async def run_notify_worker(
|
|
bot: Bot,
|
|
storage: Storage,
|
|
ha_base_url: str,
|
|
poll_interval_sec: int,
|
|
) -> None:
|
|
import asyncio
|
|
|
|
logger.info("Notify worker started (interval=%ss)", poll_interval_sec)
|
|
while True:
|
|
users = await storage.list_linked_users()
|
|
for user in users:
|
|
try:
|
|
await sync_notices_for_user(bot, storage, ha_base_url, user, send=True)
|
|
except Exception:
|
|
logger.exception("Notify sync failed for telegram_id=%s", user.telegram_id)
|
|
await asyncio.sleep(poll_interval_sec)
|