1. 整理多个文件的导入顺序,移除冗余空行和重复导入 2. 为Nostr订阅添加until参数防止重复拉取 3. 实现长消息分块发送并添加间隔等待 4. 新增Relay健康分数同步任务 5. 新增TLS强制检查配置项并支持从环境变量加载 6. 重构状态恢复逻辑适配异步存储 7. 修复反应发送的参数错误 8. 新增线程模拟自动补全消息上下文 9. 添加解密指标统计和更完善的错误日志
108 lines
4.2 KiB
Python
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")
|