Initial import of Shirobana bot backend
Include utility modules, shared channels, prompts, read-only skills, wardrobe memory and regression tests. Externalize legacy credentials and exclude private runtime state.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
"""Non-delivering channel adapter used around legacy command handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from .artifacts import ArtifactError, ArtifactStore
|
||||
|
||||
|
||||
MEDIA_TYPES = frozenset(("image", "file", "record", "video"))
|
||||
|
||||
|
||||
class CaptureChannel:
|
||||
"""Capture text and OneBot media segments instead of sending them.
|
||||
|
||||
Legacy modules call ``channel.send_message(CaptureChannel, ...)``. Returning
|
||||
``False`` from ``send`` is intentional: the shared sender then skips its
|
||||
private-message mirror fan-out, preventing a second real reply. Tool media
|
||||
delivery is performed separately and recorded by ``ToolExecutor``.
|
||||
"""
|
||||
|
||||
def __init__(self, context: Any, artifacts: ArtifactStore) -> None:
|
||||
self._context = context
|
||||
self._real_channel = context.channel
|
||||
self._artifacts = artifacts
|
||||
self.name = getattr(context.channel, "name", "unknown")
|
||||
self.texts: List[str] = []
|
||||
self.media: List[Dict[str, Any]] = []
|
||||
self.messages: List[Any] = []
|
||||
self.capture_errors: List[str] = []
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""Preserve channel metadata used by legacy modules (for example TG flags)."""
|
||||
return getattr(self._real_channel, name)
|
||||
|
||||
def is_admin(self, user_id: Any) -> bool:
|
||||
"""Use the already validated execution context, never mutable handler state."""
|
||||
return bool(self._context.is_admin)
|
||||
|
||||
async def send(self, msg_type: str, target_id: int, content: Any) -> bool:
|
||||
"""Record one legacy send operation; never deliver it to the real channel."""
|
||||
self.messages.append(content)
|
||||
if isinstance(content, str):
|
||||
self.texts.append(content)
|
||||
return False
|
||||
if not isinstance(content, (list, tuple)):
|
||||
self.texts.append(str(content))
|
||||
return False
|
||||
for segment in content:
|
||||
if not isinstance(segment, dict):
|
||||
continue
|
||||
segment_type = segment.get("type")
|
||||
data = segment.get("data")
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
if segment_type == "text":
|
||||
text = data.get("text")
|
||||
if text is not None:
|
||||
self.texts.append(str(text))
|
||||
continue
|
||||
if segment_type not in MEDIA_TYPES:
|
||||
continue
|
||||
source = data.get("file") or data.get("url")
|
||||
if not source:
|
||||
self.capture_errors.append("media segment has no source")
|
||||
continue
|
||||
try:
|
||||
artifact = self._artifacts.register(
|
||||
source,
|
||||
media_type=segment_type,
|
||||
filename=data.get("name"),
|
||||
ctx=self._context,
|
||||
)
|
||||
except (ArtifactError, OSError, ValueError):
|
||||
self.capture_errors.append("media could not be registered")
|
||||
continue
|
||||
self.media.append({
|
||||
"type": segment_type,
|
||||
"artifact_id": artifact["artifact_id"],
|
||||
"data": dict(data),
|
||||
})
|
||||
# Suppress channel._mirror_private; this is capture, not a failed tool.
|
||||
return False
|
||||
|
||||
@property
|
||||
def artifact_ids(self) -> List[str]:
|
||||
"""Return IDs in capture order, de-duplicated for compact result metadata."""
|
||||
return list(dict.fromkeys(item["artifact_id"] for item in self.media))
|
||||
|
||||
def public_artifacts(self) -> List[Dict[str, Any]]:
|
||||
"""Return safe metadata for all successfully captured media."""
|
||||
return [self._artifacts.get(item["artifact_id"], self._context) for item in self.media]
|
||||
|
||||
def safe_text(self) -> str:
|
||||
"""Join captured text for subsequent model rewriting."""
|
||||
return "\n".join(text for text in self.texts if text)
|
||||
Reference in New Issue
Block a user