ForcePilot/backend/package/yuxi/channels/adapters/nostr/monitor.py
Kris 7002557cf4 refactor(nostr): 整理代码结构并新增多项功能优化
1.  整理多个文件的导入顺序,移除冗余空行和重复导入
2.  为Nostr订阅添加until参数防止重复拉取
3.  实现长消息分块发送并添加间隔等待
4.  新增Relay健康分数同步任务
5.  新增TLS强制检查配置项并支持从环境变量加载
6.  重构状态恢复逻辑适配异步存储
7.  修复反应发送的参数错误
8.  新增线程模拟自动补全消息上下文
9.  添加解密指标统计和更完善的错误日志
2026-05-13 16:13:03 +08:00

108 lines
4.2 KiB
Python

from __future__ import annotations
import asyncio
import time
from collections.abc import Awaitable, Callable
from yuxi.channels.adapters.nostr.crypto import NostrCrypto
from yuxi.channels.adapters.nostr.relay_manager import RelayManager
from yuxi.utils.logging_config import logger
class NostrMonitor:
def __init__(
self,
crypto: NostrCrypto,
relay_manager: RelayManager,
backfill_window_sec: int = 120,
):
self._crypto = crypto
self._relay_manager = relay_manager
self._listeners: list[asyncio.Task] = []
self._running = False
self._last_subscribe_ts: int = 0
self._backfill_window_sec = backfill_window_sec
self._eose_received: set[str] = set()
self._on_eose_handlers: list[Callable[[str, int], Awaitable[None]]] = []
self._onclose_handlers: list[Callable[[str], Awaitable[None]]] = []
self._subscription_active = False
@property
def running(self) -> bool:
return self._running
def on_event(self, handler: Callable[[dict], Awaitable[None]]) -> None:
self._relay_manager.on_event(handler)
def on_eose(self, handler: Callable[[str, int], Awaitable[None]]) -> None:
self._on_eose_handlers.append(handler)
def on_subscription_close(self, handler: Callable[[str], Awaitable[None]]) -> None:
self._onclose_handlers.append(handler)
async def _handle_subscription_close(self, reason: str) -> None:
self._subscription_active = False
logger.warning("[NostrMonitor] 订阅关闭: reason=%s", reason)
for handler in self._onclose_handlers:
try:
await handler(reason)
except Exception:
logger.debug("onclose handler error", exc_info=True)
async def subscribe(self, dm_policy: str) -> None:
now = int(time.time())
if self._last_subscribe_ts == 0:
self._last_subscribe_ts = now - self._backfill_window_sec
since = self._last_subscribe_ts
self._last_subscribe_ts = now
filters = [{"kinds": [1, 4, 5, 7, 1059], "since": since, "until": now + 60}]
self._relay_manager.set_pubkey_filter([self._crypto.pubkey_hex()])
await self._relay_manager.subscribe(filters)
self._subscription_active = True
async def start(self) -> None:
self._running = True
self._relay_manager.on_eose(self._handle_eose)
connections = self._relay_manager.list_connections()
for url, ws in connections.items():
if ws and ws.open:
task = asyncio.create_task(self._listen_relay(url))
self._listeners.append(task)
logger.info(f"Nostr Monitor 已启动,监听 {len(self._listeners)} 个 Relay")
async def stop(self) -> None:
self._running = False
for task in self._listeners:
task.cancel()
self._listeners.clear()
if self._subscription_active:
await self._handle_subscription_close("stop called")
async def _handle_eose(self, url: str) -> None:
self._eose_received.add(url)
total_connections = len(self._relay_manager.list_connections())
logger.debug(f"Nostr EOSE from {url} ({len(self._eose_received)}/{total_connections})")
for handler in self._on_eose_handlers:
try:
await handler(url, len(self._eose_received))
except Exception:
logger.debug("EOSE handler error", exc_info=True)
async def _listen_relay(self, url: str) -> None:
while self._running:
connections = self._relay_manager.list_connections()
ws = connections.get(url)
if ws and ws.open:
await self._relay_manager.listen(url)
await asyncio.sleep(self._relay_manager._reconnect_interval)
else:
await asyncio.sleep(self._relay_manager._reconnect_interval)
all_disconnected = True
for ws in self._relay_manager.list_connections().values():
if ws and ws.open:
all_disconnected = False
break
if all_disconnected and self._subscription_active:
await self._handle_subscription_close("all relays disconnected")