342 lines
10 KiB
Python
342 lines
10 KiB
Python
|
|
import asyncio
|
|||
|
|
|
|||
|
|
MAX_TEXT_BYTES = 7400
|
|||
|
|
|
|||
|
|
|
|||
|
|
class WebexOutbound:
|
|||
|
|
def __init__(self, api):
|
|||
|
|
self._api = api
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def build_mention(person_id: str = "", person_email: str = "", display_name: str = "") -> str:
|
|||
|
|
if person_email:
|
|||
|
|
return f"<@personEmail:{person_email}>"
|
|||
|
|
if person_id:
|
|||
|
|
return f"<@personId:{person_id}>"
|
|||
|
|
if display_name:
|
|||
|
|
return f"**{display_name}**"
|
|||
|
|
return ""
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def welcome_card_template(display_name: str = "", bot_name: str = "ForcePilot") -> dict:
|
|||
|
|
greeting = f"你好 {display_name}!" if display_name else "你好!"
|
|||
|
|
return {
|
|||
|
|
"type": "AdaptiveCard",
|
|||
|
|
"version": "1.3",
|
|||
|
|
"body": [
|
|||
|
|
{
|
|||
|
|
"type": "TextBlock",
|
|||
|
|
"text": f"👋 {greeting}",
|
|||
|
|
"size": "Large",
|
|||
|
|
"weight": "Bolder",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "TextBlock",
|
|||
|
|
"text": f"我是 **{bot_name}**,基于 Cisco Webex 的智能助手,有什么可以帮助你的?",
|
|||
|
|
"wrap": True,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "ColumnSet",
|
|||
|
|
"columns": [
|
|||
|
|
{
|
|||
|
|
"type": "Column",
|
|||
|
|
"width": "auto",
|
|||
|
|
"items": [
|
|||
|
|
{
|
|||
|
|
"type": "TextBlock",
|
|||
|
|
"text": "🚀 可以尝试问我:",
|
|||
|
|
"wrap": True,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "TextBlock",
|
|||
|
|
"text": "- 知识查询与解答\n- 数据分析与处理\n- 任务提醒与通知",
|
|||
|
|
"wrap": True,
|
|||
|
|
"spacing": "Small",
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def confirm_card_template(
|
|||
|
|
title: str, message: str, confirm_label: str = "确认", cancel_label: str = "取消"
|
|||
|
|
) -> dict:
|
|||
|
|
return {
|
|||
|
|
"type": "AdaptiveCard",
|
|||
|
|
"version": "1.3",
|
|||
|
|
"body": [
|
|||
|
|
{
|
|||
|
|
"type": "TextBlock",
|
|||
|
|
"text": title,
|
|||
|
|
"size": "Medium",
|
|||
|
|
"weight": "Bolder",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "TextBlock",
|
|||
|
|
"text": message,
|
|||
|
|
"wrap": True,
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
"actions": [
|
|||
|
|
{
|
|||
|
|
"type": "Action.Submit",
|
|||
|
|
"title": confirm_label,
|
|||
|
|
"data": {"action": "confirm"},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"type": "Action.Submit",
|
|||
|
|
"title": cancel_label,
|
|||
|
|
"style": "default",
|
|||
|
|
"data": {"action": "cancel"},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async def send_text(
|
|||
|
|
self,
|
|||
|
|
room_id: str,
|
|||
|
|
content: str,
|
|||
|
|
*,
|
|||
|
|
parent_id: str | None = None,
|
|||
|
|
as_markdown: bool = True,
|
|||
|
|
mentions: list[str] | None = None,
|
|||
|
|
to_person_id: str | None = None,
|
|||
|
|
to_person_email: str | None = None,
|
|||
|
|
) -> list[str]:
|
|||
|
|
text = content
|
|||
|
|
if mentions:
|
|||
|
|
text = " ".join(mentions) + " " + text
|
|||
|
|
|
|||
|
|
chunks = self._chunk_text(text)
|
|||
|
|
sent_ids = []
|
|||
|
|
|
|||
|
|
for i, chunk in enumerate(chunks):
|
|||
|
|
pid = parent_id if i == 0 else None
|
|||
|
|
kwargs = self._build_target_kwargs(room_id, to_person_id, to_person_email)
|
|||
|
|
if pid:
|
|||
|
|
kwargs["parentId"] = pid
|
|||
|
|
if as_markdown:
|
|||
|
|
kwargs["markdown"] = chunk
|
|||
|
|
else:
|
|||
|
|
kwargs["text"] = chunk
|
|||
|
|
|
|||
|
|
msg = self._api.messages.create(**kwargs)
|
|||
|
|
sent_ids.append(msg.id)
|
|||
|
|
|
|||
|
|
if i < len(chunks) - 1:
|
|||
|
|
await asyncio.sleep(0.1)
|
|||
|
|
|
|||
|
|
return sent_ids
|
|||
|
|
|
|||
|
|
async def send_html(
|
|||
|
|
self,
|
|||
|
|
room_id: str,
|
|||
|
|
content: str,
|
|||
|
|
*,
|
|||
|
|
parent_id: str | None = None,
|
|||
|
|
to_person_id: str | None = None,
|
|||
|
|
to_person_email: str | None = None,
|
|||
|
|
) -> str:
|
|||
|
|
kwargs = self._build_target_kwargs(room_id, to_person_id, to_person_email)
|
|||
|
|
if parent_id:
|
|||
|
|
kwargs["parentId"] = parent_id
|
|||
|
|
kwargs["html"] = content
|
|||
|
|
msg = self._api.messages.create(**kwargs)
|
|||
|
|
return msg.id
|
|||
|
|
|
|||
|
|
async def send_media(
|
|||
|
|
self,
|
|||
|
|
room_id: str,
|
|||
|
|
media_url: str,
|
|||
|
|
*,
|
|||
|
|
parent_id: str | None = None,
|
|||
|
|
to_person_id: str | None = None,
|
|||
|
|
to_person_email: str | None = None,
|
|||
|
|
) -> str:
|
|||
|
|
kwargs = self._build_target_kwargs(room_id, to_person_id, to_person_email)
|
|||
|
|
kwargs["files"] = [media_url]
|
|||
|
|
if parent_id:
|
|||
|
|
kwargs["parentId"] = parent_id
|
|||
|
|
msg = self._api.messages.create(**kwargs)
|
|||
|
|
return msg.id
|
|||
|
|
|
|||
|
|
async def send_card(
|
|||
|
|
self,
|
|||
|
|
room_id: str,
|
|||
|
|
card_json: dict,
|
|||
|
|
*,
|
|||
|
|
parent_id: str | None = None,
|
|||
|
|
to_person_id: str | None = None,
|
|||
|
|
to_person_email: str | None = None,
|
|||
|
|
) -> str:
|
|||
|
|
kwargs = self._build_target_kwargs(room_id, to_person_id, to_person_email)
|
|||
|
|
kwargs["attachments"] = [
|
|||
|
|
{
|
|||
|
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
|||
|
|
"content": card_json,
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
if parent_id:
|
|||
|
|
kwargs["parentId"] = parent_id
|
|||
|
|
msg = self._api.messages.create(**kwargs)
|
|||
|
|
return msg.id
|
|||
|
|
|
|||
|
|
async def edit_message(
|
|||
|
|
self,
|
|||
|
|
message_id: str,
|
|||
|
|
room_id: str,
|
|||
|
|
new_content: str,
|
|||
|
|
*,
|
|||
|
|
as_markdown: bool = True,
|
|||
|
|
) -> str:
|
|||
|
|
kwargs = {"roomId": room_id}
|
|||
|
|
if as_markdown:
|
|||
|
|
kwargs["markdown"] = new_content
|
|||
|
|
else:
|
|||
|
|
kwargs["text"] = new_content
|
|||
|
|
updated = self._api.messages.update(message_id, **kwargs)
|
|||
|
|
return updated.id
|
|||
|
|
|
|||
|
|
async def delete_message(self, message_id: str) -> bool:
|
|||
|
|
try:
|
|||
|
|
self._api.messages.delete(message_id)
|
|||
|
|
return True
|
|||
|
|
except Exception:
|
|||
|
|
return False
|
|||
|
|
|
|||
|
|
async def list_messages(
|
|||
|
|
self,
|
|||
|
|
room_id: str,
|
|||
|
|
*,
|
|||
|
|
parent_id: str | None = None,
|
|||
|
|
before: str | None = None,
|
|||
|
|
max_results: int = 50,
|
|||
|
|
) -> list[dict]:
|
|||
|
|
try:
|
|||
|
|
params = {"roomId": room_id, "max": min(max_results, 100)}
|
|||
|
|
if parent_id:
|
|||
|
|
params["parentId"] = parent_id
|
|||
|
|
if before:
|
|||
|
|
params["before"] = before
|
|||
|
|
|
|||
|
|
messages = list(self._api.messages.list(**params))
|
|||
|
|
return [
|
|||
|
|
{
|
|||
|
|
"id": m.id,
|
|||
|
|
"text": m.text,
|
|||
|
|
"person_id": m.personId,
|
|||
|
|
"person_email": m.personEmail,
|
|||
|
|
"created": m.created,
|
|||
|
|
"parent_id": m.parentId,
|
|||
|
|
}
|
|||
|
|
for m in messages
|
|||
|
|
]
|
|||
|
|
except Exception:
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
async def get_message(self, message_id: str) -> dict | None:
|
|||
|
|
try:
|
|||
|
|
m = self._api.messages.get(message_id)
|
|||
|
|
return {
|
|||
|
|
"id": m.id,
|
|||
|
|
"text": m.text,
|
|||
|
|
"markdown": m.markdown,
|
|||
|
|
"html": m.html,
|
|||
|
|
"person_id": m.personId,
|
|||
|
|
"person_email": m.personEmail,
|
|||
|
|
"room_id": m.roomId,
|
|||
|
|
"parent_id": m.parentId,
|
|||
|
|
"created": m.created,
|
|||
|
|
"files": m.files,
|
|||
|
|
}
|
|||
|
|
except Exception:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
async def get_room(self, room_id: str) -> dict | None:
|
|||
|
|
try:
|
|||
|
|
r = self._api.rooms.get(room_id)
|
|||
|
|
return {
|
|||
|
|
"id": r.id,
|
|||
|
|
"title": r.title,
|
|||
|
|
"type": r.type,
|
|||
|
|
"is_locked": getattr(r, "isLocked", False),
|
|||
|
|
"created": r.created,
|
|||
|
|
}
|
|||
|
|
except Exception:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
async def list_members(self, room_id: str) -> list[dict]:
|
|||
|
|
try:
|
|||
|
|
members = list(self._api.memberships.list(roomId=room_id))
|
|||
|
|
return [
|
|||
|
|
{
|
|||
|
|
"id": m.id,
|
|||
|
|
"person_id": m.personId,
|
|||
|
|
"person_email": m.personEmail,
|
|||
|
|
"person_display_name": m.personDisplayName,
|
|||
|
|
"room_id": m.roomId,
|
|||
|
|
"is_moderator": getattr(m, "isModerator", False),
|
|||
|
|
"created": m.created,
|
|||
|
|
}
|
|||
|
|
for m in members
|
|||
|
|
]
|
|||
|
|
except Exception:
|
|||
|
|
return []
|
|||
|
|
|
|||
|
|
async def get_member(self, membership_id: str) -> dict | None:
|
|||
|
|
try:
|
|||
|
|
m = self._api.memberships.get(membership_id)
|
|||
|
|
return {
|
|||
|
|
"id": m.id,
|
|||
|
|
"person_id": m.personId,
|
|||
|
|
"person_email": m.personEmail,
|
|||
|
|
"person_display_name": m.personDisplayName,
|
|||
|
|
"room_id": m.roomId,
|
|||
|
|
"is_moderator": getattr(m, "isModerator", False),
|
|||
|
|
"created": m.created,
|
|||
|
|
}
|
|||
|
|
except Exception:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def _chunk_text(self, content: str) -> list[str]:
|
|||
|
|
encoded = content.encode("utf-8")
|
|||
|
|
if len(encoded) <= MAX_TEXT_BYTES:
|
|||
|
|
return [content]
|
|||
|
|
|
|||
|
|
chunks = []
|
|||
|
|
pos = 0
|
|||
|
|
while pos < len(encoded):
|
|||
|
|
end = min(pos + MAX_TEXT_BYTES, len(encoded))
|
|||
|
|
chunk_bytes = encoded[pos:end]
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
chunk = chunk_bytes.decode("utf-8")
|
|||
|
|
except UnicodeDecodeError as e:
|
|||
|
|
cut = e.start
|
|||
|
|
chunk_bytes = encoded[pos : pos + cut]
|
|||
|
|
chunk = chunk_bytes.decode("utf-8")
|
|||
|
|
|
|||
|
|
chunks.append(chunk)
|
|||
|
|
|
|||
|
|
if chunk_bytes:
|
|||
|
|
pos += len(chunk_bytes)
|
|||
|
|
else:
|
|||
|
|
pos += MAX_TEXT_BYTES
|
|||
|
|
|
|||
|
|
return chunks
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def _build_target_kwargs(
|
|||
|
|
room_id: str,
|
|||
|
|
to_person_id: str | None = None,
|
|||
|
|
to_person_email: str | None = None,
|
|||
|
|
) -> dict:
|
|||
|
|
if to_person_id:
|
|||
|
|
return {"toPersonId": to_person_id}
|
|||
|
|
if to_person_email:
|
|||
|
|
return {"toPersonEmail": to_person_email}
|
|||
|
|
return {"roomId": room_id}
|