该提交新增了完整的 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 部署,内置安全防护与流量控制机制。
133 lines
5.0 KiB
Python
133 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
from yuxi.channels.adapters.nostr.models import NostrProfile
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
profile_router = APIRouter(prefix="/nostr/profile", tags=["nostr-profile"])
|
|
|
|
|
|
class NostrProfileAPI:
|
|
"""Nostr Profile HTTP API — 管理 Kind 0 元数据"""
|
|
|
|
def __init__(self, adapter):
|
|
self._adapter = adapter
|
|
|
|
@property
|
|
def _crypto(self):
|
|
return self._adapter._crypto if self._adapter else None
|
|
|
|
@property
|
|
def _relay_manager(self):
|
|
return self._adapter._relay_manager if self._adapter else None
|
|
|
|
async def get_profile(self, account_id: str = "default") -> dict:
|
|
if not self._crypto:
|
|
raise HTTPException(status_code=503, detail="Adapter 未初始化")
|
|
|
|
pubkey = self._crypto.pubkey_hex()
|
|
|
|
publish_state_raw = (
|
|
self._adapter._state_store.load_profile("last_publish_state") if self._adapter._state_store else None
|
|
)
|
|
|
|
profile = await self._adapter.import_profile(pubkey)
|
|
if profile:
|
|
return {
|
|
"status": "ok",
|
|
"profile": profile,
|
|
"publishState": publish_state_raw
|
|
or {
|
|
"lastPublishedAt": None,
|
|
"lastPublishedEventId": None,
|
|
"lastPublishResults": None,
|
|
},
|
|
}
|
|
|
|
from yuxi.channels.adapters.nostr.profile import NostrProfileManager
|
|
|
|
if self._adapter._state_store:
|
|
manager = NostrProfileManager(self._crypto, self._relay_manager, self._adapter._state_store)
|
|
local = manager.load_local_profile(account_id)
|
|
if local:
|
|
return {
|
|
"status": "ok",
|
|
"profile": local.model_dump(),
|
|
"source": "local",
|
|
"publishState": manager.publish_state
|
|
or {
|
|
"lastPublishedAt": None,
|
|
"lastPublishedEventId": None,
|
|
"lastPublishResults": None,
|
|
},
|
|
}
|
|
|
|
return {
|
|
"status": "ok",
|
|
"profile": None,
|
|
"message": "Profile 未设置",
|
|
"publishState": {
|
|
"lastPublishedAt": None,
|
|
"lastPublishedEventId": None,
|
|
"lastPublishResults": None,
|
|
},
|
|
}
|
|
|
|
async def publish_profile(self, profile_data: dict, account_id: str = "default") -> dict:
|
|
if not self._crypto:
|
|
raise HTTPException(status_code=503, detail="Adapter 未初始化")
|
|
|
|
try:
|
|
result = await self._adapter.publish_profile(profile_data, account_id)
|
|
if result:
|
|
publish_state = {
|
|
"lastPublishedAt": result.get("created_at"),
|
|
"lastPublishedEventId": result.get("id"),
|
|
"lastPublishResults": {"success": True},
|
|
}
|
|
return {"status": "ok", "message": "Profile 已发布", "publishState": publish_state}
|
|
return {"status": "error", "message": "发布失败:所有 Relay 不可达"}
|
|
except Exception as e:
|
|
logger.error(f"[NostrProfile] 发布失败: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
async def import_profile(self, pubkey: str) -> dict:
|
|
if not self._crypto:
|
|
raise HTTPException(status_code=503, detail="Adapter 未初始化")
|
|
|
|
try:
|
|
from yuxi.channels.adapters.nostr.profile import NostrProfileManager
|
|
|
|
manager = NostrProfileManager(self._crypto, self._relay_manager)
|
|
profile = await manager.import_profile_from_relays(pubkey)
|
|
if profile:
|
|
return {"status": "ok", "profile": profile.model_dump()}
|
|
return {"status": "not_found", "message": f"未找到 pubkey {pubkey[:12]}... 的 Profile"}
|
|
except Exception as e:
|
|
logger.error(f"[NostrProfile] 导入失败: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
async def merge_profile(self, local_data: dict, imported_pubkey: str) -> dict:
|
|
if not self._crypto:
|
|
raise HTTPException(status_code=503, detail="Adapter 未初始化")
|
|
|
|
try:
|
|
from yuxi.channels.adapters.nostr.profile import NostrProfileManager
|
|
|
|
manager = NostrProfileManager(self._crypto, self._relay_manager)
|
|
imported = await manager.import_profile_from_relays(imported_pubkey)
|
|
if not imported:
|
|
return {
|
|
"status": "not_found",
|
|
"message": f"未找到 pubkey {imported_pubkey[:12]}... 的 Profile",
|
|
}
|
|
|
|
local = NostrProfile(**local_data)
|
|
merged = manager.merge_profiles(local, imported)
|
|
return {"status": "ok", "profile": merged.model_dump()}
|
|
except Exception as e:
|
|
logger.error(f"[NostrProfile] 合并失败: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|