实现了完整的Flock渠道接入能力,包含消息收发、Webhook事件监听、账号配置、安全校验、媒体文件处理等功能,支持私聊和群组聊天,适配ForcePilot插件规范。
185 lines
5.4 KiB
Python
185 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
import httpx
|
|
|
|
from .constants import ENDPOINT_GET_UPLOAD_URL, ENDPOINT_SEND_MESSAGE
|
|
from .types import FlockOutboundResult, FlockUploadResult
|
|
from .utils import create_http_client, flock_api_call
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def upload_file(
|
|
file_path: str,
|
|
file_name: str,
|
|
content_type: str,
|
|
target_id: str,
|
|
bot_token: str,
|
|
client: httpx.AsyncClient | None = None,
|
|
) -> FlockUploadResult:
|
|
own_client = client is None
|
|
if own_client:
|
|
client = create_http_client()
|
|
|
|
try:
|
|
file_size = os.path.getsize(file_path)
|
|
|
|
upload_url_resp = await _get_upload_url(client, bot_token, target_id, file_name, file_size, content_type)
|
|
if not upload_url_resp.success:
|
|
return upload_url_resp
|
|
|
|
upload_url = upload_url_resp.upload_url
|
|
file_id = upload_url_resp.file_id
|
|
|
|
if upload_url:
|
|
await _upload_file_content(client, upload_url, file_path, file_name, content_type)
|
|
|
|
return FlockUploadResult(success=True, file_id=file_id, upload_url=upload_url)
|
|
|
|
except Exception as e:
|
|
logger.error("Flock file upload failed: %s", e)
|
|
return FlockUploadResult(success=False, error=str(e))
|
|
finally:
|
|
if own_client:
|
|
await client.aclose()
|
|
|
|
|
|
async def _get_upload_url(
|
|
client: httpx.AsyncClient,
|
|
bot_token: str,
|
|
target_id: str,
|
|
file_name: str,
|
|
file_size: int,
|
|
content_type: str,
|
|
) -> FlockUploadResult:
|
|
payload = {
|
|
"to": target_id,
|
|
"fileName": file_name,
|
|
"fileSize": file_size,
|
|
"contentType": content_type,
|
|
}
|
|
try:
|
|
result = await flock_api_call(client, ENDPOINT_GET_UPLOAD_URL, bot_token, payload)
|
|
upload_url = result.get("uploadUrl", "")
|
|
file_id = result.get("fileId", "")
|
|
return FlockUploadResult(success=True, file_id=file_id, upload_url=upload_url)
|
|
except Exception as e:
|
|
logger.error("Flock getUploadUrl failed: %s", e)
|
|
return FlockUploadResult(success=False, error=str(e))
|
|
|
|
|
|
async def _upload_file_content(
|
|
client: httpx.AsyncClient,
|
|
upload_url: str,
|
|
file_path: str,
|
|
file_name: str,
|
|
content_type: str,
|
|
) -> None:
|
|
with open(file_path, "rb") as f:
|
|
files = {"file": (file_name, f, content_type)}
|
|
upload_client = httpx.AsyncClient(timeout=60.0)
|
|
try:
|
|
resp = await upload_client.post(upload_url, files=files)
|
|
if resp.status_code not in (200, 201, 204):
|
|
logger.warning("Flock file upload response: HTTP %d", resp.status_code)
|
|
raise RuntimeError(f"File upload failed: HTTP {resp.status_code}")
|
|
finally:
|
|
await upload_client.aclose()
|
|
|
|
|
|
async def send_image(
|
|
target_id: str,
|
|
image_url: str,
|
|
title: str = "",
|
|
*,
|
|
thread_id: str | None = None,
|
|
bot_token: str,
|
|
client: httpx.AsyncClient | None = None,
|
|
) -> FlockOutboundResult:
|
|
payload: dict = {
|
|
"to": target_id,
|
|
"text": title or "Image",
|
|
"attachments": [
|
|
{
|
|
"title": title or "Image",
|
|
"views": {
|
|
"image": {
|
|
"original": {"src": image_url},
|
|
"thumbnail": {"src": image_url},
|
|
}
|
|
},
|
|
}
|
|
],
|
|
}
|
|
if thread_id:
|
|
payload["threadId"] = thread_id
|
|
|
|
own_client = client is None
|
|
if own_client:
|
|
client = create_http_client()
|
|
|
|
try:
|
|
result = await flock_api_call(client, ENDPOINT_SEND_MESSAGE, bot_token, payload)
|
|
return FlockOutboundResult(success=True, message_uid=result.get("uid", ""))
|
|
except Exception as e:
|
|
logger.error("Flock image send failed: %s", e)
|
|
retryable = getattr(e, "retryable", False)
|
|
return FlockOutboundResult(success=False, error=str(e), retryable=retryable)
|
|
finally:
|
|
if own_client:
|
|
await client.aclose()
|
|
|
|
|
|
async def send_file(
|
|
target_id: str,
|
|
file_path: str,
|
|
file_name: str,
|
|
*,
|
|
thread_id: str | None = None,
|
|
bot_token: str,
|
|
client: httpx.AsyncClient | None = None,
|
|
) -> FlockOutboundResult:
|
|
import mimetypes
|
|
|
|
content_type, _ = mimetypes.guess_type(file_name)
|
|
if not content_type:
|
|
content_type = "application/octet-stream"
|
|
|
|
own_client = client is None
|
|
if own_client:
|
|
client = create_http_client()
|
|
|
|
try:
|
|
upload_result = await upload_file(file_path, file_name, content_type, target_id, bot_token, client)
|
|
if not upload_result.success:
|
|
return FlockOutboundResult(
|
|
success=False,
|
|
error=f"File upload failed: {upload_result.error}",
|
|
)
|
|
|
|
payload: dict = {
|
|
"to": target_id,
|
|
"text": file_name,
|
|
"attachments": [
|
|
{
|
|
"title": file_name,
|
|
"fileId": upload_result.file_id,
|
|
}
|
|
],
|
|
}
|
|
if thread_id:
|
|
payload["threadId"] = thread_id
|
|
|
|
result = await flock_api_call(client, ENDPOINT_SEND_MESSAGE, bot_token, payload)
|
|
return FlockOutboundResult(success=True, message_uid=result.get("uid", ""))
|
|
except Exception as e:
|
|
logger.error("Flock file send failed: %s", e)
|
|
retryable = getattr(e, "retryable", False)
|
|
return FlockOutboundResult(success=False, error=str(e), retryable=retryable)
|
|
finally:
|
|
if own_client:
|
|
await client.aclose()
|