ForcePilot/backend/package/yuxi/channel/extensions/nextcloud_talk/conversation.py
Kris 6fc9a4a94c feat(channel): 添加 Nextcloud Talk 渠道扩展
新增 Nextcloud Talk 渠道扩展,支持在 Yuxi 平台中集成 Nextcloud Talk 即时通讯渠道。

包含以下功能模块:
- accounts: 账户管理
- bot_admin: Bot 管理
- chat_api: 聊天 API 封装
- config_schema: 配置模式
- conversation: 会话管理
- gateway: SSE/WebSocket 网关接入
- inbound: 入站消息处理
- send: 消息发送
- webhook_server: Webhook 服务
- format: 消息格式转换
- normalize: 消息规范化
- poll_api: 投票 API
- reaction_api: 表情反应 API
- policy: 安全策略
- probe: 健康探测
- replay_guard: 重放防护
- signature: 签名验证
- room_info: 房间信息
- types: 类型定义
2026-05-21 11:29:56 +08:00

291 lines
7.0 KiB
Python

from __future__ import annotations
import base64
import logging
from urllib.parse import urljoin
import httpx
logger = logging.getLogger(__name__)
OCS_ROOM_PATH = "/ocs/v2.php/apps/spreed/api/v4/room"
OCS_ROOM_TOKEN_PATH = "/ocs/v2.php/apps/spreed/api/v4/room/{token}"
DEFAULT_TIMEOUT = 30.0
def _auth_headers(api_user: str, api_password: str) -> dict:
auth = base64.b64encode(f"{api_user}:{api_password}".encode()).decode()
return {
"Authorization": f"Basic {auth}",
"OCS-APIRequest": "true",
"Accept": "application/json",
}
async def list_conversations(
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> list[dict]:
url = urljoin(base_url.rstrip("/") + "/", OCS_ROOM_PATH)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.get(
url,
headers=_auth_headers(api_user, api_password),
)
if resp.status_code != 200:
logger.warning("Nextcloud Talk list conversations failed: HTTP %s", resp.status_code)
return []
data = resp.json()
return data.get("ocs", {}).get("data", [])
async def create_conversation(
base_url: str,
api_user: str,
api_password: str,
*,
room_type: int,
invite: str | None = None,
room_name: str | None = None,
timeout: float = DEFAULT_TIMEOUT,
) -> dict | None:
url = urljoin(base_url.rstrip("/") + "/", OCS_ROOM_PATH)
body: dict = {"roomType": room_type}
if invite:
body["invite"] = invite
if room_name:
body["roomName"] = room_name
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(
url,
json=body,
headers=_auth_headers(api_user, api_password),
)
if resp.status_code != 200:
logger.warning("Nextcloud Talk create conversation failed: HTTP %s", resp.status_code)
return None
data = resp.json()
return data.get("ocs", {}).get("data")
async def delete_conversation(
room_token: str,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
OCS_ROOM_TOKEN_PATH.format(token=room_token),
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.delete(
url,
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def rename_conversation(
room_token: str,
room_name: str,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
OCS_ROOM_TOKEN_PATH.format(token=room_token),
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.put(
url,
json={"roomName": room_name},
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def set_conversation_description(
room_token: str,
description: str,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/description",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.put(
url,
json={"description": description},
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def set_conversation_password(
room_token: str,
password: str,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/password",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.put(
url,
json={"password": password},
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def remove_conversation_password(
room_token: str,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/password",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.delete(
url,
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def set_read_only(
room_token: str,
state: int,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/read-only",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.put(
url,
json={"state": state},
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def set_message_expiration(
room_token: str,
seconds: int,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/message-expiration",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(
url,
json={"seconds": seconds},
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def set_favorite(
room_token: str,
base_url: str,
api_user: str,
api_password: str,
*,
favorite: bool = True,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
action = "favorite" if favorite else "unfavorite"
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/{action}",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(
url,
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200
async def set_notification_level(
room_token: str,
level: int,
base_url: str,
api_user: str,
api_password: str,
*,
timeout: float = DEFAULT_TIMEOUT,
) -> bool:
url = urljoin(
base_url.rstrip("/") + "/",
f"/ocs/v2.php/apps/spreed/api/v4/room/{room_token}/notify",
)
async with httpx.AsyncClient(timeout=timeout) as client:
resp = await client.post(
url,
json={"level": level},
headers=_auth_headers(api_user, api_password),
)
return resp.status_code == 200