feat(feishu): add feishu directory and calendar tool modules

新增飞书通讯录用户搜索以及日历相关的工具接口,包括查询日历列表、获取日历事件、查询日历事件列表的功能,适配飞书开放SDK的调用逻辑,统一处理了参数校验和异常情况。
This commit is contained in:
Kris 2026-05-13 16:08:39 +08:00
parent 69b7139863
commit 21c52c87ad
2 changed files with 227 additions and 0 deletions

View File

@ -0,0 +1,168 @@
from __future__ import annotations
from typing import Any
from ._validate import validate_params
try:
import lark_oapi
HAS_LARK_SDK = True
except ImportError:
HAS_LARK_SDK = False
lark_oapi = None # type: ignore
@validate_params
async def list_calendar_events(
client: Any,
calendar_id: str = "primary",
start_time: str = "",
end_time: str = "",
page_size: int = 50,
page_token: str = "",
) -> dict[str, Any]:
if not HAS_LARK_SDK or not client:
return {"events": [], "has_more": False, "page_token": ""}
try:
builder = (
lark_oapi.api.calendar.v4.ListCalendarEventRequest.builder()
.calendar_id(calendar_id)
.page_size(min(page_size, 100))
)
if start_time:
builder.start_time(start_time)
if end_time:
builder.end_time(end_time)
if page_token:
builder.page_token(page_token)
request = builder.build()
resp = await client.calendar.v4.calendar_event.list(request)
if not resp.success():
raise RuntimeError(f"\u67e5\u8be2\u65e5\u5386\u4e8b\u4ef6\u5931\u8d25: {resp.msg}")
data = resp.data if hasattr(resp, "data") else {}
items = data.get("items", [])
events = []
for item in items:
events.append(
{
"event_id": item.get("event_id", ""),
"summary": item.get("summary", ""),
"description": item.get("description", ""),
"start_time": _format_event_time(item.get("start_time")),
"end_time": _format_event_time(item.get("end_time")),
"location": item.get("location", ""),
"organizer": _get_display_name(item.get("organizer")),
"status": item.get("status", ""),
}
)
return {
"events": events,
"has_more": data.get("has_more", False),
"page_token": data.get("page_token", ""),
"sync_token": data.get("sync_token", ""),
}
except Exception as e:
raise RuntimeError(f"\u67e5\u8be2\u65e5\u5386\u4e8b\u4ef6\u5931\u8d25: {e}") from e
@validate_params
async def get_calendar_event(
client: Any,
calendar_id: str,
event_id: str,
) -> dict[str, Any]:
if not HAS_LARK_SDK or not client:
raise RuntimeError("SDK \u4e0d\u53ef\u7528")
try:
request = (
lark_oapi.api.calendar.v4.GetCalendarEventRequest.builder()
.calendar_id(calendar_id)
.event_id(event_id)
.build()
)
resp = await client.calendar.v4.calendar_event.get(request)
if not resp.success():
raise RuntimeError(f"\u83b7\u53d6\u65e5\u5386\u4e8b\u4ef6\u5931\u8d25: {resp.msg}")
data = resp.data if hasattr(resp, "data") else {}
item = data.get("event", data)
return {
"event_id": item.get("event_id", ""),
"summary": item.get("summary", ""),
"description": item.get("description", ""),
"start_time": _format_event_time(item.get("start_time")),
"end_time": _format_event_time(item.get("end_time")),
"location": item.get("location", ""),
"organizer": _get_display_name(item.get("organizer")),
"attendees": [
_get_display_name(a) for a in (item.get("attendees") or [])
],
"status": item.get("status", ""),
}
except Exception as e:
raise RuntimeError(f"\u83b7\u53d6\u65e5\u5386\u4e8b\u4ef6\u5931\u8d25: {e}") from e
@validate_params
async def list_calendars(
client: Any,
page_size: int = 50,
page_token: str = "",
) -> dict[str, Any]:
if not HAS_LARK_SDK or not client:
return {"calendars": [], "has_more": False, "page_token": ""}
try:
builder = (
lark_oapi.api.calendar.v4.ListCalendarRequest.builder()
.page_size(min(page_size, 100))
)
if page_token:
builder.page_token(page_token)
request = builder.build()
resp = await client.calendar.v4.calendar.list(request)
if not resp.success():
raise RuntimeError(f"\u67e5\u8be2\u65e5\u5386\u5217\u8868\u5931\u8d25: {resp.msg}")
data = resp.data if hasattr(resp, "data") else {}
items = data.get("calendar_list", [])
calendars = []
for item in items:
calendars.append(
{
"calendar_id": item.get("calendar_id", ""),
"summary": item.get("summary", ""),
"description": item.get("description", ""),
}
)
return {
"calendars": calendars,
"has_more": data.get("has_more", False),
"page_token": data.get("page_token", ""),
}
except Exception as e:
raise RuntimeError(f"\u67e5\u8be2\u65e5\u5386\u5217\u8868\u5931\u8d25: {e}") from e
def _format_event_time(ts: Any) -> str:
if ts is None:
return ""
if isinstance(ts, dict):
return ts.get("date_time") or ts.get("date") or ""
return str(ts)
def _get_display_name(obj: Any) -> str:
if obj is None:
return ""
if isinstance(obj, dict):
return obj.get("display_name") or obj.get("name") or ""
return str(obj)

View File

@ -0,0 +1,59 @@
from __future__ import annotations
from typing import Any
from ._validate import validate_params
try:
import lark_oapi
HAS_LARK_SDK = True
except ImportError:
HAS_LARK_SDK = False
lark_oapi = None # type: ignore
@validate_params
async def search_users(
client: Any,
query: str,
page_size: int = 20,
page_token: str = "",
) -> dict[str, Any]:
if not HAS_LARK_SDK or not client:
return {"users": [], "has_more": False, "page_token": ""}
try:
request = (
lark_oapi.api.contact.v3.SearchUserRequest.builder()
.query(query)
.page_size(min(page_size, 100))
.page_token(page_token)
.build()
)
resp = await client.contact.v3.user.search(request)
if not resp.success():
raise RuntimeError(f"搜索用户失败: {resp.msg}")
data = resp.data if hasattr(resp, "data") else {}
items = data.get("items", [])
users = [
{
"open_id": item.get("open_id", ""),
"user_id": item.get("user_id", ""),
"name": item.get("name", ""),
"email": item.get("email", ""),
"avatar_url": (
item.get("avatar", {}).get("avatar_240", "") if isinstance(item.get("avatar"), dict) else ""
),
}
for item in items
]
return {
"users": users,
"has_more": data.get("has_more", False),
"page_token": data.get("page_token", ""),
}
except Exception as e:
raise RuntimeError(f"搜索用户失败: {e}") from e