277 lines
11 KiB
Python
277 lines
11 KiB
Python
|
|
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",
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|