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