from __future__ import annotations from yuxi.channels.adapters.nostr.crypto import NostrCrypto, NostrCryptoError from yuxi.channels.adapters.nostr.probe import probe_relay from yuxi.utils.logging_config import logger import asyncio class NostrSetupAdapter: def __init__(self, adapter=None): self._adapter = adapter def get_setup_steps(self) -> list[dict]: return [ { "step": 1, "title": "生成或输入私钥", "description": "Nostr 私钥用于签名和加密。可以留空自动生成,或输入现有的 nsec/hex 格式私钥", "field_key": "private_key", "required": False, "help": "支持 nsec1... 或 64 位 hex 格式", }, { "step": 2, "title": "配置 Relay 服务器", "description": "Relay 是中继服务器,用于收发 Nostr 消息。支持 ws:// 和 wss:// 协议", "field_key": "relays", "required": False, "default": [ "wss://relay.damus.io", "wss://relay.primal.net", "wss://relay.nostr.info", "wss://nos.lol", ], "help": "多个 Relay URL 以逗号分隔", }, { "step": 3, "title": "选择 DM 策略", "description": "控制谁可以向你的 Nostr Bot 发送私信", "field_key": "dm_policy", "required": True, "default": "pairing", "options": ["pairing", "open", "whitelist", "disabled"], "help": "pairing=配对认证 | open=所有人 | whitelist=白名单 | disabled=禁用", }, { "step": 4, "title": "配置高级选项", "description": "配置 NIP-17 加密、流式输出模式、Markdown 表格转换等高级选项", "field_key": "advanced", "required": False, "default": { "nip17_enabled": True, "streaming_mode": "block", "markdown_table_mode": "off", "nip42_auth_enabled": False, }, }, { "step": 5, "title": "探测 Relay 连接", "description": "验证 Relay 服务器是否可达", "action": "probe_relays", "field_key": "relays", }, { "step": 6, "title": "验证私钥", "description": "验证私钥格式并获取对应的 npub", "action": "verify_key", "field_key": "private_key", }, ] async def probe_relays(self, relays: list[str]) -> dict: probe_tasks = [probe_relay(url, timeout=8.0) for url in relays] results = await asyncio.gather(*probe_tasks, return_exceptions=True) reachable = 0 total = len(relays) details: list[dict] = [] for result in results: if isinstance(result, Exception): details.append({"error": str(result)}) continue entry = { "url": result.url, "connected": result.connected, "latency_ms": round(result.latency_ms, 1) if result.latency_ms else None, } if result.connected: reachable += 1 if result.error: entry["error"] = result.error[:120] details.append(entry) return { "success": reachable > 0, "reachable": reachable, "total": total, "details": details, } async def verify_key(self, private_key: str) -> dict: if not private_key: crypto = NostrCrypto(None) return { "success": True, "auto_generated": True, "npub": crypto.npub, "hex": crypto.pubkey_hex(), "nsec": crypto.nsec(), } try: crypto = NostrCrypto(private_key) return { "success": True, "npub": crypto.npub, "hex": crypto.pubkey_hex(), } except NostrCryptoError as e: return {"success": False, "error": str(e)} async def run_interactive_setup(self) -> dict: logger.info("[Nostr Setup] Starting interactive setup wizard") steps = self.get_setup_steps() return { "status": "setup_required", "channel": "nostr", "steps": steps, "help": "详细文档请参考: /docs/channels/nostr", } def generate_config_schema(self) -> dict: return { "private_key": { "type": "str", "required": False, "label": "私钥 (nsec/hex)", "description": "Nostr 私钥 (nsec/hex),留空自动生成", "placeholder": "nsec1... 或 64 位 hex 格式", "help": "建议使用 nsec1 格式。留空则自动生成一个新的临时密钥", }, "relays": { "type": "list[str]", "required": False, "label": "Relay 中继服务器", "description": "Relay 中继服务器 URL 列表 (ws:// 或 wss://)", "placeholder": "wss://relay.damus.io", "help": "至少配置 1 个 Relay 用于收发消息。建议使用 wss:// 加密连接", "default": [ "wss://relay.damus.io", "wss://relay.primal.net", "wss://relay.nostr.info", "wss://nos.lol", ], }, "dm_policy": { "type": "str", "required": False, "default": "pairing", "label": "DM 策略", "description": "控制谁可以向您的 Nostr Bot 发送私信", "help": "pairing=配对认证 | open=所有人 | whitelist=白名单 | disabled=禁用 DM", "enum": ["pairing", "open", "whitelist", "disabled"], }, "nip17_enabled": { "type": "bool", "required": False, "default": True, "label": "NIP-17 Gift Wrap 加密", "description": "启用 NIP-17 Gift Wrap 加密(推荐)", "help": "NIP-17 提供更好的隐私保护,建议开启", }, "streaming_mode": { "type": "str", "required": False, "default": "block", "label": "流式输出模式", "description": "控制消息的流式输出方式", "help": "off=关闭流式 | block=分段输出 | progress=进度式输出", "enum": ["off", "block", "progress"], }, "markdown_table_mode": { "type": "str", "required": False, "default": "off", "label": "Markdown 表格转换", "description": "将 Markdown 表格转换为适合 Nostr 的格式", "help": "Nostr 客户端通常不支持原生的 Markdown 表格渲染", "enum": ["off", "convert"], }, "nip42_auth_enabled": { "type": "bool", "required": False, "default": False, "label": "NIP-42 AUTH 认证", "description": "启用 Relay AUTH 认证 (NIP-42)", "help": "需要 Relay 支持 NIP-42 才能生效", }, "allow_from": { "type": "list[str]", "required": False, "default": [], "label": "DM 白名单", "description": "DM 白名单 (npub/hex/pubkey)", "placeholder": "npub1...", "help": "当 DM 策略为 whitelist 时,只有此列表中的 pubkey 能发消息", }, "profile": { "type": "dict", "required": False, "label": "Bot Profile", "description": "Nostr Kind 0 元数据 Profile", "schema": { "name": { "type": "str", "default": "", "label": "用户名", "description": "Nostr 用户名", "placeholder": "my_nostr_bot", }, "display_name": { "type": "str", "default": "", "label": "显示名称", "description": "Nostr 显示名称", "placeholder": "My Nostr Bot", }, "about": { "type": "str", "default": "", "label": "简介", "description": "Bot 简介", "placeholder": "A Nostr bot powered by ForcePilot", }, "picture": { "type": "str", "default": "", "label": "头像 URL", "description": "头像图片 URL(需要 https)", "placeholder": "https://example.com/avatar.png", }, "banner": { "type": "str", "default": "", "label": "横幅图 URL", "description": "Profile 横幅图 URL(需要 https)", "placeholder": "https://example.com/banner.png", }, "website": { "type": "str", "default": "", "label": "个人网站", "description": "个人/项目网站 URL", "placeholder": "https://example.com", }, "nip05": { "type": "str", "default": "", "label": "NIP-05 标识", "description": "NIP-05 验证标识 (name@domain)", "placeholder": "bot@example.com", }, "lud16": { "type": "str", "default": "", "label": "Lightning Address", "description": "Lightning 地址用于接收 Zap 打赏", "placeholder": "bot@getalby.com", }, }, }, }