Team Ai
Apppublic

Underground-Digital/Workflow-Engine

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