该提交新增了完整的 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 部署,内置安全防护与流量控制机制。
140 lines
5.4 KiB
Python
140 lines
5.4 KiB
Python
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import os
|
||
from typing import Any
|
||
|
||
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
|
||
|
||
|
||
async def run_setup_wizard(config_dir: str | None = None) -> dict[str, Any]:
|
||
logger.info("[Nostr] Starting setup wizard")
|
||
|
||
accounts: dict[str, dict[str, Any]] = {}
|
||
account_counter = 0
|
||
|
||
while True:
|
||
if account_counter == 0:
|
||
account_name = "default"
|
||
else:
|
||
account_name = input(f"\n账户名称 (默认: account_{account_counter}): ").strip()
|
||
if not account_name:
|
||
account_name = f"account_{account_counter}"
|
||
if account_name in accounts:
|
||
logger.warning(f"账户 '{account_name}' 已存在,请使用不同名称")
|
||
continue
|
||
|
||
account_config = await _configure_account(account_name, account_counter)
|
||
if account_config is None:
|
||
if account_counter == 0:
|
||
return {"status": "cancelled", "message": "初始帐户配置已取消"}
|
||
continue
|
||
|
||
accounts[account_name] = account_config
|
||
account_counter += 1
|
||
|
||
if account_counter > 0:
|
||
more = input("\n添加其他账户? [y/n] (默认: n): ").strip().lower()
|
||
if more != "y":
|
||
break
|
||
|
||
config: dict[str, Any] = {"accounts": accounts}
|
||
primary = accounts.get("default", list(accounts.values())[0])
|
||
primary_crypto = NostrCrypto(primary.get("private_key", ""))
|
||
npub = primary_crypto.npub
|
||
|
||
logger.info("[Nostr] 设置向导完成: %s 个账户", len(accounts))
|
||
return {"status": "ok", "npub": npub, "config": config, "account_count": len(accounts)}
|
||
|
||
|
||
async def _configure_account(account_name: str, index: int) -> dict[str, Any] | None:
|
||
logger.info("[Nostr] 配置账户: %s", account_name)
|
||
|
||
env_key = os.environ.get("NOSTR_PRIVATE_KEY", "")
|
||
if env_key and index == 0:
|
||
use_env = input("\n检测到环境变量 NOSTR_PRIVATE_KEY。是否使用? [y/n] (默认: y): ").strip().lower()
|
||
if use_env != "n":
|
||
try:
|
||
crypto = NostrCrypto(env_key)
|
||
logger.info(f"[Nostr] 使用环境变量密钥: npub={crypto.npub}")
|
||
private_key = env_key
|
||
skip_key_input = True
|
||
except NostrCryptoError as e:
|
||
logger.warning(f"[Nostr] 环境变量密钥无效: {e},将跳转到手动输入")
|
||
skip_key_input = False
|
||
else:
|
||
skip_key_input = False
|
||
else:
|
||
skip_key_input = False
|
||
|
||
if not skip_key_input:
|
||
private_key = input("\nNostr 私钥 (nsec/hex,留空自动生成): ").strip()
|
||
else:
|
||
private_key = env_key
|
||
|
||
try:
|
||
crypto = NostrCrypto(private_key if private_key else None)
|
||
private_key = crypto.nsec()
|
||
logger.info(f"[Nostr] 密钥就绪: npub={crypto.npub}")
|
||
except NostrCryptoError as e:
|
||
logger.error(f"[Nostr] 私钥无效: {e}")
|
||
return None
|
||
|
||
relays_input = input("Relay URL 列表 (逗号分隔,默认: damus.io, primal.net, nostr.info, nos.lol): ").strip()
|
||
if relays_input:
|
||
relays = [r.strip() for r in relays_input.split(",") if r.strip()]
|
||
else:
|
||
relays = [
|
||
"wss://relay.damus.io",
|
||
"wss://relay.primal.net",
|
||
"wss://relay.nostr.info",
|
||
"wss://nos.lol",
|
||
]
|
||
|
||
dm_policy = input("DM 策略 [pairing/open/whitelist/disabled] (默认: pairing): ").strip().lower()
|
||
dm_policy = dm_policy if dm_policy in ("pairing", "open", "whitelist", "disabled") else "pairing"
|
||
|
||
nip17_input = input("启用 NIP-17 加密 [y/n] (默认: y): ").strip().lower()
|
||
nip17_enabled = nip17_input != "n"
|
||
|
||
streaming_mode = input("流式模式 [off/block/progress] (默认: block): ").strip().lower()
|
||
streaming_mode = streaming_mode if streaming_mode in ("off", "block", "progress") else "block"
|
||
|
||
markdown_tables = input("启用 Markdown 表格转换 [y/n] (默认: n): ").strip().lower()
|
||
markdown_table_mode = "convert" if markdown_tables == "y" else "off"
|
||
|
||
probe_input = input("是否探测 Relay 连接? [y/n] (默认: y): ").strip().lower()
|
||
if probe_input != "n":
|
||
logger.info("[Nostr] 正在探测 Relay 连接...")
|
||
probe_tasks = [probe_relay(url, timeout=8.0) for url in relays]
|
||
results = await asyncio.gather(*probe_tasks, return_exceptions=True)
|
||
reachable = 0
|
||
for result in results:
|
||
if isinstance(result, Exception):
|
||
continue
|
||
if result.connected:
|
||
reachable += 1
|
||
logger.info(f" ✅ {result.url} (延迟: {result.latency_ms:.1f}ms)")
|
||
else:
|
||
logger.warning(f" ❌ {result.url} - {result.error or '不可达'}")
|
||
|
||
if reachable == 0:
|
||
logger.warning("[Nostr] 所有 Relay 不可达,请检查网络和 URL")
|
||
else:
|
||
logger.info(f"[Nostr] {reachable}/{len(relays)} 个 Relay 可连接")
|
||
|
||
account_config: dict[str, Any] = {
|
||
"private_key": private_key,
|
||
"relays": relays,
|
||
"dm_policy": dm_policy,
|
||
"nip17_enabled": nip17_enabled,
|
||
"streaming_mode": streaming_mode,
|
||
"markdown_table_mode": markdown_table_mode,
|
||
}
|
||
if index > 0:
|
||
account_config["enabled"] = True
|
||
|
||
return account_config
|