394 lines
13 KiB
Python
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"} |