ForcePilot/backend/package/yuxi/channels/adapters/nostr/setup_plugin.py
Kris a1f8288d20 feat(nostr): 实现完整的 Nostr 协议适配器模块
该提交新增了完整的 Nostr 去中心化社交网络适配器实现,包含以下核心功能:
1. 基础加密与密钥处理:支持 nsec/npub/hex 格式密钥转换,实现 NIP-04/NIP-17 加解密
2. Relay 管理与健康监控:支持多 Relay 连接、自动重连、健康评分与自动选优
3. 事件与消息处理:实现事件校验、去重、速率限制、消息缓存与状态持久化
4. 个人资料管理:支持发布/导入/合并 Nostr Kind 0 元数据事件
5. 配对机制:实现安全的双向配对通信流程
6. Zap 功能:支持 NIP-57 打赏请求与收据解析
7. NIP-05 验证:实现域名身份验证
8. 配置系统:完整的配置校验与多账户支持
9. 监控与指标:提供连接监控、事件统计与健康快照
10. HTTP API:提供个人资料管理的 RESTful 接口
11. 安装向导:命令行配置向导与初始化流程

所有模块均遵循 Nostr 协议规范,支持多账户、多 Relay 部署,内置安全防护与流量控制机制。
2026-05-12 00:47:41 +08:00

99 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
from typing import Any
from yuxi.channels.adapters.nostr.setup import NostrSetupAdapter
from yuxi.channels.adapters.nostr.crypto import NostrCrypto, NostrCryptoError
from yuxi.channels.adapters.nostr.config import NostrConfig
class NostrSetupPlugin:
channel_id: str = "nostr"
channel_label: str = "Nostr (Decentralized)"
channel_description: str = "Nostr 去中心化社交网络 DM 协议配置向导"
def __init__(self, config: dict[str, Any] | None = None):
self._config = config or {}
self._setup_adapter = NostrSetupAdapter()
@property
def config(self) -> dict[str, Any]:
return self._config
def get_setup_wizard(self) -> dict[str, Any]:
return {
"channel_id": self.channel_id,
"label": self.channel_label,
"description": self.channel_description,
"steps": self._setup_adapter.get_setup_steps(),
"help": "详细文档请参考: /docs/channels/nostr",
}
async def validate_credentials(self, private_key: str) -> dict[str, Any]:
result = await self._setup_adapter.verify_key(private_key)
if result.get("success"):
return {"valid": True, "npub": result.get("npub"), "hex": result.get("hex")}
return {"valid": False, "error": result.get("error", "unknown")}
async def probe_connectivity(self, relays: list[str], timeout_ms: int = 8000) -> dict[str, Any]:
result = await self._setup_adapter.probe_relays(relays)
return {
"connected": result.get("success", False),
"reachable": result.get("reachable", 0),
"total": result.get("total", 0),
"details": result.get("details", []),
}
def get_recommendations(self) -> dict[str, Any]:
return {
"relays": [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nostr.info",
"wss://nos.lol",
],
"dm_policy": "pairing",
"nip17_enabled": True,
"streaming_mode": "block",
}
async def validate_full_config(self, full_config: dict[str, Any]) -> dict[str, Any]:
errors: list[str] = []
warnings: list[str] = []
try:
NostrConfig.from_dict(full_config)
except Exception as e:
errors.append(f"配置验证失败: {e}")
return {"valid": False, "errors": errors, "warnings": warnings}
private_key = full_config.get("private_key") or full_config.get("accounts", {}).get("default", {}).get(
"private_key", ""
)
if private_key:
try:
NostrCrypto(private_key)
except NostrCryptoError as e:
errors.append(f"私钥无效: {e}")
else:
warnings.append("未配置私钥,将自动生成临时密钥")
relays = full_config.get("relays") or full_config.get("accounts", {}).get("default", {}).get("relays")
if not relays:
warnings.append("未配置 Relay将使用默认 Relay 列表")
else:
invalid_relays = [r for r in relays if not r.startswith(("ws://", "wss://"))]
if invalid_relays:
warnings.append(f"以下 Relay URL 格式可能无效: {invalid_relays}")
return {"valid": len(errors) == 0, "errors": errors, "warnings": warnings}
def get_setup_plugin(config: dict[str, Any] | None = None) -> NostrSetupPlugin:
return NostrSetupPlugin(config)
async def verify_channel_setup(config: dict[str, Any]) -> dict[str, Any]:
plugin = NostrSetupPlugin(config)
return await plugin.validate_full_config(config)