added RAG, Multiuser, TG bot
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user