445 lines
14 KiB
Python
445 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
try:
|
|
import lark_oapi
|
|
|
|
HAS_LARK_SDK = True
|
|
except ImportError:
|
|
HAS_LARK_SDK = False
|
|
lark_oapi = None
|
|
|
|
|
|
_client_cache: dict[str, Any] = {}
|
|
_CACHE_TTL = 3600
|
|
MAX_CLIENTS = 32
|
|
|
|
|
|
def get_client(app_id: str, app_secret: str, domain: str = "feishu", http_timeout_ms: int = 30000) -> Any:
|
|
if not HAS_LARK_SDK:
|
|
raise ImportError("lark-oapi SDK is required. Install with: pip install lark-oapi")
|
|
|
|
domain_key = domain.lower()
|
|
if domain_key in ("lark", "larksuite"):
|
|
base_url = "https://open.larksuite.com"
|
|
elif domain_key.startswith("http"):
|
|
base_url = domain_key.rstrip("/")
|
|
else:
|
|
base_url = "https://open.feishu.cn"
|
|
|
|
cache_key = f"{app_id}:{base_url}"
|
|
entry = _client_cache.get(cache_key)
|
|
now = time.monotonic()
|
|
|
|
if entry and now - entry["ts"] < _CACHE_TTL and entry["secret"] == app_secret:
|
|
return entry["client"]
|
|
|
|
timeout_seconds = max(http_timeout_ms, 1000) / 1000.0
|
|
client = lark_oapi.Client.builder() \
|
|
.app_id(app_id) \
|
|
.app_secret(app_secret) \
|
|
.open_api_url(base_url) \
|
|
.timeout(timeout_seconds) \
|
|
.build()
|
|
|
|
_client_cache[cache_key] = {"client": client, "ts": now, "secret": app_secret}
|
|
|
|
if len(_client_cache) > MAX_CLIENTS:
|
|
oldest = min(_client_cache, key=lambda k: _client_cache[k]["ts"])
|
|
del _client_cache[oldest]
|
|
|
|
return client
|
|
|
|
|
|
def invalidate_client(app_id: str, domain: str = "feishu") -> None:
|
|
domain_key = domain.lower()
|
|
if domain_key in ("lark", "larksuite"):
|
|
base_url = "https://open.larksuite.com"
|
|
elif domain_key.startswith("http"):
|
|
base_url = domain_key.rstrip("/")
|
|
else:
|
|
base_url = "https://open.feishu.cn"
|
|
cache_key = f"{app_id}:{base_url}"
|
|
_client_cache.pop(cache_key, None)
|
|
|
|
|
|
def get_ws_client(app_id: str, app_secret: str, domain: str = "feishu") -> Any:
|
|
if not HAS_LARK_SDK:
|
|
raise ImportError("lark-oapi SDK is required")
|
|
|
|
domain_key = domain.lower()
|
|
if domain_key in ("lark", "larksuite"):
|
|
base_url = "https://open.larksuite.com"
|
|
elif domain_key.startswith("http"):
|
|
base_url = domain_key.rstrip("/")
|
|
else:
|
|
base_url = "https://open.feishu.cn"
|
|
|
|
return lark_oapi.ws.Client.builder() \
|
|
.app_id(app_id) \
|
|
.app_secret(app_secret) \
|
|
.open_api_url(base_url) \
|
|
.build()
|
|
|
|
|
|
async def get_bot_info(client: Any) -> dict:
|
|
resp = await client.im.v1.bot_info.get_async()
|
|
if resp.success():
|
|
data = resp.data
|
|
bot = getattr(data, "bot", {}) or {}
|
|
return {
|
|
"bot_name": getattr(bot, "app_name", "") if hasattr(bot, "app_name") else "",
|
|
"activate_status": getattr(bot, "activate_status", 0) if hasattr(bot, "activate_status") else 0,
|
|
"open_id": getattr(bot, "open_id", "") if hasattr(bot, "open_id") else "",
|
|
}
|
|
return {}
|
|
|
|
|
|
async def get_chat_info(client: Any, chat_id: str) -> dict | None:
|
|
resp = await client.im.v1.chat.get_async(lark_oapi.im.v1.GetChatRequest(chat_id=chat_id))
|
|
if resp.success() and resp.data:
|
|
return {
|
|
"name": getattr(resp.data, "name", ""),
|
|
"description": getattr(resp.data, "description", ""),
|
|
"chat_type": getattr(resp.data, "chat_type", ""),
|
|
"owner_id": getattr(resp.data, "owner_id", ""),
|
|
"owner_id_type": getattr(resp.data, "owner_id_type", ""),
|
|
}
|
|
return None
|
|
|
|
|
|
async def get_chat_members(client: Any, chat_id: str, page_token: str = "", page_size: int = 100) -> dict:
|
|
resp = await client.im.v1.chat_members.get_async(
|
|
lark_oapi.im.v1.GetChatMembersRequest(
|
|
chat_id=chat_id,
|
|
page_token=page_token,
|
|
page_size=page_size,
|
|
member_id_type="open_id",
|
|
)
|
|
)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
return {
|
|
"members": [
|
|
{
|
|
"member_id": getattr(m, "member_id", ""),
|
|
"name": getattr(m, "name", ""),
|
|
"member_type": getattr(m, "member_type", ""),
|
|
}
|
|
for m in items
|
|
],
|
|
"page_token": getattr(resp.data, "page_token", ""),
|
|
"has_more": getattr(resp.data, "has_more", False),
|
|
}
|
|
return {"members": [], "page_token": "", "has_more": False}
|
|
|
|
|
|
async def get_user_info(client: Any, user_id: str) -> dict | None:
|
|
resp = await client.contact.v3.user.get_async(
|
|
lark_oapi.contact.v3.GetUserRequest(
|
|
user_id=user_id,
|
|
user_id_type="open_id",
|
|
)
|
|
)
|
|
if resp.success() and resp.data and resp.data.user:
|
|
u = resp.data.user
|
|
return {
|
|
"open_id": getattr(u, "open_id", ""),
|
|
"name": getattr(u, "name", ""),
|
|
"avatar_url": getattr(u, "avatar_url", ""),
|
|
"email": getattr(u, "email", ""),
|
|
"mobile": getattr(u, "mobile", ""),
|
|
"department_ids": getattr(u, "department_ids", []) or [],
|
|
}
|
|
return None
|
|
|
|
|
|
async def get_message(client: Any, message_id: str) -> dict | None:
|
|
resp = await client.im.v1.message.get_async(
|
|
lark_oapi.im.v1.GetMessageRequest(message_id=message_id)
|
|
)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
if items:
|
|
item = items[0]
|
|
return {
|
|
"message_id": getattr(item, "message_id", ""),
|
|
"msg_type": getattr(item, "msg_type", ""),
|
|
"body": getattr(item, "body", {}),
|
|
"sender": {
|
|
"id": getattr(item, "sender", {}).get("id", ""),
|
|
"id_type": getattr(item, "sender", {}).get("id_type", ""),
|
|
},
|
|
"root_id": getattr(item, "root_id", ""),
|
|
"parent_id": getattr(item, "parent_id", ""),
|
|
"thread_id": getattr(item, "thread_id", ""),
|
|
}
|
|
return None
|
|
|
|
|
|
async def list_messages(
|
|
client: Any,
|
|
container_id_type: str = "chat",
|
|
container_id: str = "",
|
|
page_token: str = "",
|
|
page_size: int = 20,
|
|
start_time: str = "",
|
|
end_time: str = "",
|
|
sort_type: str = "ByCreateTimeDesc",
|
|
) -> dict:
|
|
req = lark_oapi.im.v1.ListMessageRequest(
|
|
container_id_type=container_id_type,
|
|
container_id=container_id,
|
|
page_token=page_token if page_token else None,
|
|
page_size=page_size,
|
|
start_time=start_time if start_time else None,
|
|
end_time=end_time if end_time else None,
|
|
sort_type=sort_type,
|
|
)
|
|
resp = await client.im.v1.message.list_async(req)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
return {
|
|
"messages": [
|
|
{
|
|
"message_id": getattr(m, "message_id", ""),
|
|
"msg_type": getattr(m, "msg_type", ""),
|
|
"root_id": getattr(m, "root_id", ""),
|
|
"parent_id": getattr(m, "parent_id", ""),
|
|
"thread_id": getattr(m, "thread_id", ""),
|
|
"create_time": getattr(m, "create_time", ""),
|
|
}
|
|
for m in items
|
|
],
|
|
"has_more": getattr(resp.data, "has_more", False),
|
|
"page_token": getattr(resp.data, "page_token", ""),
|
|
}
|
|
return {"messages": [], "has_more": False, "page_token": ""}
|
|
|
|
|
|
async def batch_get_users(client: Any, user_ids: list[str]) -> list[dict]:
|
|
if not user_ids:
|
|
return []
|
|
results = []
|
|
for user_id in user_ids:
|
|
info = await get_user_info(client, user_id)
|
|
if info:
|
|
results.append(info)
|
|
return results
|
|
|
|
|
|
async def create_chat(
|
|
client: Any,
|
|
name: str,
|
|
description: str = "",
|
|
user_ids: list[str] | None = None,
|
|
chat_type: str = "group",
|
|
) -> dict:
|
|
resp = await client.im.v1.chat.create_async(
|
|
lark_oapi.im.v1.CreateChatRequest(
|
|
request_body=lark_oapi.im.v1.CreateChatRequestBody(
|
|
name=name,
|
|
description=description,
|
|
user_ids=user_ids or [],
|
|
chat_type=chat_type,
|
|
),
|
|
)
|
|
)
|
|
if resp.success() and resp.data:
|
|
return {
|
|
"success": True,
|
|
"chat_id": getattr(resp.data, "chat_id", ""),
|
|
}
|
|
return {"success": False, "error": getattr(resp, "msg", "")}
|
|
|
|
|
|
async def update_chat(
|
|
client: Any,
|
|
chat_id: str,
|
|
name: str = "",
|
|
description: str = "",
|
|
) -> dict:
|
|
body = {}
|
|
if name:
|
|
body["name"] = name
|
|
if description:
|
|
body["description"] = description
|
|
resp = await client.im.v1.chat.update_async(
|
|
lark_oapi.im.v1.UpdateChatRequest(
|
|
chat_id=chat_id,
|
|
request_body=lark_oapi.im.v1.UpdateChatRequestBody(**body),
|
|
)
|
|
)
|
|
if resp.success():
|
|
return {"success": True}
|
|
return {"success": False, "error": getattr(resp, "msg", "")}
|
|
|
|
|
|
async def delete_chat(client: Any, chat_id: str) -> dict:
|
|
resp = await client.im.v1.chat.delete_async(
|
|
lark_oapi.im.v1.DeleteChatRequest(chat_id=chat_id)
|
|
)
|
|
if resp.success():
|
|
return {"success": True}
|
|
return {"success": False, "error": getattr(resp, "msg", "")}
|
|
|
|
|
|
async def add_chat_members(
|
|
client: Any,
|
|
chat_id: str,
|
|
member_ids: list[str],
|
|
) -> dict:
|
|
resp = await client.im.v1.chat_members.create_async(
|
|
lark_oapi.im.v1.CreateChatMembersRequest(
|
|
chat_id=chat_id,
|
|
member_id_type="open_id",
|
|
request_body=lark_oapi.im.v1.CreateChatMembersRequestBody(
|
|
id_list=member_ids,
|
|
),
|
|
)
|
|
)
|
|
if resp.success():
|
|
return {"success": True}
|
|
return {"success": False, "error": getattr(resp, "msg", "")}
|
|
|
|
|
|
async def remove_chat_members(
|
|
client: Any,
|
|
chat_id: str,
|
|
member_ids: list[str],
|
|
) -> dict:
|
|
resp = await client.im.v1.chat_members.delete_async(
|
|
lark_oapi.im.v1.DeleteChatMembersRequest(
|
|
chat_id=chat_id,
|
|
member_id_type="open_id",
|
|
request_body=lark_oapi.im.v1.DeleteChatMembersRequestBody(
|
|
id_list=member_ids,
|
|
),
|
|
)
|
|
)
|
|
if resp.success():
|
|
return {"success": True}
|
|
return {"success": False, "error": getattr(resp, "msg", "")}
|
|
|
|
|
|
async def list_user_chats(
|
|
client: Any,
|
|
user_id: str = "",
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
) -> dict:
|
|
req = lark_oapi.im.v1.ListChatRequest(
|
|
user_id_type="open_id" if user_id else None,
|
|
page_token=page_token if page_token else None,
|
|
page_size=page_size,
|
|
)
|
|
resp = await client.im.v1.chat.list_async(req)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
return {
|
|
"chats": [
|
|
{
|
|
"chat_id": getattr(c, "chat_id", ""),
|
|
"name": getattr(c, "name", ""),
|
|
"chat_type": getattr(c, "chat_type", ""),
|
|
}
|
|
for c in items
|
|
],
|
|
"has_more": getattr(resp.data, "has_more", False),
|
|
"page_token": getattr(resp.data, "page_token", ""),
|
|
}
|
|
return {"chats": [], "has_more": False, "page_token": ""}
|
|
|
|
|
|
async def search_chats(
|
|
client: Any,
|
|
query: str = "",
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
) -> dict:
|
|
req = lark_oapi.im.v1.SearchChatRequest(
|
|
query=query if query else None,
|
|
page_token=page_token if page_token else None,
|
|
page_size=page_size,
|
|
)
|
|
resp = await client.im.v1.chat.search_async(req)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
return {
|
|
"chats": [
|
|
{
|
|
"chat_id": getattr(c, "chat_id", ""),
|
|
"name": getattr(c, "name", ""),
|
|
"chat_type": getattr(c, "chat_type", ""),
|
|
}
|
|
for c in items
|
|
],
|
|
"has_more": getattr(resp.data, "has_more", False),
|
|
"page_token": getattr(resp.data, "page_token", ""),
|
|
}
|
|
return {"chats": [], "has_more": False, "page_token": ""}
|
|
|
|
|
|
async def list_departments(
|
|
client: Any,
|
|
parent_department_id: str = "0",
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
) -> dict:
|
|
resp = await client.contact.v3.department.list_async(
|
|
lark_oapi.contact.v3.ListDepartmentRequest(
|
|
parent_department_id=parent_department_id,
|
|
page_token=page_token if page_token else None,
|
|
page_size=page_size,
|
|
)
|
|
)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
return {
|
|
"departments": [
|
|
{
|
|
"department_id": getattr(d, "department_id", ""),
|
|
"name": getattr(d, "name", ""),
|
|
"parent_department_id": getattr(d, "parent_department_id", ""),
|
|
}
|
|
for d in items
|
|
],
|
|
"has_more": getattr(resp.data, "has_more", False),
|
|
"page_token": getattr(resp.data, "page_token", ""),
|
|
}
|
|
return {"departments": [], "has_more": False, "page_token": ""}
|
|
|
|
|
|
async def find_users_by_department(
|
|
client: Any,
|
|
department_id: str,
|
|
page_token: str = "",
|
|
page_size: int = 50,
|
|
) -> dict:
|
|
resp = await client.contact.v3.user.find_by_department_async(
|
|
lark_oapi.contact.v3.FindByDepartmentUserRequest(
|
|
department_id=department_id,
|
|
page_token=page_token if page_token else None,
|
|
page_size=page_size,
|
|
)
|
|
)
|
|
if resp.success() and resp.data:
|
|
items = getattr(resp.data, "items", []) or []
|
|
return {
|
|
"users": [
|
|
{
|
|
"open_id": getattr(u, "open_id", ""),
|
|
"name": getattr(u, "name", ""),
|
|
"email": getattr(u, "email", ""),
|
|
}
|
|
for u in items
|
|
],
|
|
"has_more": getattr(resp.data, "has_more", False),
|
|
"page_token": getattr(resp.data, "page_token", ""),
|
|
}
|
|
return {"users": [], "has_more": False, "page_token": ""} |