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
|