ForcePilot/backend/package/yuxi/channels/adapters/urbit/threads.py
Kris 1f78c44b03 refactor: 整理并清理项目中的冗余代码与格式问题
这是一个批量整理提交,包含以下主要改动:
1.  删除多处冗余的空行和未使用的导入
2.  修复文件末尾缺少换行符的问题
3.  调整部分模块的导入顺序与代码排版
4.  修复部分配置默认值与策略逻辑
5.  新增多个功能模块与辅助工具
6.  完善异常处理与日志记录
7.  修复速率限制、消息缓存、权限校验等逻辑bug
8.  废弃部分旧有API与配置项并添加警告提示
2026-05-12 14:51:53 +08:00

145 lines
5.0 KiB
Python

from __future__ import annotations
from typing import Any, TYPE_CHECKING
from yuxi.utils.logging_config import logger
if TYPE_CHECKING:
from .client import UrbitClient
from .settings import SettingsStore
class ThreadManager:
def __init__(self, max_history: int = 20, context_lines: int = 10):
self._participated: set[str] = set()
self._max_history = max_history
self._context_lines = context_lines
self._settings_store: SettingsStore | None = None
def bind_settings_store(self, store: SettingsStore) -> None:
self._settings_store = store
async def restore_from_store(self) -> None:
if not self._settings_store:
return
stored = self._settings_store.get("participatedThreads", [])
if isinstance(stored, list):
self._participated = set(stored)
async def persist_participated(self) -> None:
if not self._settings_store:
return
self._settings_store.update_key("participatedThreads", list(self._participated))
def is_thread_reply(self, raw_data: dict[str, Any]) -> bool:
graph_update = raw_data.get("graph-update", {})
additions = graph_update.get("additions", {})
for node_data in additions.values():
post = node_data.get("post", {})
contents = post.get("contents", [])
for item in contents:
if isinstance(item, dict) and "reply" in item:
return True
return False
def get_reply_parent_id(self, raw_data: dict[str, Any]) -> str | None:
graph_update = raw_data.get("graph-update", {})
additions = graph_update.get("additions", {})
for node_data in additions.values():
post = node_data.get("post", {})
contents = post.get("contents", [])
for item in contents:
if isinstance(item, dict) and "reply" in item:
reply_data = item["reply"]
return reply_data.get("id") or reply_data
return None
def add_participated(self, thread_id: str) -> None:
self._participated.add(thread_id)
def has_participated(self, thread_id: str) -> bool:
return thread_id in self._participated
async def fetch_thread_history(
self, client: UrbitClient, ship_name: str, resource_path: str, parent_id: str
) -> list[dict[str, Any]]:
from .scry import scry_get
try:
path = f"/graph/{ship_name}/{resource_path}"
result = await scry_get(client, "graph", path)
if not result or not isinstance(result, dict):
return []
nodes = result.get("graph", {}).get("nodes", {})
thread_messages: list[dict[str, Any]] = []
for _node_id, node in nodes.items():
post = node.get("post", {})
if not post:
continue
if _is_child_of(parent_id, node):
thread_messages.append(_extract_message_content(node))
thread_messages.sort(key=lambda m: m.get("time", 0))
return thread_messages[-self._max_history :]
except Exception as e:
logger.warning(f"[Urbit] Failed to fetch thread history: {e}")
return []
def build_thread_context(self, history: list[dict[str, Any]]) -> str:
recent = history[-self._context_lines :]
if not recent:
return ""
lines: list[str] = [
f"[Thread conversation - {len(recent)} previous replies...]",
"[Previous messages]",
]
for msg in recent:
author = msg.get("author", "unknown")
content = msg.get("content", "")
lines.append(f"~{author}: {content}")
lines.append("[Current message]")
return "\n".join(lines)
def _is_child_of(parent_id: str, node: dict[str, Any]) -> bool:
children = node.get("children", {})
if parent_id in children:
return True
for child_node in children.values():
if isinstance(child_node, dict) and _is_child_of(parent_id, child_node):
return True
return False
def _extract_message_content(node: dict[str, Any]) -> dict[str, Any]:
post = node.get("post", {})
author = post.get("author", "")
index = post.get("index", "")
time_sent = node.get("post", {}).get("time-sent", 0)
contents = post.get("contents", [])
text_parts: list[str] = []
for item in contents:
if "text" in item:
text_parts.append(item["text"])
elif "mention" in item:
text_parts.append(f"~{item['mention']}")
elif "url" in item:
text_parts.append(item["url"])
elif "code" in item:
code_block = item["code"]
if isinstance(code_block, dict):
text_parts.append(code_block.get("code", ""))
else:
text_parts.append(str(code_block))
return {
"author": author,
"index": index,
"content": "".join(text_parts),
"time": time_sent,
}