新增 Zalo OA、Zoom Chat、Zulip 三个渠道扩展。 Zalo OA 渠道扩展主要模块:sidecar_client, config, gateway, outbound, streaming, pairing, security, auth, dedupe, directory, monitor, status, session, reactions, tools Zoom Chat 渠道扩展主要模块:config, gateway, webhook, outbound, streaming, pairing, security, crypto, dedupe, actions, media, mentions, monitor, status, session, reactions, threading Zulip 渠道扩展主要模块:client, config, gateway, outbound, streaming, pairing, security, monitor, status
128 lines
4.2 KiB
Python
128 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from yuxi.channel.extensions.zulip.client import ZulipAsyncClient
|
|
from yuxi.channel.extensions.zulip.format import safe_split_zulip_content, truncate_zulip_content
|
|
from yuxi.channel.extensions.zulip.types import OutboundResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def parse_target_id(target_id: str) -> tuple[str, str | list[str]]:
|
|
if target_id.startswith("stream:"):
|
|
stream_name = target_id[len("stream:") :]
|
|
return "stream", stream_name
|
|
elif target_id.startswith("private:"):
|
|
user_email = target_id[len("private:") :]
|
|
return "private", [user_email]
|
|
else:
|
|
raise ValueError(f"Invalid target_id: {target_id}")
|
|
|
|
|
|
class ZulipOutbound:
|
|
async def send_text(
|
|
self,
|
|
client: ZulipAsyncClient,
|
|
target_id: str,
|
|
content: str,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
) -> OutboundResult:
|
|
chat_type, destination = parse_target_id(target_id)
|
|
|
|
chunks = safe_split_zulip_content(content)
|
|
last_result: OutboundResult | None = None
|
|
|
|
for chunk in chunks:
|
|
truncated = truncate_zulip_content(chunk)
|
|
payload: dict[str, Any] = {
|
|
"type": chat_type,
|
|
"to": destination,
|
|
"content": truncated,
|
|
}
|
|
if chat_type == "stream" and thread_id:
|
|
payload["topic"] = thread_id
|
|
|
|
try:
|
|
result = await client.send_message(payload)
|
|
last_result = OutboundResult(
|
|
message_id=str(result.get("id", "")),
|
|
success=result.get("result") == "success",
|
|
raw=result,
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Failed to send text to %s: %s", target_id, exc)
|
|
last_result = OutboundResult(
|
|
success=False,
|
|
error=str(exc),
|
|
)
|
|
|
|
return last_result or OutboundResult(success=False, error="no chunks sent")
|
|
|
|
async def send_media(
|
|
self,
|
|
client: ZulipAsyncClient,
|
|
target_id: str,
|
|
file_path: str,
|
|
media_type: str,
|
|
*,
|
|
thread_id: str | None = None,
|
|
) -> OutboundResult:
|
|
try:
|
|
upload_result = await client.upload_file(file_path)
|
|
file_url = upload_result.get("url", "")
|
|
file_name = Path(file_path).name
|
|
|
|
if media_type.startswith("image/"):
|
|
markdown = f"[{file_name}]({file_url})"
|
|
else:
|
|
markdown = f"[📎 {file_name}]({file_url})"
|
|
|
|
return await self.send_text(
|
|
client,
|
|
target_id,
|
|
markdown,
|
|
thread_id=thread_id,
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Failed to send media to %s: %s", target_id, exc)
|
|
return OutboundResult(success=False, error=str(exc))
|
|
|
|
async def edit_message(
|
|
self,
|
|
client: ZulipAsyncClient,
|
|
message_id: int,
|
|
content: str,
|
|
) -> OutboundResult:
|
|
try:
|
|
truncated = truncate_zulip_content(content)
|
|
result = await client.update_message(message_id, truncated)
|
|
return OutboundResult(
|
|
message_id=str(message_id),
|
|
success=result.get("result") == "success",
|
|
raw=result,
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Failed to edit message %s: %s", message_id, exc)
|
|
return OutboundResult(success=False, error=str(exc))
|
|
|
|
async def delete_message(
|
|
self,
|
|
client: ZulipAsyncClient,
|
|
message_id: int,
|
|
) -> OutboundResult:
|
|
try:
|
|
result = await client.delete_message(message_id)
|
|
return OutboundResult(
|
|
message_id=str(message_id),
|
|
success=result.get("result") == "success",
|
|
raw=result,
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Failed to delete message %s: %s", message_id, exc)
|
|
return OutboundResult(success=False, error=str(exc))
|