fix: deliver Buzz media as native attachments

This commit is contained in:
phil baker
2026-08-25 12:24:42 -05:00
committed by Teknium
parent ebda1fbf8e
commit fafc3ddce5
2 changed files with 133 additions and 54 deletions
+116 -54
View File
@@ -1458,42 +1458,61 @@ class BuzzAdapter(BasePlatformAdapter):
"""Send an image: local files upload via --file, URLs go as a link."""
local = Path(image_url).expanduser() if not image_url.startswith(("http://", "https://")) else None
if local is not None and local.is_file():
args = [
"messages", "send",
"--channel", str(chat_id),
"--file", str(local),
"--content", "-",
]
reply_target = self._resolve_reply_anchor(
(metadata or {}).get("thread_id") or reply_to
)
if reply_target and self._reply_to_mode != "off":
args += ["--reply-to", str(reply_target)]
code, out, err = await self._run_message_send(args, caption or "")
if code != 0:
return SendResult(success=False, error=_cli_error_message(err, code), retryable=code == 2)
try:
data = json.loads(out or "{}")
except ValueError:
data = {}
event_id = data.get("event_id")
if event_id:
self._mark_seen(str(chat_id), str(event_id))
self._remember_event_meta(
str(chat_id),
str(event_id),
self._self_pubkey,
caption or "",
)
return SendResult(
success=bool(data.get("accepted", True)),
message_id=str(event_id) if event_id else None,
raw_response=data,
return await self._send_file_attachment(
chat_id,
local,
caption=caption,
reply_to=reply_to,
metadata=metadata,
)
# Markdown renders in Buzz, so a URL arrives as a clickable image link.
text = f"{caption}\n{image_url}" if caption else image_url
return await self.send(chat_id, text, reply_to=reply_to, metadata=metadata)
async def _send_file_attachment(
self,
chat_id: str,
file_path: Path,
*,
caption: Optional[str] = None,
reply_to: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
) -> SendResult:
"""Upload a local file and publish it as a native Buzz attachment."""
local = Path(file_path).expanduser()
if not local.is_file():
return SendResult(success=False, error=f"File not found: {local}")
args = [
"messages", "send",
"--channel", str(chat_id),
"--file", str(local),
"--content", "-",
]
reply_target = self._resolve_reply_anchor(
(metadata or {}).get("thread_id") or reply_to
)
if reply_target and self._reply_to_mode != "off":
args += ["--reply-to", str(reply_target)]
code, out, err = await self._run_cli(args, input_text=caption or "")
if code != 0:
return SendResult(
success=False,
error=_cli_error_message(err, code),
retryable=code == 2,
)
try:
data = json.loads(out or "{}")
except ValueError:
data = {}
event_id = data.get("event_id")
if event_id:
self._mark_seen(str(chat_id), str(event_id))
return SendResult(
success=bool(data.get("accepted", True)),
message_id=str(event_id) if event_id else None,
raw_response=data,
)
async def send_image_file(
self,
chat_id: str,
@@ -1503,31 +1522,19 @@ class BuzzAdapter(BasePlatformAdapter):
metadata: Optional[Dict[str, Any]] = None,
**kwargs,
) -> SendResult:
"""Upload a local image through Buzz's native ``--file`` path.
Missing or non-file paths retain the Base fallback so host
filesystem paths are never echoed into chat (#74999).
"""
local = Path(image_path).expanduser()
if local.is_file():
args = [
"messages", "send",
"--channel", str(chat_id),
"--file", str(local),
"--content", "-",
]
reply_target = reply_to or (metadata or {}).get("thread_id")
if reply_target:
args += ["--reply-to", str(reply_target)]
code, out, err = await self._run_cli(args, input_text=caption or "")
if code != 0:
return SendResult(success=False, error=_cli_error_message(err, code), retryable=code == 2)
try:
data = json.loads(out or "{}")
except ValueError:
data = {}
event_id = data.get("event_id")
if event_id:
self._mark_seen(str(chat_id), str(event_id))
return SendResult(
success=bool(data.get("accepted", True)),
message_id=str(event_id) if event_id else None,
raw_response=data,
return await self._send_file_attachment(
chat_id,
local,
caption=caption,
reply_to=reply_to,
metadata=metadata,
)
return await super().send_image_file(
chat_id=chat_id,
@@ -1538,6 +1545,61 @@ class BuzzAdapter(BasePlatformAdapter):
**kwargs,
)
async def send_document(
self,
chat_id: str,
file_path: str,
caption: Optional[str] = None,
file_name: Optional[str] = None,
reply_to: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
**kwargs,
) -> SendResult:
"""Upload a local document through Buzz's native ``--file`` path."""
return await self._send_file_attachment(
chat_id,
Path(file_path),
caption=caption,
reply_to=reply_to,
metadata=metadata,
)
async def send_video(
self,
chat_id: str,
video_path: str,
caption: Optional[str] = None,
reply_to: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
**kwargs,
) -> SendResult:
"""Upload a local video through Buzz's native ``--file`` path."""
return await self._send_file_attachment(
chat_id,
Path(video_path),
caption=caption,
reply_to=reply_to,
metadata=metadata,
)
async def send_voice(
self,
chat_id: str,
audio_path: str,
caption: Optional[str] = None,
reply_to: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
**kwargs,
) -> SendResult:
"""Upload a local audio file through Buzz's native ``--file`` path."""
return await self._send_file_attachment(
chat_id,
Path(audio_path),
caption=caption,
reply_to=reply_to,
metadata=metadata,
)
async def get_chat_info(self, chat_id: str) -> Dict[str, Any]:
chat_id = str(chat_id)
state = self._channel_state.get(chat_id)
+17
View File
@@ -2415,6 +2415,22 @@ class TestBuzzAdapterSend:
assert stdin_text == "screenshot\n⚠️ Couldn't deliver the image attachment."
assert str(missing) not in stdin_text
@pytest.mark.asyncio
async def test_send_document_uses_native_file_flag(self, tmp_path):
document = tmp_path / "package.zip"
document.write_bytes(b"PK fake")
adapter = _make_adapter()
cli = _ScriptedCli()
cli.script("messages", "send", {"accepted": True, "event_id": "evt128", "message": ""})
adapter._run_cli = cli
result = await adapter.send_document(CHANNEL, str(document), caption="files")
assert result.success is True
args, stdin_text = cli.calls[0]
assert args[args.index("--file") + 1] == str(document)
assert stdin_text == "files"
@pytest.mark.asyncio
async def test_send_multiple_images_file_url_uses_native_file_send(self, tmp_path):
img = tmp_path / "shot with spaces.png"
@@ -2918,6 +2934,7 @@ class TestInboundMediaAuthorizationGate:
assert len(captured[0].media_urls) == 1
# ── Lifecycle ─────────────────────────────────────────────────────────────