新增 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: 类型定义
291 lines
7.0 KiB
Python
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
|