飞书应用机器人文件上传

news2024/11/14 5:52:07

背景:

  接上一篇 flask_apscheduler实现定时推送飞书消息,当检查出的异常结果比较多的时候,群里会有很多推送消息,一条条检查工作量会比较大,且容易出现遗漏。
  现在需要将定时任务执行的结果记录到文件,最好是飞书的云文件中,通过分享云文档的方式分析给响应的人员。

功能:

  飞书群机器人没有文件上传的的功能,满足这个功能需要使用飞书应用机器人。创建飞书应用后,需要完成机器人配置,以及上传文件的权限申请。
在这里插入图片描述在这里插入图片描述
待使用的接口功能:

  1. 实现文件上传,参考文档。通过该接口实现将定时任务执行结果保存上传至飞书云文档。
    在这里插入图片描述2. 更新云文档权限设置,参考文档。修改上传至云文档的文件权限,使组织内成员可阅读。
    在这里插入图片描述

实现:

  • 实现效果:
    在这里插入图片描述

  • 功能代码:

    # -*- coding:UTF-8 -*-
    
    """
     @ProjectName  : HotelGo2DelonixPmx
     @FileName     : webhook
     @Description  : 飞书消息推送
     @Time         : 2023/9/17 13:36
     @Author       : Qredsun
     """
    
    import os
    import requests
    
    
    class FeishuApplication():
        TENANT_ACCESS_TOKEN_URL = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal'
        GET_USER_ID_URL = 'https://open.feishu.cn/open-apis/contact/v3/users/batch_get_id'
        IM_MESSAGES_URL = 'https://open.feishu.cn/open-apis/im/v1/messages'
        FILES_UPLOAD_URL = 'https://open.feishu.cn/open-apis/drive/v1/files/upload_all'
        DRIVE_FILES_URL = 'https://open.feishu.cn/open-apis/drive/v1/files'
        FILE_PERMISSION = 'https://open.feishu.cn/open-apis/drive/v2/permissions/token/public'
        CREATE_FOLDER = 'https://open.feishu.cn/open-apis/drive/v1/files/create_folder'
    
        def __init__(self, app_id, app_secret):
            self.app_id = app_id
            self.app_secret = app_secret
            self.get_tenant_access_token()
    
            self._url_prefix = None
            self._file_url_prefix = None
    
        def get_tenant_access_token(self):
            url = self.TENANT_ACCESS_TOKEN_URL
            data = {
                "app_id"    : self.app_id,
                "app_secret": self.app_secret
            }
            response = requests.post(url, json=data)
            response.raise_for_status()
            res_data = response.json()
            if res_data:
                self._tenant_access_token = res_data['tenant_access_token']
                self.headers = {
                    'Content-Type' : 'application/json',
                    'Authorization': f'Bearer {self._tenant_access_token}'
                }
                logger.debug(f'自建应用更新token成功')
                return self._tenant_access_token
            else:
                logger.error(f'自建应用获取token失败:{response.text}')
                return False
    
        def get_user_open_id(self, user_info):
            # 单用户id查询
            url = self.GET_USER_ID_URL
            params = {
                "user_id_type": "open_id"
            }
    
            payload = {
                "emails" : [],
                "mobiles": []
            }
            if '@' in user_info:
                payload["emails"].append(user_info)
                response = requests.post(url, headers=self.headers, params=params, json=payload)
    
            elif user_info.isalnum():
                payload["mobiles"].append(user_info)
                response = requests.post(url, headers=self.headers, params=params, json=payload)
    
            response.raise_for_status()
            res_data = response.json()
            if res_data:
                self.open_id = res_data['data']["user_list"][0]["user_id"]
                return self.open_id
            else:
                logger.error(f'获取用户{user_info} open_id 失败:{response.text}')
                return None
    
        def send_single_message(self, msg = "single chat msg", open_id = ''):
            if not open_id:
                logger.error('缺少对话用户 open_id ')
                return
    
            url = self.IM_MESSAGES_URL
            params = { "receive_id_type": "open_id" }
            msgContent = {
                "text": msg
            }
            req = {
                "receive_id": open_id,  # chat id
                "msg_type"  : "text",
                "content"   : json.dumps(msgContent)
            }
            payload = json.dumps(req)
            response = requests.request("POST", url, params=params, headers=self.headers, data=payload)
    
            response.raise_for_status()
            res_data = response.json()
            if res_data:
                self.open_id = res_data['data']["chat_id"]
                return True
            else:
                logger.error(f'给用户 {self.open_id} 发送消息失败:{response.text}')
                return False
    
        def remove_file_or_folder(self, file_token, file_type='file'):
            url = self.DRIVE_FILES_URL
            url += f'/{file_token}'
            payload = ''
            params = {
                'type':file_type
            }
            response = requests.request("DELETE", url, headers=self.headers, params=params, data=payload)
            response.raise_for_status()
            result = response.json()
            if result.get("code") and result.get("code") != 0:
                logger.error(f'移除文件失败:{response.text}')
                return False
            else:
                logger.debug(f'移除文件成功:{response.text}')
                return True
    
        def update_permissions(self, folder_token = '', file_type='file'):
            url = self.FILE_PERMISSION
            url  = url.replace('token', folder_token)
            params = {
                'type': file_type
            }
    
            payload = json.dumps({
                "comment_entity"            : "anyone_can_view",
                "copy_entity"               : "anyone_can_view",
                "external_access_entity"    : "open",
                "link_share_entity"         : "tenant_editable",
                "manage_collaborator_entity": "collaborator_can_view",
                "security_entity"           : "anyone_can_view",
                "share_entity"              : "anyone"
            })
    
            response = requests.request("PATCH", url, headers=self.headers, data=payload, params=params)
            response.raise_for_status()
            result = response.json()
            if result.get("code") and result.get("code") != 0:
                logger.error(f'更新文件权限失败:{response.text}')
                return False
            else:
                logger.debug(f'更新文件权限成功:{response.text}')
                return True
    
        """上传文件"""
        def upload_file(self, file_path = "../data/result/23_09_25_订房检查任务.xlsx",
                        parent_node = 'ErVlfbxP8lqZ1sdMIWkc11TQn8g'):
            if not os.path.isfile(file_path):
                logger.error(f'{file_path} 文件路径没有指定特定文件')
                return
    
            url = self.FILES_UPLOAD_URL
    
            file_size = os.path.getsize(file_path)
            file_name = os.path.basename(file_path)
    
            payload = {
                'file_name'  : file_name,
                'parent_type': 'explorer',
                'parent_node': parent_node,
                'size'       : f'{file_size}'
            }
            files = [
                ('file', (file_name, open(os.path.abspath(file_path), 'rb'),
                          'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'))
            ]
            headers = {
                'Authorization': self.headers['Authorization']
            }
            resp = requests.request("POST", url, headers=headers, data=payload, files=files)
    
            resp.raise_for_status()
            result = resp.json()
            if result.get("code") and result.get("code") != 0:
                logger.error(f'文件上传失败:{resp.text}')
                return False
            else:
                file_token = result['data']['file_token']
                logger.debug(f'文件上传成功:{resp.text}')
                return file_token
    
        """获取文件夹下的清单"""
        def expoler(self, direction = 'DESC', order_by = 'EditedTime'):
            url = self.DRIVE_FILES_URL
            params = {
                'direction': direction,
                'order_by' : order_by
            }
    
            resp = requests.request("GET", url, headers=self.headers, params=params)
            resp.raise_for_status()
            result = resp.json()
            if result.get("code") and result.get("code") != 0:
                logger.error(f'获取云空间列表失败:{resp.text}')
                return None
            else:
                self.files = result['data']['files']
                self.update_url_prefix()
                logger.debug(f'获取云空间列表成功: {self.files}')
                return self.files
    
        """新建文件夹"""
        def create_folder(self, folder_name = "", folder_token = ""):
            url = self.CREATE_FOLDER
            payload = {
                "folder_token": folder_token,
                "name"        : folder_name
            }
    
            resp = requests.request("POST", url, headers=self.headers, json=payload)
            resp.raise_for_status()
            result = resp.json()
            if result.get("code") and result.get("code") != 0:
                logger.error(f'新建文件夹失败:{resp.text}')
                return None
            else:
                self.folder_token = result['data']['token']
                logger.debug(f'新建文件夹成功: {self.folder_token}')
    
                folder_url = result['data']['url']
                start_index = folder_url.find('//') + 2
                r_index = folder_url.find('/', start_index) + 1
                self._url_prefix = folder_url[:r_index]
                logger.debug(f'更新应用地址前缀:{self._url_prefix}')
                self._file_url_prefix = self._url_prefix + 'file/'
                logger.debug(f'更新云文件前缀:{self._file_url_prefix}')
    
                return self.folder_token
    
        def update_url_prefix(self):
            for obj in self.files:
                if obj['type'] == 'folder':
                    obj_url = obj['url']
                    start_index = obj_url.find('//') + 2
                    r_index = obj_url.find('/', start_index) + 1
                    self._url_prefix = obj_url[:r_index]
                    logger.debug(f'更新应用地址前缀:{self._url_prefix}')
                    self._file_url_prefix = self._url_prefix + 'file/'
                    logger.debug(f'更新云文件前缀:{self._file_url_prefix}')
                    break
            return self._url_prefix
    
    def upload_schedule_result(upload_file, app_id, app_secret):
        robot = FeishuApplication(app_id, app_secret)
        default_folder = 'schedule_demo'
        file_path = upload_file
        parent_node = ''
    
        robot.expoler()
        if not robot.files.__len__():
            # 创建文件夹
            result = robot.create_folder(default_folder)
            if result:
                parent_node = result
        else:
            for file in robot.files:
                if default_folder == file['name']:
                    parent_node = file['parent_token']
                    parent_node = file['token']
                    break
    
        # 移除文件
        robot.remove_file_or_folder('O3MgbgYKgo7NgtxUNc4cqkQZnWe')
        upload_file_token = robot.upload_file(file_path=file_path, parent_node=parent_node)
        if upload_file_token:
            result = robot.update_permissions(upload_file_token)
            if result:
                file_url = f'{robot._file_url_prefix}{upload_file_token}'
                logger.debug(f'待分享的文件url: {file_url}')
        else:
            file_url = ''
            logger.debug('上传结果至飞书失败')
        return file_url
    
    
    if __name__ == '__main__':
        upload_file = "../data/result/23_09_24_订房检查任务.xlsx"
        app_id = 'XXXX'
        app_secret = 'XXX'
        upload_schedule_result(upload_file, app_id, app_secret)
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1079359.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESPHome不经过HA设备1直接控制设备2

目录 1.公共配置文件2.设备2:台灯3.控制器(http.post)4.获取状态(http.get) 1.公共配置文件 #wifi.yaml wifi: networks:- ssid: "123"password: "www.123.com"- ssid: "456"password: "www.123.com"# 当连接不上指定wifi,开启热点配…

idea-java: 错误: 无效的源发行版:16

1.CtrlAltShiftS 2.jdk版本对应好 3.modules中一栏sdk 4.modules中一栏源码 5.所有modules修改 6.修改项目配置 ctrlalts,修改为指定版本,应用即可

codesys【按钮】

1用于控制bool信号。 1声明全局变量 2绑定该变量 运行后,按钮就能控制这个bool变量了。 2按钮【自复位】 3按钮【锁位】

订购OV通配符SSL证书

目前很多单位申请OV通配符SSL证书,一头雾水,其实操作还是比较简单的。 订购条件OV通配符组织单位,申请之前需要确定好单位全程,并且在第三方一些主流的公共平台可以查到主体信息。 然后根据以下步骤操作就可以了: 1…

什么是API接口?给大家举例说明

Api接口也就是所谓的应用程序接口,api接口的全称是Application Program Interface,通过API接口可以实现计算机软件之间的相互通信,开发人员可以通过API接口程序开发应用程序,可以减少编写无用程序,减轻编程任务&#x…

WARNING: There was an error checking the latest version of pip.

警告:检查最新版本的pip时出错 忽略无效的分发 -ip VPN网络的提示对我非常有帮助。模块安装失败,出现上述警告,并重试连接断开警告: WARNING: There was an error checking the latest version of pip. WARNING: Retrying (Retr…

【JavaEE】_tomcat的安装与简单使用

目录 1. 安装tomcat 1.1 下载tomcat并解压缩 1.2 启动tomcat 1.3 访问tomcat欢迎页面 2. tomcat简单使用:部署前端代码 3. 基于tomcat的网站后端开发 tomcat是一个HTTP服务器,HTTP协议就是HTTP客户端与HTTP服务器之间通信使用的协议。 其中HTTP客…

编写一个应用程序,给出汉字‘你’、‘我’、‘他’在Unicode表中的位置。

(1)思路\n\n通过类型转换来显示中文的位置。 (2)代码实现 public class Place{public static void main (String args[]){char chinaWord 你;char china_Word 我;char china__Word 他;System.out.println("汉字: "ch…

OCP Java17 SE Developers 复习题04

答案 F. Line 5 does not compile. This question is checking to see whether you are paying attention to the types. numFish is an int, and 1 is an int. Therefore, we use numeric addition and get 5. The problem is that we cant store an int in a String variab…

新媒体达人投放技巧有哪些,投放总结!

达人投放,一个当今时代品牌传播跳不开的词。关于达人投放的优势与特点,相信所有人都已经不在陌生。但是真的进行达人投放时,又该如何实施,今天来分享下新媒体达人投放技巧有哪些,投放总结! 一、品牌不同阶段…

Redis分布式系统: 主从复制

“你小心保管我,不思议的念头。秘密从不会对谁泄漏~” 什么是分布式系统? 分布式系统的出现,就是为了解决单机问题(硬件资源不足)。在分布式系统中,通常会把数据复制多个副本部署到其他服务器,满⾜故障恢复和负载均衡等…

通讯网关软件020——利用CommGate X2Mysql实现Modbus TCP数据转储Mysql

本文介绍利用CommGate X2MYSQL实现从Modbus TCP设备读取数据并转储至MYSQL数据库。CommGate X2MYSQL是宁波科安网信开发的网关软件,软件可以登录到网信智汇(http://wangxinzhihui.com)下载。 【案例】如下图所示,实现从Modbus TCP设备读取数据并转储至M…

苹果ios安卓apk应用APP文件怎么修改手机APP显示的名称

修改应用名称虽然很简单,但是也是由很多喜欢代码却不是很懂代码的白白同学,所以在这里我简单的说一下具体的话还是要靠同学们自行研究,由更好的方式也可以评论区说一下哈,让俺们也学习学习。 要修改 APK 文件的应用名称&#xff…

【rust基础】基本类型、所有权与借用、复合类型

文章目录 1 基本类型1.1 数值类型1.1.1 Rust 中的内置的整数类型:1.1.2 浮点类型1.1.3 数学运算1.1.4 位运算1.1.5 序列(Range) 1.2 字符、布尔、单元类型1.3 语句和表达式1.4 函数 2 所有权与借用2.1 栈(Stack)与堆(Heap)2.2 所有权原则2.2.1 转移所有权2.2.2 克隆…

VMware安装CentOS虚拟机

1. 简介 VMware 虚拟机是一种基于虚拟化技术的软件应用程序,它允许用户在一台物理计算机上模拟多个虚拟计算环境。虚拟机提供了一个可隔离且独立于物理硬件的虚拟环境,其中可以安装和运行操作系统和应用程序。 官网地址:https://www.vmware…

IT运维管理平台助力企业打造监、管、控一体化

成立20多年业务遍及全球100多个国家和地区的某大型企业随着全球化业务快速发展,业务对信息系统运维的可用性和持续性要求随之越来越高,伴随业务发展带来的IT环境复杂度呈指数级增加,系统运维工作正面临严峻的挑战。在业务系统运行过程中&…

金融人为什么都在争先申请中国人民大学与加拿大女王大学金融硕士

“知识就是力量”,这是弗朗西斯培根的名言,也是金融行业人才追求的动力。2023年,随着金融行业的飞速发展和复杂性的增加,各企业对优秀人才的需求更加迫切。在这场激烈的人才争夺战中,越来越多的金融人选择了在职研究生…

【AI视野·今日Robot 机器人论文速览 第五十期】Mon, 9 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Mon, 9 Oct 2023 Totally 25 papers 👉上期速览✈更多精彩请移步主页 Daily Robotics Papers Learning to Grasp: from Somewhere to Anywhere Authors Fran ois H l non, Johann Huber, Fa z Ben Amar, St phane Doncieux…

基于大规模分布式系统的云原生运维实践

在云计算、大数据、人工智能等新兴技术的推动下,众多行业都在经历一场轰轰烈烈的数字化转型大潮。随着容器技术和编排系统的发展、基础设施不断云化、分布式微服务架构不断演进和敏捷、DevOps等开发理念的带动,应用云化已经是不可逆转的趋势,云原生也成为…

uni-app:实现简易自定义下拉列表

效果 代码 <template><view><view class"dropdown-trigger" tap"showDropdown">{{ selectedItem }}</view><view class"dropdown-list" v-if"showList"><view class"dropdown-item" v-f…