ForcePilot/backend/package/yuxi/channels/adapters/nostr/setup.py

277 lines
11 KiB
Python
Raw Normal View History

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",
},
},
},
}