ForcePilot/backend/package/yuxi/channels/adapters/urbit/poke_api.py
Kris 80e3f66974 refactor(urbit): 整理导入顺序并修复多处代码细节
1.  统一调整多个文件的导入排序,将TYPE_CHECKING相关导入放在正确位置
2.  修复rate_limiter中当限制数<=0时直接返回false的逻辑
3.  为message_cache新增更新消息内容的方法
4.  重构extract_graph_content支持日记类型内容提取
5.  调整@提及匹配的正则表达式,避免误匹配
6.  完善invite_manager,添加客户端和凭据支持并实现自动接受群邀请逻辑
7.  调整adapter.py中的导入顺序和初始化逻辑
8.  修复monitor中的编辑事件处理,改为异步处理并实现消息更新缓存
9.  调整datetime导入顺序,统一使用UTC在前的格式
2026-05-13 16:16:27 +08:00

139 lines
4.0 KiB
Python

from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, Any
from yuxi.utils.logging_config import logger
if TYPE_CHECKING:
from .client import UrbitClient
class HttpPokeApiClient:
def __init__(self, client: UrbitClient, channel_id: str):
self._client = client
self._channel_id = channel_id
self._closed = False
@property
def channel_id(self) -> str:
return self._channel_id
async def poke(self, app: str, mark: str, json_data: dict[str, Any], *, timeout: float = 15.0) -> dict[str, Any]:
if self._closed:
raise RuntimeError("HttpPokeApiClient is closed")
payload = {
"action": "poke",
"ship": self._client.ship_name,
"app": app,
"mark": mark,
"json": json_data,
}
r = await self._client.put(
f"/~/channel/{self._channel_id}",
json=payload,
timeout=timeout,
)
r.raise_for_status()
return r.json()
async def scry(self, app: str, path: str) -> dict[str, Any]:
if self._closed:
raise RuntimeError("HttpPokeApiClient is closed")
r = await self._client.get(
f"/~/scry/{app}{path}",
timeout=10.0,
)
r.raise_for_status()
return r.json()
async def subscribe(self, app: str, path: str) -> None:
if self._closed:
raise RuntimeError("HttpPokeApiClient is closed")
payload = {
"action": "subscribe",
"ship": self._client.ship_name,
"app": app,
"path": path,
}
r = await self._client.put(
f"/~/channel/{self._channel_id}",
json=payload,
timeout=10.0,
)
r.raise_for_status()
logger.debug(f"[Urbit] HttpPokeApi subscribed: {app}{path} on {self._channel_id}")
async def unsubscribe(self) -> None:
if self._closed:
return
payload = {
"action": "unsubscribe",
"ship": self._client.ship_name,
}
try:
r = await self._client.put(
f"/~/channel/{self._channel_id}",
json=payload,
timeout=5.0,
)
r.raise_for_status()
except Exception as e:
logger.debug(f"[Urbit] HttpPokeApi unsubscribe error on {self._channel_id}: {e}")
async def delete(self) -> None:
if self._closed:
return
try:
r = await self._client.delete(f"/~/channel/{self._channel_id}")
if r.status_code not in (200, 204):
logger.debug(f"[Urbit] HttpPokeApi DELETE {self._channel_id}{r.status_code}")
except Exception as e:
logger.debug(f"[Urbit] HttpPokeApi DELETE error on {self._channel_id}: {e}")
async def close(self) -> None:
if self._closed:
return
await self.unsubscribe()
await self.delete()
self._closed = True
logger.debug(f"[Urbit] HttpPokeApiClient closed: {self._channel_id}")
@property
def is_closed(self) -> bool:
return self._closed
@asynccontextmanager
async def with_http_poke_api(
client: UrbitClient,
channel_id: str | None = None,
) -> AsyncIterator[HttpPokeApiClient]:
import time
cid = channel_id or f"poke-api-{int(time.time() * 1000)}"
api = HttpPokeApiClient(client, cid)
logger.debug(f"[Urbit] HttpPokeApiClient opened: {cid}")
try:
yield api
finally:
try:
await api.close()
except Exception as e:
logger.warning(f"[Urbit] HttpPokeApiClient cleanup error for {cid}: {e}")
async def create_http_poke_api(client: UrbitClient, channel_id: str | None = None) -> HttpPokeApiClient:
import time
cid = channel_id or f"poke-api-{int(time.time() * 1000)}"
api = HttpPokeApiClient(client, cid)
logger.debug(f"[Urbit] HttpPokeApiClient created: {cid}")
return api