ForcePilot/backend/package/yuxi/channels/adapters/nostr/profile_api.py
Kris 7002557cf4 refactor(nostr): 整理代码结构并新增多项功能优化
1.  整理多个文件的导入顺序,移除冗余空行和重复导入
2.  为Nostr订阅添加until参数防止重复拉取
3.  实现长消息分块发送并添加间隔等待
4.  新增Relay健康分数同步任务
5.  新增TLS强制检查配置项并支持从环境变量加载
6.  重构状态恢复逻辑适配异步存储
7.  修复反应发送的参数错误
8.  新增线程模拟自动补全消息上下文
9.  添加解密指标统计和更完善的错误日志
2026-05-13 16:13:03 +08:00

132 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))