82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
import time
|
||
|
|
from pathlib import Path
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from yuxi.utils.logging_config import logger
|
||
|
|
|
||
|
|
|
||
|
|
class ThreadPersistence:
|
||
|
|
def __init__(self, storage_path: str | None = None):
|
||
|
|
self._storage_path = storage_path
|
||
|
|
self._bindings: dict[str, dict[str, Any]] = {}
|
||
|
|
self._load()
|
||
|
|
|
||
|
|
def _load(self) -> None:
|
||
|
|
if not self._storage_path:
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
path = Path(self._storage_path)
|
||
|
|
if path.exists():
|
||
|
|
self._bindings = json.loads(path.read_text(encoding="utf-8"))
|
||
|
|
logger.debug(f"[Telegram] Loaded {len(self._bindings)} thread bindings")
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning(f"[Telegram] Failed to load thread bindings: {e}")
|
||
|
|
self._bindings = {}
|
||
|
|
|
||
|
|
def _save(self) -> None:
|
||
|
|
if not self._storage_path:
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
path = Path(self._storage_path)
|
||
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
path.write_text(json.dumps(self._bindings, ensure_ascii=False, indent=2), encoding="utf-8")
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning(f"[Telegram] Failed to save thread bindings: {e}")
|
||
|
|
|
||
|
|
def bind_channel_to_thread(
|
||
|
|
self,
|
||
|
|
channel_id: str,
|
||
|
|
thread_id: str,
|
||
|
|
metadata: dict[str, Any] | None = None,
|
||
|
|
) -> None:
|
||
|
|
entry = {
|
||
|
|
"channel_id": channel_id,
|
||
|
|
"thread_id": thread_id,
|
||
|
|
"bound_at": time.time(),
|
||
|
|
"metadata": metadata or {},
|
||
|
|
}
|
||
|
|
self._bindings[f"channel:{channel_id}"] = entry
|
||
|
|
self._bindings[f"thread:{thread_id}"] = entry
|
||
|
|
self._save()
|
||
|
|
logger.debug(f"[Telegram] Bound channel {channel_id} -> thread {thread_id}")
|
||
|
|
|
||
|
|
def get_thread_for_channel(self, channel_id: str) -> str | None:
|
||
|
|
binding = self._bindings.get(f"channel:{channel_id}")
|
||
|
|
return binding["thread_id"] if binding else None
|
||
|
|
|
||
|
|
def get_channel_for_thread(self, thread_id: str) -> str | None:
|
||
|
|
binding = self._bindings.get(f"thread:{thread_id}")
|
||
|
|
return binding["channel_id"] if binding else None
|
||
|
|
|
||
|
|
def unbind_channel(self, channel_id: str) -> None:
|
||
|
|
binding = self._bindings.pop(f"channel:{channel_id}", None)
|
||
|
|
if binding:
|
||
|
|
self._bindings.pop(f"thread:{binding['thread_id']}", None)
|
||
|
|
self._save()
|
||
|
|
|
||
|
|
def get_all_bindings(self) -> list[dict[str, Any]]:
|
||
|
|
seen = set()
|
||
|
|
result = []
|
||
|
|
for key, binding in self._bindings.items():
|
||
|
|
if key.startswith("channel:"):
|
||
|
|
result.append(binding)
|
||
|
|
seen.add(key)
|
||
|
|
return result
|
||
|
|
|
||
|
|
def clear(self) -> None:
|
||
|
|
self._bindings.clear()
|
||
|
|
self._save()
|