1. 整理多个文件的导入顺序,移除冗余空行和重复导入 2. 为Nostr订阅添加until参数防止重复拉取 3. 实现长消息分块发送并添加间隔等待 4. 新增Relay健康分数同步任务 5. 新增TLS强制检查配置项并支持从环境变量加载 6. 重构状态恢复逻辑适配异步存储 7. 修复反应发送的参数错误 8. 新增线程模拟自动补全消息上下文 9. 添加解密指标统计和更完善的错误日志
132 lines
5.0 KiB
Python
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))
|