841 lines
31 KiB
Python
841 lines
31 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
from typing import Any
|
|
|
|
from yuxi.channel.extensions.feishu.client import get_client
|
|
from yuxi.channel.extensions.feishu.config import FeishuConfigAdapter
|
|
from yuxi.channel.extensions.feishu.format import text_to_post_message, chunk_post_content, markdown_to_feishu_post
|
|
from yuxi.channel.extensions.feishu.media import upload_image as _upload_media_image, upload_file as _upload_media_file
|
|
from yuxi.channel.extensions.feishu.types import FeishuAccount
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
REPLY_DEGRADE_ERRORS = frozenset({230011, 231003})
|
|
|
|
|
|
class FeishuOutboundAdapter:
|
|
delivery_mode = "direct"
|
|
|
|
def __init__(self):
|
|
self._config_adapter = FeishuConfigAdapter()
|
|
|
|
async def _resolve_account(self, account_id: str | None, config: dict | None = None) -> FeishuAccount:
|
|
aid = account_id or self._config_adapter.default_account_id(config or {})
|
|
return await self._config_adapter.resolve_account(aid, config)
|
|
|
|
async def send_text(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
body = self._build_message_body(target_id, content, reply_to_id=reply_to_id)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.CreateMessageRequest(
|
|
receive_id_type="chat_id",
|
|
request_body=lark_oapi.im.v1.CreateMessageRequestBody(**body),
|
|
)
|
|
resp = await client.im.v1.message.create_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu send_text error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def send_card(
|
|
self,
|
|
target_id: str,
|
|
card_json: dict,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
body: dict[str, Any] = {
|
|
"receive_id": target_id,
|
|
"msg_type": "interactive",
|
|
"content": json.dumps(card_json, ensure_ascii=False),
|
|
}
|
|
|
|
if reply_to_id:
|
|
body["root_id"] = reply_to_id
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.CreateMessageRequest(
|
|
receive_id_type="chat_id",
|
|
request_body=lark_oapi.im.v1.CreateMessageRequestBody(**body),
|
|
)
|
|
resp = await client.im.v1.message.create_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu send_card error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def send_markdown_card(
|
|
self,
|
|
target_id: str,
|
|
text: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
from yuxi.channel.extensions.feishu.card import build_markdown_card
|
|
return await self.send_card(target_id, build_markdown_card(text), account_id=account_id, config=config)
|
|
|
|
async def send_structured_card(
|
|
self,
|
|
target_id: str,
|
|
text: str,
|
|
*,
|
|
title: str | None = None,
|
|
template: str = "blue",
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
from yuxi.channel.extensions.feishu.card import build_structured_card
|
|
return await self.send_card(
|
|
target_id, build_structured_card(text, title=title, template=template),
|
|
account_id=account_id, config=config,
|
|
)
|
|
|
|
async def reply_message(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
post_content = text_to_post_message(content)
|
|
|
|
body: dict[str, Any] = {
|
|
"content": post_content,
|
|
"msg_type": "post",
|
|
}
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.ReplyMessageRequest(
|
|
message_id=message_id,
|
|
request_body=lark_oapi.im.v1.ReplyMessageRequestBody(**body),
|
|
)
|
|
resp = await client.im.v1.message.reply_async(req)
|
|
|
|
if resp.success():
|
|
return {"success": True, "msg_id": getattr(resp.data, "message_id", "")}
|
|
|
|
err_code = getattr(resp, "code", 0)
|
|
err_msg = getattr(resp, "msg", "")
|
|
|
|
if err_code in REPLY_DEGRADE_ERRORS:
|
|
logger.debug("Reply target not found, degrading to direct send: code=%d", err_code)
|
|
return await self.send_text(
|
|
target_id, content, account_id=account_id, config=config,
|
|
)
|
|
|
|
return {"success": False, "error": err_msg, "code": err_code}
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu reply_message error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def thread_reply(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
root_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
return await self.send_text(
|
|
target_id, content, reply_to_id=root_id,
|
|
account_id=account_id, config=config,
|
|
)
|
|
|
|
async def delete_message(
|
|
self,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.DeleteMessageRequest(message_id=message_id)
|
|
resp = await client.im.v1.message.delete_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu delete_message error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def forward_message(
|
|
self,
|
|
message_id: str,
|
|
target_chat_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.ForwardMessageRequest(
|
|
message_id=message_id,
|
|
request_body=lark_oapi.im.v1.ForwardMessageRequestBody(
|
|
receive_id=target_chat_id,
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.forward_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu forward_message error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def merge_forward(
|
|
self,
|
|
target_id: str,
|
|
message_ids: list[str],
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.MergeForwardMessageRequest(
|
|
receive_id_type="chat_id",
|
|
request_body=lark_oapi.im.v1.MergeForwardMessageRequestBody(
|
|
receive_id=target_id,
|
|
message_id_list=message_ids,
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.merge_forward_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu merge_forward error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def edit_message(
|
|
self,
|
|
message_id: str,
|
|
content: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
post_content = text_to_post_message(content)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.PatchMessageRequest(
|
|
message_id=message_id,
|
|
request_body=lark_oapi.im.v1.PatchMessageRequestBody(
|
|
content=post_content,
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.patch_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu edit_message error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def send_image(
|
|
self,
|
|
target_id: str,
|
|
image_key: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.CreateMessageRequest(
|
|
receive_id_type="chat_id",
|
|
request_body=lark_oapi.im.v1.CreateMessageRequestBody(
|
|
receive_id=target_id,
|
|
msg_type="image",
|
|
content=json.dumps({"image_key": image_key}),
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.create_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu send_image error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def send_image_by_path(
|
|
self,
|
|
target_id: str,
|
|
file_path: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
upload_result = await _upload_media_image(
|
|
account.app_id, account.app_secret, account.domain, file_path, account.http_timeout_ms
|
|
)
|
|
if not upload_result.get("success"):
|
|
return upload_result
|
|
|
|
image_key = upload_result.get("image_key", "")
|
|
return await self.send_image(target_id, image_key, account_id=account_id, config=config)
|
|
|
|
async def send_file(
|
|
self,
|
|
target_id: str,
|
|
file_key: str,
|
|
file_type: str = "stream",
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.CreateMessageRequest(
|
|
receive_id_type="chat_id",
|
|
request_body=lark_oapi.im.v1.CreateMessageRequestBody(
|
|
receive_id=target_id,
|
|
msg_type="file",
|
|
content=json.dumps({"file_key": file_key}),
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.create_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu send_file error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def send_file_by_path(
|
|
self,
|
|
target_id: str,
|
|
file_path: str,
|
|
*,
|
|
file_type: str = "stream",
|
|
filename: str = "",
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
upload_result = await _upload_media_file(
|
|
account.app_id, account.app_secret, account.domain,
|
|
file_path, file_type, filename, account.http_timeout_ms,
|
|
)
|
|
if not upload_result.get("success"):
|
|
return upload_result
|
|
|
|
file_key = upload_result.get("file_key", "")
|
|
return await self.send_file(target_id, file_key, file_type, account_id=account_id, config=config)
|
|
|
|
async def send_audio_by_path(
|
|
self,
|
|
target_id: str,
|
|
file_path: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
from yuxi.channel.extensions.feishu.media import detect_file_type
|
|
|
|
file_type = detect_file_type(file_path)
|
|
upload_result = await _upload_media_file(
|
|
account.app_id, account.app_secret, account.domain,
|
|
file_path, file_type, "", account.http_timeout_ms,
|
|
)
|
|
if not upload_result.get("success"):
|
|
return upload_result
|
|
|
|
file_key = upload_result.get("file_key", "")
|
|
return await self._send_media_by_key(target_id, file_key, "audio", account_id=account_id)
|
|
|
|
async def send_video_by_path(
|
|
self,
|
|
target_id: str,
|
|
file_path: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
from yuxi.channel.extensions.feishu.media import detect_file_type
|
|
|
|
file_type = detect_file_type(file_path)
|
|
upload_result = await _upload_media_file(
|
|
account.app_id, account.app_secret, account.domain,
|
|
file_path, file_type, "", account.http_timeout_ms,
|
|
)
|
|
if not upload_result.get("success"):
|
|
return upload_result
|
|
|
|
file_key = upload_result.get("file_key", "")
|
|
return await self._send_media_by_key(target_id, file_key, "media", account_id=account_id)
|
|
|
|
async def _send_media_by_key(
|
|
self,
|
|
target_id: str,
|
|
file_key: str,
|
|
msg_type: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, None)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
if msg_type == "audio":
|
|
content_json = json.dumps({"file_key": file_key})
|
|
elif msg_type == "media":
|
|
content_json = json.dumps({"file_key": file_key, "image_key": ""})
|
|
else:
|
|
return {"success": False, "error": f"Unsupported media type: {msg_type}"}
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.CreateMessageRequest(
|
|
receive_id_type="chat_id",
|
|
request_body=lark_oapi.im.v1.CreateMessageRequestBody(
|
|
receive_id=target_id,
|
|
msg_type=msg_type,
|
|
content=content_json,
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.create_async(req)
|
|
|
|
return self._parse_response(resp)
|
|
except ImportError:
|
|
return {"success": False, "error": "lark-oapi SDK not installed"}
|
|
except Exception:
|
|
logger.exception("Feishu _send_media_by_key error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def send_media(
|
|
self,
|
|
target_id: str,
|
|
media_url: str,
|
|
media_type: str,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
) -> None:
|
|
account_id = None
|
|
|
|
try:
|
|
await self._resolve_account(account_id, None)
|
|
except Exception:
|
|
logger.warning("Feishu send_media - could not resolve account")
|
|
return
|
|
|
|
if media_type.startswith("image"):
|
|
await self.send_image_by_path(target_id, media_url, account_id=account_id)
|
|
elif media_type.startswith("audio") or media_type.startswith("voice"):
|
|
await self.send_audio_by_path(target_id, media_url, account_id=account_id)
|
|
elif media_type.startswith("video"):
|
|
await self.send_video_by_path(target_id, media_url, account_id=account_id)
|
|
|
|
def chunker(self, text: str, limit: int, ctx: object | None = None) -> list[str]:
|
|
if limit >= 28000:
|
|
post = markdown_to_feishu_post(text)
|
|
elements = post.get("zh_cn", {}).get("content", [[]])
|
|
chunks = chunk_post_content(elements, limit=min(limit, 28000))
|
|
result = []
|
|
for chunk_elements in chunks:
|
|
chunk_post = {
|
|
"zh_cn": {
|
|
"title": "",
|
|
"content": chunk_elements,
|
|
}
|
|
}
|
|
result.append(json.dumps(chunk_post, ensure_ascii=False))
|
|
return result
|
|
|
|
if len(text) <= limit:
|
|
return [text]
|
|
|
|
chunks = []
|
|
remaining = text
|
|
while len(remaining) > limit:
|
|
split_at = remaining.rfind("\n", 0, limit)
|
|
if split_at == -1 or split_at < limit // 2:
|
|
split_at = limit
|
|
chunks.append(remaining[:split_at])
|
|
remaining = remaining[split_at:].lstrip("\n")
|
|
if remaining:
|
|
chunks.append(remaining)
|
|
return chunks
|
|
|
|
async def edit_card(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
card_content: dict,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> str | None:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return None
|
|
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
|
|
import lark_oapi
|
|
req = lark_oapi.im.v1.PatchMessageRequest(
|
|
message_id=message_id,
|
|
request_body=lark_oapi.im.v1.PatchMessageRequestBody(
|
|
content=json.dumps(card_content, ensure_ascii=False),
|
|
),
|
|
)
|
|
resp = await client.im.v1.message.patch_async(req)
|
|
if resp.success():
|
|
return message_id
|
|
logger.warning("Feishu edit_card failed: code=%s, msg=%s", resp.code, resp.msg)
|
|
return None
|
|
except ImportError:
|
|
return None
|
|
except Exception:
|
|
logger.exception("Feishu edit_card error")
|
|
return None
|
|
|
|
async def get_message(
|
|
self,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict | None:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return None
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import get_message as _get_msg
|
|
return await _get_msg(client, message_id)
|
|
except Exception:
|
|
logger.exception("Feishu get_message error")
|
|
return None
|
|
|
|
async def list_chat_messages(
|
|
self,
|
|
chat_id: str,
|
|
*,
|
|
page_token: str = "",
|
|
page_size: int = 20,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"messages": [], "has_more": False, "page_token": ""}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import list_messages as _list_msg
|
|
return await _list_msg(client, container_id=chat_id, page_token=page_token, page_size=page_size)
|
|
except Exception:
|
|
logger.exception("Feishu list_chat_messages error")
|
|
return {"messages": [], "has_more": False, "page_token": ""}
|
|
|
|
async def create_group_chat(
|
|
self,
|
|
name: str,
|
|
user_ids: list[str] | None = None,
|
|
description: str = "",
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import create_chat
|
|
return await create_chat(client, name, description, user_ids or [], "group")
|
|
except Exception:
|
|
logger.exception("Feishu create_group_chat error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def update_group_chat(
|
|
self,
|
|
chat_id: str,
|
|
name: str = "",
|
|
description: str = "",
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import update_chat
|
|
return await update_chat(client, chat_id, name, description)
|
|
except Exception:
|
|
logger.exception("Feishu update_group_chat error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def delete_group_chat(
|
|
self,
|
|
chat_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import delete_chat
|
|
return await delete_chat(client, chat_id)
|
|
except Exception:
|
|
logger.exception("Feishu delete_group_chat error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def add_group_members(
|
|
self,
|
|
chat_id: str,
|
|
member_ids: list[str],
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import add_chat_members
|
|
return await add_chat_members(client, chat_id, member_ids)
|
|
except Exception:
|
|
logger.exception("Feishu add_group_members error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def remove_group_members(
|
|
self,
|
|
chat_id: str,
|
|
member_ids: list[str],
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"success": False, "error": "Account not configured"}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import remove_chat_members
|
|
return await remove_chat_members(client, chat_id, member_ids)
|
|
except Exception:
|
|
logger.exception("Feishu remove_group_members error")
|
|
return {"success": False, "error": "Internal error"}
|
|
|
|
async def list_user_chats(
|
|
self,
|
|
user_id: str = "",
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"chats": [], "has_more": False, "page_token": ""}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import list_user_chats as _list_user_chats
|
|
return await _list_user_chats(client, user_id, page_token, page_size)
|
|
except Exception:
|
|
logger.exception("Feishu list_user_chats error")
|
|
return {"chats": [], "has_more": False, "page_token": ""}
|
|
|
|
async def search_chats(
|
|
self,
|
|
query: str = "",
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"chats": [], "has_more": False, "page_token": ""}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import search_chats as _search_chats
|
|
return await _search_chats(client, query, page_token, page_size)
|
|
except Exception:
|
|
logger.exception("Feishu search_chats error")
|
|
return {"chats": [], "has_more": False, "page_token": ""}
|
|
|
|
async def list_departments(
|
|
self,
|
|
parent_department_id: str = "0",
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"departments": [], "has_more": False, "page_token": ""}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import list_departments as _list_dept
|
|
return await _list_dept(client, parent_department_id, page_token, page_size)
|
|
except Exception:
|
|
logger.exception("Feishu list_departments error")
|
|
return {"departments": [], "has_more": False, "page_token": ""}
|
|
|
|
async def find_users_by_department(
|
|
self,
|
|
department_id: str,
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
*,
|
|
account_id: str | None = None,
|
|
config: dict | None = None,
|
|
) -> dict:
|
|
account = await self._resolve_account(account_id, config)
|
|
if not account.is_configured():
|
|
return {"users": [], "has_more": False, "page_token": ""}
|
|
try:
|
|
client = get_client(account.app_id, account.app_secret, account.domain, account.http_timeout_ms)
|
|
from yuxi.channel.extensions.feishu.client import find_users_by_department as _find_dept_users
|
|
return await _find_dept_users(client, department_id, page_token, page_size)
|
|
except Exception:
|
|
logger.exception("Feishu find_users_by_department error")
|
|
return {"users": [], "has_more": False, "page_token": ""}
|
|
|
|
def _build_message_body(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
) -> dict[str, Any]:
|
|
post_content = text_to_post_message(content)
|
|
body: dict[str, Any] = {
|
|
"receive_id": target_id,
|
|
"msg_type": "post",
|
|
"content": post_content,
|
|
}
|
|
if reply_to_id:
|
|
body["root_id"] = reply_to_id
|
|
return body
|
|
|
|
def _parse_response(self, resp) -> dict:
|
|
if resp.success():
|
|
msg_id = getattr(resp.data, "message_id", "")
|
|
return {"success": True, "msg_id": msg_id}
|
|
err_code = getattr(resp, "code", 0)
|
|
err_msg = getattr(resp, "msg", "")
|
|
|
|
if err_code in REPLY_DEGRADE_ERRORS:
|
|
logger.debug("Reply target not found, code=%d", err_code)
|
|
|
|
return {"success": False, "error": err_msg, "code": err_code}
|