feishu_api_utils.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. import json
  2. from typing import Any, Optional, cast
  3. import httpx
  4. from core.tools.errors import ToolProviderCredentialValidationError
  5. from extensions.ext_redis import redis_client
  6. def auth(credentials):
  7. app_id = credentials.get("app_id")
  8. app_secret = credentials.get("app_secret")
  9. if not app_id or not app_secret:
  10. raise ToolProviderCredentialValidationError("app_id and app_secret is required")
  11. try:
  12. assert FeishuRequest(app_id, app_secret).tenant_access_token is not None
  13. except Exception as e:
  14. raise ToolProviderCredentialValidationError(str(e))
  15. def convert_add_records(json_str):
  16. try:
  17. data = json.loads(json_str)
  18. if not isinstance(data, list):
  19. raise ValueError("Parsed data must be a list")
  20. converted_data = [{"fields": json.dumps(item, ensure_ascii=False)} for item in data]
  21. return converted_data
  22. except json.JSONDecodeError:
  23. raise ValueError("The input string is not valid JSON")
  24. except Exception as e:
  25. raise ValueError(f"An error occurred while processing the data: {e}")
  26. def convert_update_records(json_str):
  27. try:
  28. data = json.loads(json_str)
  29. if not isinstance(data, list):
  30. raise ValueError("Parsed data must be a list")
  31. converted_data = [
  32. {"fields": json.dumps(record["fields"], ensure_ascii=False), "record_id": record["record_id"]}
  33. for record in data
  34. if "fields" in record and "record_id" in record
  35. ]
  36. if len(converted_data) != len(data):
  37. raise ValueError("Each record must contain 'fields' and 'record_id'")
  38. return converted_data
  39. except json.JSONDecodeError:
  40. raise ValueError("The input string is not valid JSON")
  41. except Exception as e:
  42. raise ValueError(f"An error occurred while processing the data: {e}")
  43. class FeishuRequest:
  44. API_BASE_URL = "https://lark-plugin-api.solutionsuite.cn/lark-plugin"
  45. def __init__(self, app_id: str, app_secret: str):
  46. self.app_id = app_id
  47. self.app_secret = app_secret
  48. @property
  49. def tenant_access_token(self):
  50. feishu_tenant_access_token = f"tools:{self.app_id}:feishu_tenant_access_token"
  51. if redis_client.exists(feishu_tenant_access_token):
  52. return redis_client.get(feishu_tenant_access_token).decode()
  53. res = self.get_tenant_access_token(self.app_id, self.app_secret)
  54. redis_client.setex(feishu_tenant_access_token, res.get("expire"), res.get("tenant_access_token"))
  55. return res.get("tenant_access_token")
  56. def _send_request(
  57. self,
  58. url: str,
  59. method: str = "post",
  60. require_token: bool = True,
  61. payload: Optional[dict] = None,
  62. params: Optional[dict] = None,
  63. ):
  64. headers = {
  65. "Content-Type": "application/json",
  66. "user-agent": "Dify",
  67. }
  68. if require_token:
  69. headers["tenant-access-token"] = f"{self.tenant_access_token}"
  70. res = httpx.request(method=method, url=url, headers=headers, json=payload, params=params, timeout=30).json()
  71. if res.get("code") != 0:
  72. raise Exception(res)
  73. return res
  74. def get_tenant_access_token(self, app_id: str, app_secret: str) -> dict:
  75. """
  76. API url: https://open.feishu.cn/document/server-docs/authentication-management/access-token/tenant_access_token_internal
  77. Example Response:
  78. {
  79. "code": 0,
  80. "msg": "ok",
  81. "tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3",
  82. "expire": 7200
  83. }
  84. """
  85. url = f"{self.API_BASE_URL}/access_token/get_tenant_access_token"
  86. payload = {"app_id": app_id, "app_secret": app_secret}
  87. res: dict = self._send_request(url, require_token=False, payload=payload)
  88. return res
  89. def create_document(self, title: str, content: str, folder_token: str) -> dict:
  90. """
  91. API url: https://open.larkoffice.com/document/server-docs/docs/docs/docx-v1/document/create
  92. Example Response:
  93. {
  94. "data": {
  95. "title": "title",
  96. "url": "https://svi136aogf123.feishu.cn/docx/VWbvd4fEdoW0WSxaY1McQTz8n7d",
  97. "type": "docx",
  98. "token": "VWbvd4fEdoW0WSxaY1McQTz8n7d"
  99. },
  100. "log_id": "021721281231575fdbddc0200ff00060a9258ec0000103df61b5d",
  101. "code": 0,
  102. "msg": "创建飞书文档成功,请查看"
  103. }
  104. """
  105. url = f"{self.API_BASE_URL}/document/create_document"
  106. payload = {
  107. "title": title,
  108. "content": content,
  109. "folder_token": folder_token,
  110. }
  111. res: dict = self._send_request(url, payload=payload)
  112. if "data" in res:
  113. data: dict = res.get("data", {})
  114. return data
  115. return res
  116. def write_document(self, document_id: str, content: str, position: str = "end") -> dict:
  117. url = f"{self.API_BASE_URL}/document/write_document"
  118. payload = {"document_id": document_id, "content": content, "position": position}
  119. res: dict = self._send_request(url, payload=payload)
  120. return res
  121. def get_document_content(self, document_id: str, mode: str = "markdown", lang: str = "0") -> str:
  122. """
  123. API url: https://open.larkoffice.com/document/server-docs/docs/docs/docx-v1/document/raw_content
  124. Example Response:
  125. {
  126. "code": 0,
  127. "msg": "success",
  128. "data": {
  129. "content": "云文档\n多人实时协同,插入一切元素。不仅是在线文档,更是强大的创作和互动工具\n云文档:专为协作而生\n"
  130. }
  131. }
  132. """ # noqa: E501
  133. params = {
  134. "document_id": document_id,
  135. "mode": mode,
  136. "lang": lang,
  137. }
  138. url = f"{self.API_BASE_URL}/document/get_document_content"
  139. res: dict = self._send_request(url, method="GET", params=params)
  140. if "data" in res:
  141. return cast(str, res.get("data", {}).get("content"))
  142. return ""
  143. def list_document_blocks(
  144. self, document_id: str, page_token: str, user_id_type: str = "open_id", page_size: int = 500
  145. ) -> dict:
  146. """
  147. API url: https://open.larkoffice.com/document/server-docs/docs/docs/docx-v1/document/list
  148. """
  149. params = {
  150. "user_id_type": user_id_type,
  151. "document_id": document_id,
  152. "page_size": page_size,
  153. "page_token": page_token,
  154. }
  155. url = f"{self.API_BASE_URL}/document/list_document_blocks"
  156. res: dict = self._send_request(url, method="GET", params=params)
  157. if "data" in res:
  158. data: dict = res.get("data", {})
  159. return data
  160. return res
  161. def send_bot_message(self, receive_id_type: str, receive_id: str, msg_type: str, content: str) -> dict:
  162. """
  163. API url: https://open.larkoffice.com/document/server-docs/im-v1/message/create
  164. """
  165. url = f"{self.API_BASE_URL}/message/send_bot_message"
  166. params = {
  167. "receive_id_type": receive_id_type,
  168. }
  169. payload = {
  170. "receive_id": receive_id,
  171. "msg_type": msg_type,
  172. "content": content.strip('"').replace(r"\"", '"').replace(r"\\", "\\"),
  173. }
  174. res: dict = self._send_request(url, params=params, payload=payload)
  175. if "data" in res:
  176. data: dict = res.get("data", {})
  177. return data
  178. return res
  179. def send_webhook_message(self, webhook: str, msg_type: str, content: str) -> dict:
  180. url = f"{self.API_BASE_URL}/message/send_webhook_message"
  181. payload = {
  182. "webhook": webhook,
  183. "msg_type": msg_type,
  184. "content": content.strip('"').replace(r"\"", '"').replace(r"\\", "\\"),
  185. }
  186. res: dict = self._send_request(url, require_token=False, payload=payload)
  187. return res
  188. def get_chat_messages(
  189. self,
  190. container_id: str,
  191. start_time: str,
  192. end_time: str,
  193. page_token: str,
  194. sort_type: str = "ByCreateTimeAsc",
  195. page_size: int = 20,
  196. ) -> dict:
  197. """
  198. API url: https://open.larkoffice.com/document/server-docs/im-v1/message/list
  199. """
  200. url = f"{self.API_BASE_URL}/message/get_chat_messages"
  201. params = {
  202. "container_id": container_id,
  203. "start_time": start_time,
  204. "end_time": end_time,
  205. "sort_type": sort_type,
  206. "page_token": page_token,
  207. "page_size": page_size,
  208. }
  209. res: dict = self._send_request(url, method="GET", params=params)
  210. if "data" in res:
  211. data: dict = res.get("data", {})
  212. return data
  213. return res
  214. def get_thread_messages(
  215. self, container_id: str, page_token: str, sort_type: str = "ByCreateTimeAsc", page_size: int = 20
  216. ) -> dict:
  217. """
  218. API url: https://open.larkoffice.com/document/server-docs/im-v1/message/list
  219. """
  220. url = f"{self.API_BASE_URL}/message/get_thread_messages"
  221. params = {
  222. "container_id": container_id,
  223. "sort_type": sort_type,
  224. "page_token": page_token,
  225. "page_size": page_size,
  226. }
  227. res: dict = self._send_request(url, method="GET", params=params)
  228. if "data" in res:
  229. data: dict = res.get("data", {})
  230. return data
  231. return res
  232. def create_task(self, summary: str, start_time: str, end_time: str, completed_time: str, description: str) -> dict:
  233. # 创建任务
  234. url = f"{self.API_BASE_URL}/task/create_task"
  235. payload = {
  236. "summary": summary,
  237. "start_time": start_time,
  238. "end_time": end_time,
  239. "completed_at": completed_time,
  240. "description": description,
  241. }
  242. res: dict = self._send_request(url, payload=payload)
  243. if "data" in res:
  244. data: dict = res.get("data", {})
  245. return data
  246. return res
  247. def update_task(
  248. self, task_guid: str, summary: str, start_time: str, end_time: str, completed_time: str, description: str
  249. ) -> dict:
  250. # 更新任务
  251. url = f"{self.API_BASE_URL}/task/update_task"
  252. payload = {
  253. "task_guid": task_guid,
  254. "summary": summary,
  255. "start_time": start_time,
  256. "end_time": end_time,
  257. "completed_time": completed_time,
  258. "description": description,
  259. }
  260. res: dict = self._send_request(url, method="PATCH", payload=payload)
  261. if "data" in res:
  262. data: dict = res.get("data", {})
  263. return data
  264. return res
  265. def delete_task(self, task_guid: str) -> dict:
  266. # 删除任务
  267. url = f"{self.API_BASE_URL}/task/delete_task"
  268. payload = {
  269. "task_guid": task_guid,
  270. }
  271. res: dict = self._send_request(url, method="DELETE", payload=payload)
  272. return res
  273. def add_members(self, task_guid: str, member_phone_or_email: str, member_role: str) -> dict:
  274. # 删除任务
  275. url = f"{self.API_BASE_URL}/task/add_members"
  276. payload = {
  277. "task_guid": task_guid,
  278. "member_phone_or_email": member_phone_or_email,
  279. "member_role": member_role,
  280. }
  281. res: dict = self._send_request(url, payload=payload)
  282. return res
  283. def get_wiki_nodes(self, space_id: str, parent_node_token: str, page_token: str, page_size: int = 20) -> dict:
  284. # 获取知识库全部子节点列表
  285. url = f"{self.API_BASE_URL}/wiki/get_wiki_nodes"
  286. payload = {
  287. "space_id": space_id,
  288. "parent_node_token": parent_node_token,
  289. "page_token": page_token,
  290. "page_size": page_size,
  291. }
  292. res: dict = self._send_request(url, payload=payload)
  293. if "data" in res:
  294. data: dict = res.get("data", {})
  295. return data
  296. return res
  297. def get_primary_calendar(self, user_id_type: str = "open_id") -> dict:
  298. url = f"{self.API_BASE_URL}/calendar/get_primary_calendar"
  299. params = {
  300. "user_id_type": user_id_type,
  301. }
  302. res: dict = self._send_request(url, method="GET", params=params)
  303. if "data" in res:
  304. data: dict = res.get("data", {})
  305. return data
  306. return res
  307. def create_event(
  308. self,
  309. summary: str,
  310. description: str,
  311. start_time: str,
  312. end_time: str,
  313. attendee_ability: str,
  314. need_notification: bool = True,
  315. auto_record: bool = False,
  316. ) -> dict:
  317. url = f"{self.API_BASE_URL}/calendar/create_event"
  318. payload = {
  319. "summary": summary,
  320. "description": description,
  321. "need_notification": need_notification,
  322. "start_time": start_time,
  323. "end_time": end_time,
  324. "auto_record": auto_record,
  325. "attendee_ability": attendee_ability,
  326. }
  327. res: dict = self._send_request(url, payload=payload)
  328. if "data" in res:
  329. data: dict = res.get("data", {})
  330. return data
  331. return res
  332. def update_event(
  333. self,
  334. event_id: str,
  335. summary: str,
  336. description: str,
  337. need_notification: bool,
  338. start_time: str,
  339. end_time: str,
  340. auto_record: bool,
  341. ) -> dict:
  342. url = f"{self.API_BASE_URL}/calendar/update_event/{event_id}"
  343. payload: dict[str, Any] = {}
  344. if summary:
  345. payload["summary"] = summary
  346. if description:
  347. payload["description"] = description
  348. if start_time:
  349. payload["start_time"] = start_time
  350. if end_time:
  351. payload["end_time"] = end_time
  352. if need_notification:
  353. payload["need_notification"] = need_notification
  354. if auto_record:
  355. payload["auto_record"] = auto_record
  356. res: dict = self._send_request(url, method="PATCH", payload=payload)
  357. return res
  358. def delete_event(self, event_id: str, need_notification: bool = True) -> dict:
  359. url = f"{self.API_BASE_URL}/calendar/delete_event/{event_id}"
  360. params = {
  361. "need_notification": need_notification,
  362. }
  363. res: dict = self._send_request(url, method="DELETE", params=params)
  364. return res
  365. def list_events(self, start_time: str, end_time: str, page_token: str, page_size: int = 50) -> dict:
  366. url = f"{self.API_BASE_URL}/calendar/list_events"
  367. params = {
  368. "start_time": start_time,
  369. "end_time": end_time,
  370. "page_token": page_token,
  371. "page_size": page_size,
  372. }
  373. res: dict = self._send_request(url, method="GET", params=params)
  374. if "data" in res:
  375. data: dict = res.get("data", {})
  376. return data
  377. return res
  378. def search_events(
  379. self,
  380. query: str,
  381. start_time: str,
  382. end_time: str,
  383. page_token: str,
  384. user_id_type: str = "open_id",
  385. page_size: int = 20,
  386. ) -> dict:
  387. url = f"{self.API_BASE_URL}/calendar/search_events"
  388. payload = {
  389. "query": query,
  390. "start_time": start_time,
  391. "end_time": end_time,
  392. "page_token": page_token,
  393. "user_id_type": user_id_type,
  394. "page_size": page_size,
  395. }
  396. res: dict = self._send_request(url, payload=payload)
  397. if "data" in res:
  398. data: dict = res.get("data", {})
  399. return data
  400. return res
  401. def add_event_attendees(self, event_id: str, attendee_phone_or_email: str, need_notification: bool = True) -> dict:
  402. # 参加日程参会人
  403. url = f"{self.API_BASE_URL}/calendar/add_event_attendees"
  404. payload = {
  405. "event_id": event_id,
  406. "attendee_phone_or_email": attendee_phone_or_email,
  407. "need_notification": need_notification,
  408. }
  409. res: dict = self._send_request(url, payload=payload)
  410. if "data" in res:
  411. data: dict = res.get("data", {})
  412. return data
  413. return res
  414. def create_spreadsheet(
  415. self,
  416. title: str,
  417. folder_token: str,
  418. ) -> dict:
  419. # 创建电子表格
  420. url = f"{self.API_BASE_URL}/spreadsheet/create_spreadsheet"
  421. payload = {
  422. "title": title,
  423. "folder_token": folder_token,
  424. }
  425. res: dict = self._send_request(url, payload=payload)
  426. if "data" in res:
  427. data: dict = res.get("data", {})
  428. return data
  429. return res
  430. def get_spreadsheet(
  431. self,
  432. spreadsheet_token: str,
  433. user_id_type: str = "open_id",
  434. ) -> dict:
  435. # 获取电子表格信息
  436. url = f"{self.API_BASE_URL}/spreadsheet/get_spreadsheet"
  437. params = {
  438. "spreadsheet_token": spreadsheet_token,
  439. "user_id_type": user_id_type,
  440. }
  441. res: dict = self._send_request(url, method="GET", params=params)
  442. if "data" in res:
  443. data: dict = res.get("data", {})
  444. return data
  445. return res
  446. def list_spreadsheet_sheets(
  447. self,
  448. spreadsheet_token: str,
  449. ) -> dict:
  450. # 列出电子表格的所有工作表
  451. url = f"{self.API_BASE_URL}/spreadsheet/list_spreadsheet_sheets"
  452. params = {
  453. "spreadsheet_token": spreadsheet_token,
  454. }
  455. res: dict = self._send_request(url, method="GET", params=params)
  456. if "data" in res:
  457. data: dict = res.get("data", {})
  458. return data
  459. return res
  460. def add_rows(
  461. self,
  462. spreadsheet_token: str,
  463. sheet_id: str,
  464. sheet_name: str,
  465. length: int,
  466. values: str,
  467. ) -> dict:
  468. # 增加行,在工作表最后添加
  469. url = f"{self.API_BASE_URL}/spreadsheet/add_rows"
  470. payload = {
  471. "spreadsheet_token": spreadsheet_token,
  472. "sheet_id": sheet_id,
  473. "sheet_name": sheet_name,
  474. "length": length,
  475. "values": values,
  476. }
  477. res: dict = self._send_request(url, payload=payload)
  478. if "data" in res:
  479. data: dict = res.get("data", {})
  480. return data
  481. return res
  482. def add_cols(
  483. self,
  484. spreadsheet_token: str,
  485. sheet_id: str,
  486. sheet_name: str,
  487. length: int,
  488. values: str,
  489. ) -> dict:
  490. # 增加列,在工作表最后添加
  491. url = f"{self.API_BASE_URL}/spreadsheet/add_cols"
  492. payload = {
  493. "spreadsheet_token": spreadsheet_token,
  494. "sheet_id": sheet_id,
  495. "sheet_name": sheet_name,
  496. "length": length,
  497. "values": values,
  498. }
  499. res: dict = self._send_request(url, payload=payload)
  500. if "data" in res:
  501. data: dict = res.get("data", {})
  502. return data
  503. return res
  504. def read_rows(
  505. self,
  506. spreadsheet_token: str,
  507. sheet_id: str,
  508. sheet_name: str,
  509. start_row: int,
  510. num_rows: int,
  511. user_id_type: str = "open_id",
  512. ) -> dict:
  513. # 读取工作表行数据
  514. url = f"{self.API_BASE_URL}/spreadsheet/read_rows"
  515. params = {
  516. "spreadsheet_token": spreadsheet_token,
  517. "sheet_id": sheet_id,
  518. "sheet_name": sheet_name,
  519. "start_row": start_row,
  520. "num_rows": num_rows,
  521. "user_id_type": user_id_type,
  522. }
  523. res: dict = self._send_request(url, method="GET", params=params)
  524. if "data" in res:
  525. data: dict = res.get("data", {})
  526. return data
  527. return res
  528. def read_cols(
  529. self,
  530. spreadsheet_token: str,
  531. sheet_id: str,
  532. sheet_name: str,
  533. start_col: int,
  534. num_cols: int,
  535. user_id_type: str = "open_id",
  536. ) -> dict:
  537. # 读取工作表列数据
  538. url = f"{self.API_BASE_URL}/spreadsheet/read_cols"
  539. params = {
  540. "spreadsheet_token": spreadsheet_token,
  541. "sheet_id": sheet_id,
  542. "sheet_name": sheet_name,
  543. "start_col": start_col,
  544. "num_cols": num_cols,
  545. "user_id_type": user_id_type,
  546. }
  547. res: dict = self._send_request(url, method="GET", params=params)
  548. if "data" in res:
  549. data: dict = res.get("data", {})
  550. return data
  551. return res
  552. def read_table(
  553. self,
  554. spreadsheet_token: str,
  555. sheet_id: str,
  556. sheet_name: str,
  557. num_range: str,
  558. query: str,
  559. user_id_type: str = "open_id",
  560. ) -> dict:
  561. # 自定义读取行列数据
  562. url = f"{self.API_BASE_URL}/spreadsheet/read_table"
  563. params = {
  564. "spreadsheet_token": spreadsheet_token,
  565. "sheet_id": sheet_id,
  566. "sheet_name": sheet_name,
  567. "range": num_range,
  568. "query": query,
  569. "user_id_type": user_id_type,
  570. }
  571. res: dict = self._send_request(url, method="GET", params=params)
  572. if "data" in res:
  573. data: dict = res.get("data", {})
  574. return data
  575. return res
  576. def create_base(
  577. self,
  578. name: str,
  579. folder_token: str,
  580. ) -> dict:
  581. # 创建多维表格
  582. url = f"{self.API_BASE_URL}/base/create_base"
  583. payload = {
  584. "name": name,
  585. "folder_token": folder_token,
  586. }
  587. res: dict = self._send_request(url, payload=payload)
  588. if "data" in res:
  589. data: dict = res.get("data", {})
  590. return data
  591. return res
  592. def add_records(
  593. self,
  594. app_token: str,
  595. table_id: str,
  596. table_name: str,
  597. records: str,
  598. user_id_type: str = "open_id",
  599. ) -> dict:
  600. # 新增多条记录
  601. url = f"{self.API_BASE_URL}/base/add_records"
  602. params = {
  603. "app_token": app_token,
  604. "table_id": table_id,
  605. "table_name": table_name,
  606. "user_id_type": user_id_type,
  607. }
  608. payload = {
  609. "records": convert_add_records(records),
  610. }
  611. res: dict = self._send_request(url, params=params, payload=payload)
  612. if "data" in res:
  613. data: dict = res.get("data", {})
  614. return data
  615. return res
  616. def update_records(
  617. self,
  618. app_token: str,
  619. table_id: str,
  620. table_name: str,
  621. records: str,
  622. user_id_type: str,
  623. ) -> dict:
  624. # 更新多条记录
  625. url = f"{self.API_BASE_URL}/base/update_records"
  626. params = {
  627. "app_token": app_token,
  628. "table_id": table_id,
  629. "table_name": table_name,
  630. "user_id_type": user_id_type,
  631. }
  632. payload = {
  633. "records": convert_update_records(records),
  634. }
  635. res: dict = self._send_request(url, params=params, payload=payload)
  636. if "data" in res:
  637. data: dict = res.get("data", {})
  638. return data
  639. return res
  640. def delete_records(
  641. self,
  642. app_token: str,
  643. table_id: str,
  644. table_name: str,
  645. record_ids: str,
  646. ) -> dict:
  647. # 删除多条记录
  648. url = f"{self.API_BASE_URL}/base/delete_records"
  649. params = {
  650. "app_token": app_token,
  651. "table_id": table_id,
  652. "table_name": table_name,
  653. }
  654. if not record_ids:
  655. record_id_list = []
  656. else:
  657. try:
  658. record_id_list = json.loads(record_ids)
  659. except json.JSONDecodeError:
  660. raise ValueError("The input string is not valid JSON")
  661. payload = {
  662. "records": record_id_list,
  663. }
  664. res: dict = self._send_request(url, params=params, payload=payload)
  665. if "data" in res:
  666. data: dict = res.get("data", {})
  667. return data
  668. return res
  669. def search_record(
  670. self,
  671. app_token: str,
  672. table_id: str,
  673. table_name: str,
  674. view_id: str,
  675. field_names: str,
  676. sort: str,
  677. filters: str,
  678. page_token: str,
  679. automatic_fields: bool = False,
  680. user_id_type: str = "open_id",
  681. page_size: int = 20,
  682. ) -> dict:
  683. # 查询记录,单次最多查询 500 行记录。
  684. url = f"{self.API_BASE_URL}/base/search_record"
  685. params = {
  686. "app_token": app_token,
  687. "table_id": table_id,
  688. "table_name": table_name,
  689. "user_id_type": user_id_type,
  690. "page_token": page_token,
  691. "page_size": page_size,
  692. }
  693. if not field_names:
  694. field_name_list = []
  695. else:
  696. try:
  697. field_name_list = json.loads(field_names)
  698. except json.JSONDecodeError:
  699. raise ValueError("The input string is not valid JSON")
  700. if not sort:
  701. sort_list = []
  702. else:
  703. try:
  704. sort_list = json.loads(sort)
  705. except json.JSONDecodeError:
  706. raise ValueError("The input string is not valid JSON")
  707. if not filters:
  708. filter_dict = {}
  709. else:
  710. try:
  711. filter_dict = json.loads(filters)
  712. except json.JSONDecodeError:
  713. raise ValueError("The input string is not valid JSON")
  714. payload: dict[str, Any] = {}
  715. if view_id:
  716. payload["view_id"] = view_id
  717. if field_names:
  718. payload["field_names"] = field_name_list
  719. if sort:
  720. payload["sort"] = sort_list
  721. if filters:
  722. payload["filter"] = filter_dict
  723. if automatic_fields:
  724. payload["automatic_fields"] = automatic_fields
  725. res: dict = self._send_request(url, params=params, payload=payload)
  726. if "data" in res:
  727. data: dict = res.get("data", {})
  728. return data
  729. return res
  730. def get_base_info(
  731. self,
  732. app_token: str,
  733. ) -> dict:
  734. # 获取多维表格元数据
  735. url = f"{self.API_BASE_URL}/base/get_base_info"
  736. params = {
  737. "app_token": app_token,
  738. }
  739. res: dict = self._send_request(url, method="GET", params=params)
  740. if "data" in res:
  741. data: dict = res.get("data", {})
  742. return data
  743. return res
  744. def create_table(
  745. self,
  746. app_token: str,
  747. table_name: str,
  748. default_view_name: str,
  749. fields: str,
  750. ) -> dict:
  751. # 新增一个数据表
  752. url = f"{self.API_BASE_URL}/base/create_table"
  753. params = {
  754. "app_token": app_token,
  755. }
  756. if not fields:
  757. fields_list = []
  758. else:
  759. try:
  760. fields_list = json.loads(fields)
  761. except json.JSONDecodeError:
  762. raise ValueError("The input string is not valid JSON")
  763. payload = {
  764. "name": table_name,
  765. "fields": fields_list,
  766. }
  767. if default_view_name:
  768. payload["default_view_name"] = default_view_name
  769. res: dict = self._send_request(url, params=params, payload=payload)
  770. if "data" in res:
  771. data: dict = res.get("data", {})
  772. return data
  773. return res
  774. def delete_tables(
  775. self,
  776. app_token: str,
  777. table_ids: str,
  778. table_names: str,
  779. ) -> dict:
  780. # 删除多个数据表
  781. url = f"{self.API_BASE_URL}/base/delete_tables"
  782. params = {
  783. "app_token": app_token,
  784. }
  785. if not table_ids:
  786. table_id_list = []
  787. else:
  788. try:
  789. table_id_list = json.loads(table_ids)
  790. except json.JSONDecodeError:
  791. raise ValueError("The input string is not valid JSON")
  792. if not table_names:
  793. table_name_list = []
  794. else:
  795. try:
  796. table_name_list = json.loads(table_names)
  797. except json.JSONDecodeError:
  798. raise ValueError("The input string is not valid JSON")
  799. payload = {
  800. "table_ids": table_id_list,
  801. "table_names": table_name_list,
  802. }
  803. res: dict = self._send_request(url, params=params, payload=payload)
  804. if "data" in res:
  805. data: dict = res.get("data", {})
  806. return data
  807. return res
  808. def list_tables(
  809. self,
  810. app_token: str,
  811. page_token: str,
  812. page_size: int = 20,
  813. ) -> dict:
  814. # 列出多维表格下的全部数据表
  815. url = f"{self.API_BASE_URL}/base/list_tables"
  816. params = {
  817. "app_token": app_token,
  818. "page_token": page_token,
  819. "page_size": page_size,
  820. }
  821. res: dict = self._send_request(url, method="GET", params=params)
  822. if "data" in res:
  823. data: dict = res.get("data", {})
  824. return data
  825. return res
  826. def read_records(
  827. self,
  828. app_token: str,
  829. table_id: str,
  830. table_name: str,
  831. record_ids: str,
  832. user_id_type: str = "open_id",
  833. ) -> dict:
  834. url = f"{self.API_BASE_URL}/base/read_records"
  835. params = {
  836. "app_token": app_token,
  837. "table_id": table_id,
  838. "table_name": table_name,
  839. }
  840. if not record_ids:
  841. record_id_list = []
  842. else:
  843. try:
  844. record_id_list = json.loads(record_ids)
  845. except json.JSONDecodeError:
  846. raise ValueError("The input string is not valid JSON")
  847. payload = {
  848. "record_ids": record_id_list,
  849. "user_id_type": user_id_type,
  850. }
  851. res: dict = self._send_request(url, method="GET", params=params, payload=payload)
  852. if "data" in res:
  853. data: dict = res.get("data", {})
  854. return data
  855. return res