| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852 | import jsonfrom typing import Any, Optional, castimport httpxfrom core.tools.errors import ToolProviderCredentialValidationErrorfrom extensions.ext_redis import redis_clientdef lark_auth(credentials):    app_id = credentials.get("app_id")    app_secret = credentials.get("app_secret")    if not app_id or not app_secret:        raise ToolProviderCredentialValidationError("app_id and app_secret is required")    try:        assert LarkRequest(app_id, app_secret).tenant_access_token is not None    except Exception as e:        raise ToolProviderCredentialValidationError(str(e))class LarkRequest:    API_BASE_URL = "https://lark-plugin-api.solutionsuite.ai/lark-plugin"    def __init__(self, app_id: str, app_secret: str):        self.app_id = app_id        self.app_secret = app_secret    def convert_add_records(self, json_str):        try:            data = json.loads(json_str)            if not isinstance(data, list):                raise ValueError("Parsed data must be a list")            converted_data = [{"fields": json.dumps(item, ensure_ascii=False)} for item in data]            return converted_data        except json.JSONDecodeError:            raise ValueError("The input string is not valid JSON")        except Exception as e:            raise ValueError(f"An error occurred while processing the data: {e}")    def convert_update_records(self, json_str):        try:            data = json.loads(json_str)            if not isinstance(data, list):                raise ValueError("Parsed data must be a list")            converted_data = [                {"fields": json.dumps(record["fields"], ensure_ascii=False), "record_id": record["record_id"]}                for record in data                if "fields" in record and "record_id" in record            ]            if len(converted_data) != len(data):                raise ValueError("Each record must contain 'fields' and 'record_id'")            return converted_data        except json.JSONDecodeError:            raise ValueError("The input string is not valid JSON")        except Exception as e:            raise ValueError(f"An error occurred while processing the data: {e}")    @property    def tenant_access_token(self) -> str:        feishu_tenant_access_token = f"tools:{self.app_id}:feishu_tenant_access_token"        if redis_client.exists(feishu_tenant_access_token):            return str(redis_client.get(feishu_tenant_access_token).decode())        res: dict[str, str] = self.get_tenant_access_token(self.app_id, self.app_secret)        redis_client.setex(feishu_tenant_access_token, res.get("expire"), res.get("tenant_access_token"))        return res.get("tenant_access_token", "")    def _send_request(        self,        url: str,        method: str = "post",        require_token: bool = True,        payload: Optional[dict] = None,        params: Optional[dict] = None,    ):        headers = {            "Content-Type": "application/json",            "user-agent": "Dify",        }        if require_token:            headers["tenant-access-token"] = f"{self.tenant_access_token}"        res = httpx.request(method=method, url=url, headers=headers, json=payload, params=params, timeout=30).json()        if res.get("code") != 0:            raise Exception(res)        return res    def get_tenant_access_token(self, app_id: str, app_secret: str) -> dict:        url = f"{self.API_BASE_URL}/access_token/get_tenant_access_token"        payload = {"app_id": app_id, "app_secret": app_secret}        res: dict = self._send_request(url, require_token=False, payload=payload)        return res    def create_document(self, title: str, content: str, folder_token: str) -> dict:        url = f"{self.API_BASE_URL}/document/create_document"        payload = {            "title": title,            "content": content,            "folder_token": folder_token,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def write_document(self, document_id: str, content: str, position: str = "end") -> dict:        url = f"{self.API_BASE_URL}/document/write_document"        payload = {"document_id": document_id, "content": content, "position": position}        res: dict = self._send_request(url, payload=payload)        return res    def get_document_content(self, document_id: str, mode: str = "markdown", lang: str = "0") -> str | dict:        params = {            "document_id": document_id,            "mode": mode,            "lang": lang,        }        url = f"{self.API_BASE_URL}/document/get_document_content"        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            return cast(dict, res.get("data", {}).get("content"))        return ""    def list_document_blocks(        self, document_id: str, page_token: str, user_id_type: str = "open_id", page_size: int = 500    ) -> dict:        params = {            "user_id_type": user_id_type,            "document_id": document_id,            "page_size": page_size,            "page_token": page_token,        }        url = f"{self.API_BASE_URL}/document/list_document_blocks"        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def send_bot_message(self, receive_id_type: str, receive_id: str, msg_type: str, content: str) -> dict:        url = f"{self.API_BASE_URL}/message/send_bot_message"        params = {            "receive_id_type": receive_id_type,        }        payload = {            "receive_id": receive_id,            "msg_type": msg_type,            "content": content.strip('"').replace(r"\"", '"').replace(r"\\", "\\"),        }        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def send_webhook_message(self, webhook: str, msg_type: str, content: str) -> dict:        url = f"{self.API_BASE_URL}/message/send_webhook_message"        payload = {            "webhook": webhook,            "msg_type": msg_type,            "content": content.strip('"').replace(r"\"", '"').replace(r"\\", "\\"),        }        res: dict = self._send_request(url, require_token=False, payload=payload)        return res    def get_chat_messages(        self,        container_id: str,        start_time: str,        end_time: str,        page_token: str,        sort_type: str = "ByCreateTimeAsc",        page_size: int = 20,    ) -> dict:        url = f"{self.API_BASE_URL}/message/get_chat_messages"        params = {            "container_id": container_id,            "start_time": start_time,            "end_time": end_time,            "sort_type": sort_type,            "page_token": page_token,            "page_size": page_size,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def get_thread_messages(        self, container_id: str, page_token: str, sort_type: str = "ByCreateTimeAsc", page_size: int = 20    ) -> dict:        url = f"{self.API_BASE_URL}/message/get_thread_messages"        params = {            "container_id": container_id,            "sort_type": sort_type,            "page_token": page_token,            "page_size": page_size,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def create_task(self, summary: str, start_time: str, end_time: str, completed_time: str, description: str) -> dict:        url = f"{self.API_BASE_URL}/task/create_task"        payload = {            "summary": summary,            "start_time": start_time,            "end_time": end_time,            "completed_at": completed_time,            "description": description,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def update_task(        self, task_guid: str, summary: str, start_time: str, end_time: str, completed_time: str, description: str    ) -> dict:        url = f"{self.API_BASE_URL}/task/update_task"        payload = {            "task_guid": task_guid,            "summary": summary,            "start_time": start_time,            "end_time": end_time,            "completed_time": completed_time,            "description": description,        }        res: dict = self._send_request(url, method="PATCH", payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def delete_task(self, task_guid: str) -> dict:        url = f"{self.API_BASE_URL}/task/delete_task"        payload = {            "task_guid": task_guid,        }        res: dict = self._send_request(url, method="DELETE", payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def add_members(self, task_guid: str, member_phone_or_email: str, member_role: str) -> dict:        url = f"{self.API_BASE_URL}/task/add_members"        payload = {            "task_guid": task_guid,            "member_phone_or_email": member_phone_or_email,            "member_role": member_role,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def get_wiki_nodes(self, space_id: str, parent_node_token: str, page_token: str, page_size: int = 20) -> dict:        url = f"{self.API_BASE_URL}/wiki/get_wiki_nodes"        payload = {            "space_id": space_id,            "parent_node_token": parent_node_token,            "page_token": page_token,            "page_size": page_size,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def get_primary_calendar(self, user_id_type: str = "open_id") -> dict:        url = f"{self.API_BASE_URL}/calendar/get_primary_calendar"        params = {            "user_id_type": user_id_type,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def create_event(        self,        summary: str,        description: str,        start_time: str,        end_time: str,        attendee_ability: str,        need_notification: bool = True,        auto_record: bool = False,    ) -> dict:        url = f"{self.API_BASE_URL}/calendar/create_event"        payload = {            "summary": summary,            "description": description,            "need_notification": need_notification,            "start_time": start_time,            "end_time": end_time,            "auto_record": auto_record,            "attendee_ability": attendee_ability,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def update_event(        self,        event_id: str,        summary: str,        description: str,        need_notification: bool,        start_time: str,        end_time: str,        auto_record: bool,    ) -> dict:        url = f"{self.API_BASE_URL}/calendar/update_event/{event_id}"        payload: dict[str, Any] = {}        if summary:            payload["summary"] = summary        if description:            payload["description"] = description        if start_time:            payload["start_time"] = start_time        if end_time:            payload["end_time"] = end_time        if need_notification:            payload["need_notification"] = need_notification        if auto_record:            payload["auto_record"] = auto_record        res: dict = self._send_request(url, method="PATCH", payload=payload)        return res    def delete_event(self, event_id: str, need_notification: bool = True) -> dict:        url = f"{self.API_BASE_URL}/calendar/delete_event/{event_id}"        params = {            "need_notification": need_notification,        }        res: dict = self._send_request(url, method="DELETE", params=params)        return res    def list_events(self, start_time: str, end_time: str, page_token: str, page_size: int = 50) -> dict:        url = f"{self.API_BASE_URL}/calendar/list_events"        params = {            "start_time": start_time,            "end_time": end_time,            "page_token": page_token,            "page_size": page_size,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def search_events(        self,        query: str,        start_time: str,        end_time: str,        page_token: str,        user_id_type: str = "open_id",        page_size: int = 20,    ) -> dict:        url = f"{self.API_BASE_URL}/calendar/search_events"        payload = {            "query": query,            "start_time": start_time,            "end_time": end_time,            "page_token": page_token,            "user_id_type": user_id_type,            "page_size": page_size,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def add_event_attendees(self, event_id: str, attendee_phone_or_email: str, need_notification: bool = True) -> dict:        url = f"{self.API_BASE_URL}/calendar/add_event_attendees"        payload = {            "event_id": event_id,            "attendee_phone_or_email": attendee_phone_or_email,            "need_notification": need_notification,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def create_spreadsheet(        self,        title: str,        folder_token: str,    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/create_spreadsheet"        payload = {            "title": title,            "folder_token": folder_token,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def get_spreadsheet(        self,        spreadsheet_token: str,        user_id_type: str = "open_id",    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/get_spreadsheet"        params = {            "spreadsheet_token": spreadsheet_token,            "user_id_type": user_id_type,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def list_spreadsheet_sheets(        self,        spreadsheet_token: str,    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/list_spreadsheet_sheets"        params = {            "spreadsheet_token": spreadsheet_token,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def add_rows(        self,        spreadsheet_token: str,        sheet_id: str,        sheet_name: str,        length: int,        values: str,    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/add_rows"        payload = {            "spreadsheet_token": spreadsheet_token,            "sheet_id": sheet_id,            "sheet_name": sheet_name,            "length": length,            "values": values,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def add_cols(        self,        spreadsheet_token: str,        sheet_id: str,        sheet_name: str,        length: int,        values: str,    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/add_cols"        payload = {            "spreadsheet_token": spreadsheet_token,            "sheet_id": sheet_id,            "sheet_name": sheet_name,            "length": length,            "values": values,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def read_rows(        self,        spreadsheet_token: str,        sheet_id: str,        sheet_name: str,        start_row: int,        num_rows: int,        user_id_type: str = "open_id",    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/read_rows"        params = {            "spreadsheet_token": spreadsheet_token,            "sheet_id": sheet_id,            "sheet_name": sheet_name,            "start_row": start_row,            "num_rows": num_rows,            "user_id_type": user_id_type,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def read_cols(        self,        spreadsheet_token: str,        sheet_id: str,        sheet_name: str,        start_col: int,        num_cols: int,        user_id_type: str = "open_id",    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/read_cols"        params = {            "spreadsheet_token": spreadsheet_token,            "sheet_id": sheet_id,            "sheet_name": sheet_name,            "start_col": start_col,            "num_cols": num_cols,            "user_id_type": user_id_type,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def read_table(        self,        spreadsheet_token: str,        sheet_id: str,        sheet_name: str,        num_range: str,        query: str,        user_id_type: str = "open_id",    ) -> dict:        url = f"{self.API_BASE_URL}/spreadsheet/read_table"        params = {            "spreadsheet_token": spreadsheet_token,            "sheet_id": sheet_id,            "sheet_name": sheet_name,            "range": num_range,            "query": query,            "user_id_type": user_id_type,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def create_base(        self,        name: str,        folder_token: str,    ) -> dict:        url = f"{self.API_BASE_URL}/base/create_base"        payload = {            "name": name,            "folder_token": folder_token,        }        res: dict = self._send_request(url, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def add_records(        self,        app_token: str,        table_id: str,        table_name: str,        records: str,        user_id_type: str = "open_id",    ) -> dict:        url = f"{self.API_BASE_URL}/base/add_records"        params = {            "app_token": app_token,            "table_id": table_id,            "table_name": table_name,            "user_id_type": user_id_type,        }        payload = {            "records": self.convert_add_records(records),        }        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def update_records(        self,        app_token: str,        table_id: str,        table_name: str,        records: str,        user_id_type: str,    ) -> dict:        url = f"{self.API_BASE_URL}/base/update_records"        params = {            "app_token": app_token,            "table_id": table_id,            "table_name": table_name,            "user_id_type": user_id_type,        }        payload = {            "records": self.convert_update_records(records),        }        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def delete_records(        self,        app_token: str,        table_id: str,        table_name: str,        record_ids: str,    ) -> dict:        url = f"{self.API_BASE_URL}/base/delete_records"        params = {            "app_token": app_token,            "table_id": table_id,            "table_name": table_name,        }        if not record_ids:            record_id_list = []        else:            try:                record_id_list = json.loads(record_ids)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        payload = {            "records": record_id_list,        }        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def search_record(        self,        app_token: str,        table_id: str,        table_name: str,        view_id: str,        field_names: str,        sort: str,        filters: str,        page_token: str,        automatic_fields: bool = False,        user_id_type: str = "open_id",        page_size: int = 20,    ) -> dict:        url = f"{self.API_BASE_URL}/base/search_record"        params = {            "app_token": app_token,            "table_id": table_id,            "table_name": table_name,            "user_id_type": user_id_type,            "page_token": page_token,            "page_size": page_size,        }        if not field_names:            field_name_list = []        else:            try:                field_name_list = json.loads(field_names)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        if not sort:            sort_list = []        else:            try:                sort_list = json.loads(sort)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        if not filters:            filter_dict = {}        else:            try:                filter_dict = json.loads(filters)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        payload: dict[str, Any] = {}        if view_id:            payload["view_id"] = view_id        if field_names:            payload["field_names"] = field_name_list        if sort:            payload["sort"] = sort_list        if filters:            payload["filter"] = filter_dict        if automatic_fields:            payload["automatic_fields"] = automatic_fields        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def get_base_info(        self,        app_token: str,    ) -> dict:        url = f"{self.API_BASE_URL}/base/get_base_info"        params = {            "app_token": app_token,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def create_table(        self,        app_token: str,        table_name: str,        default_view_name: str,        fields: str,    ) -> dict:        url = f"{self.API_BASE_URL}/base/create_table"        params = {            "app_token": app_token,        }        if not fields:            fields_list = []        else:            try:                fields_list = json.loads(fields)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        payload = {            "name": table_name,            "fields": fields_list,        }        if default_view_name:            payload["default_view_name"] = default_view_name        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def delete_tables(        self,        app_token: str,        table_ids: str,        table_names: str,    ) -> dict:        url = f"{self.API_BASE_URL}/base/delete_tables"        params = {            "app_token": app_token,        }        if not table_ids:            table_id_list = []        else:            try:                table_id_list = json.loads(table_ids)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        if not table_names:            table_name_list = []        else:            try:                table_name_list = json.loads(table_names)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        payload = {            "table_ids": table_id_list,            "table_names": table_name_list,        }        res: dict = self._send_request(url, params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def list_tables(        self,        app_token: str,        page_token: str,        page_size: int = 20,    ) -> dict:        url = f"{self.API_BASE_URL}/base/list_tables"        params = {            "app_token": app_token,            "page_token": page_token,            "page_size": page_size,        }        res: dict = self._send_request(url, method="GET", params=params)        if "data" in res:            data: dict = res.get("data", {})            return data        return res    def read_records(        self,        app_token: str,        table_id: str,        table_name: str,        record_ids: str,        user_id_type: str = "open_id",    ) -> dict:        url = f"{self.API_BASE_URL}/base/read_records"        params = {            "app_token": app_token,            "table_id": table_id,            "table_name": table_name,        }        if not record_ids:            record_id_list = []        else:            try:                record_id_list = json.loads(record_ids)            except json.JSONDecodeError:                raise ValueError("The input string is not valid JSON")        payload = {            "record_ids": record_id_list,            "user_id_type": user_id_type,        }        res: dict = self._send_request(url, method="POST", params=params, payload=payload)        if "data" in res:            data: dict = res.get("data", {})            return data        return res
 |