ForcePilot/backend/package/yuxi/channel/extensions/feishu/tools/bitable.py
Kris 5e91bb9985 feat(feishu): 新增飞书渠道完整插件实现
新增飞书渠道插件,包含基础配置、事件处理、消息收发、工具调用、权限管理等完整功能模块,支持WebSocket长连接、卡片消息、流式响应、群管理、审批通知等能力
2026-05-21 10:46:42 +08:00

394 lines
13 KiB
Python

from __future__ import annotations
import logging
import re
from yuxi.channel.extensions.feishu.client import get_client
logger = logging.getLogger(__name__)
FEISHU_BITABLE_TOOLS = [
{
"name": "feishu_bitable_tables",
"description": "列出多维表格中的数据表",
"parameters": {"app_token": "多维表格 app_token"},
},
{
"name": "feishu_bitable_fields",
"description": "列出数据表的字段",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID"},
},
{
"name": "feishu_bitable_query",
"description": "查询多维表格记录",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID"},
},
{
"name": "feishu_bitable_create",
"description": "创建多维表格记录",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID", "fields": "字段值字典"},
},
{
"name": "feishu_bitable_update",
"description": "更新多维表格记录",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID", "record_id": "记录 ID", "fields": "字段值字典"},
},
{
"name": "feishu_bitable_delete",
"description": "删除多维表格记录",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID", "record_id": "记录 ID"},
},
{
"name": "feishu_bitable_batch_create",
"description": "批量创建多维表格记录",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID", "records": "记录列表"},
},
{
"name": "feishu_bitable_batch_update",
"description": "批量更新多维表格记录",
"parameters": {"app_token": "多维表格 token", "table_id": "数据表 ID", "records": "记录列表"},
},
]
BITABLE_URL_PATTERN = re.compile(
r"(?:feishu|lark)\.(?:cn|suite)\.com/(?:base|wiki/base)/([A-Za-z0-9]+)(?:\?.*table=([A-Za-z0-9]+))?"
)
def parse_bitable_url(url: str) -> dict | None:
match = BITABLE_URL_PATTERN.search(url)
if match:
result = {"app_token": match.group(1)}
if match.group(2):
result["table_id"] = match.group(2)
return result
return None
async def list_bitable_tables(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.ListAppTableRequest(app_token=app_token, page_size=50)
resp = await client.bitable.v1.app_table.list_async(req)
if resp.success() and resp.data:
items = getattr(resp.data, "items", []) or []
return {
"success": True,
"tables": [
{
"table_id": getattr(t, "table_id", ""),
"name": getattr(t, "name", ""),
"revision": getattr(t, "revision", 0),
}
for t in items
],
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu list_bitable_tables error")
return {"success": False, "error": "Internal error"}
async def list_bitable_fields(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.ListAppTableFieldRequest(
app_token=app_token,
table_id=table_id,
page_size=100,
)
resp = await client.bitable.v1.app_table_field.list_async(req)
if resp.success() and resp.data:
items = getattr(resp.data, "items", []) or []
return {
"success": True,
"fields": [
{
"field_id": getattr(f, "field_id", ""),
"field_name": getattr(f, "field_name", ""),
"type": getattr(f, "type", 0),
}
for f in items
],
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu list_bitable_fields error")
return {"success": False, "error": "Internal error"}
async def query_bitable_records(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
*,
page_size: int = 100,
page_token: str = "",
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.ListAppTableRecordRequest(
app_token=app_token,
table_id=table_id,
page_size=page_size,
page_token=page_token if page_token else None,
)
resp = await client.bitable.v1.app_table_record.list_async(req)
if resp.success() and resp.data:
items = getattr(resp.data, "items", []) or []
return {
"success": True,
"records": [
{
"record_id": getattr(r, "record_id", ""),
"fields": getattr(r, "fields", {}),
}
for r in items
],
"has_more": getattr(resp.data, "has_more", False),
"page_token": getattr(resp.data, "page_token", ""),
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu query_bitable_records error")
return {"success": False, "error": "Internal error"}
async def create_bitable_record(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
fields: dict,
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.CreateAppTableRecordRequest(
app_token=app_token,
table_id=table_id,
request_body=lark_oapi.bitable.v1.CreateAppTableRecordRequestBody(
fields=fields,
),
)
resp = await client.bitable.v1.app_table_record.create_async(req)
if resp.success() and resp.data:
return {
"success": True,
"record": {
"record_id": getattr(resp.data.record, "record_id", ""),
"fields": getattr(resp.data.record, "fields", {}),
},
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu create_bitable_record error")
return {"success": False, "error": "Internal error"}
async def update_bitable_record(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
record_id: str,
fields: dict,
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.UpdateAppTableRecordRequest(
app_token=app_token,
table_id=table_id,
record_id=record_id,
request_body=lark_oapi.bitable.v1.UpdateAppTableRecordRequestBody(
fields=fields,
),
)
resp = await client.bitable.v1.app_table_record.update_async(req)
if resp.success() and resp.data:
return {
"success": True,
"record": {
"record_id": getattr(resp.data.record, "record_id", ""),
"fields": getattr(resp.data.record, "fields", {}),
},
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu update_bitable_record error")
return {"success": False, "error": "Internal error"}
async def delete_bitable_record(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
record_id: str,
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.DeleteAppTableRecordRequest(
app_token=app_token,
table_id=table_id,
record_id=record_id,
)
resp = await client.bitable.v1.app_table_record.delete_async(req)
if resp.success():
return {"success": True}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu delete_bitable_record error")
return {"success": False, "error": "Internal error"}
async def batch_create_bitable_records(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
records: list[dict],
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.CreateAppTableRecordRequest(
app_token=app_token,
table_id=table_id,
request_body=lark_oapi.bitable.v1.BatchCreateAppTableRecordRequestBody(
records=[
lark_oapi.bitable.v1.AppTableRecord(fields=r.get("fields", {}))
for r in records
],
),
)
resp = await client.bitable.v1.app_table_record.batch_create_async(req)
if resp.success() and resp.data:
items = getattr(resp.data, "records", []) or []
return {
"success": True,
"records": [
{
"record_id": getattr(r, "record_id", ""),
"fields": getattr(r, "fields", {}),
}
for r in items
],
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu batch_create_bitable_records error")
return {"success": False, "error": "Internal error"}
async def batch_update_bitable_records(
app_id: str,
app_secret: str,
domain: str,
app_token: str,
table_id: str,
records: list[dict],
http_timeout_ms: int = 30000,
) -> dict:
try:
client = get_client(app_id, app_secret, domain, http_timeout_ms)
import lark_oapi
req = lark_oapi.bitable.v1.BatchUpdateAppTableRecordRequest(
app_token=app_token,
table_id=table_id,
request_body=lark_oapi.bitable.v1.BatchUpdateAppTableRecordRequestBody(
records=[
lark_oapi.bitable.v1.AppTableRecord(
record_id=r.get("record_id", ""),
fields=r.get("fields", {}),
)
for r in records
],
),
)
resp = await client.bitable.v1.app_table_record.batch_update_async(req)
if resp.success() and resp.data:
items = getattr(resp.data, "records", []) or []
return {
"success": True,
"records": [
{
"record_id": getattr(r, "record_id", ""),
"fields": getattr(r, "fields", {}),
}
for r in items
],
}
else:
return {"success": False, "error": getattr(resp, "msg", "")}
except ImportError:
return {"success": False, "error": "lark-oapi SDK not installed"}
except Exception:
logger.exception("Feishu batch_update_bitable_records error")
return {"success": False, "error": "Internal error"}