封装Python脚本:使用钉钉机器人发送消息至钉钉

news2024/11/28 10:48:31

官方帮助文档:https://open.dingtalk.com/document/robots/custom-robot-access

一、获取自定义机器人webhook

可以通过如下步骤设置钉钉机器人:

  1. 首先建立或者进入某个群聊
  2. 在群聊内部点击“设置>机器人>添加机器人”
    在这里插入图片描述
  3. 添加一个自定义机器人, 机器人名称自定义,安全设置勾选“加签”
    在这里插入图片描述
    在这里插入图片描述
  4. 添加完成后,复制机器人的webhook地址以及加签的密钥。(可以重新进去机器人管理页面查看得到)

安全设置

安全设置目前有3种方式:

1)方式一:自定义关键词
最多可以设置10个关键词,消息中至少包含其中1个关键词才可以发送成功。

例如:添加了一个自定义关键词:监控报警

则这个机器人所发送的消息,必须包含 监控报警 这个词,才能发送成功。

2)方式二:加签
第一步,把timestamp+“\n”+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)。

3)方式三:IP地址
仅指定的IP地址支持钉钉机器人,最多添加10个IP地址。

常见问题

当出现以下错误时,表示消息校验未通过,请查看机器人的安全设置。

// 消息内容中不包含任何关键词
{
  "errcode":310000,
  "errmsg":"keywords not in content"
}

// timestamp 无效
{
  "errcode":310000,
  "errmsg":"invalid timestamp"
}

// 签名不匹配
{
  "errcode":310000,
  "errmsg":"sign not match"
}

// IP地址不在白名单
{
  "errcode":310000,
  "errmsg":"ip X.X.X.X not in whitelist"
}

二、python封装脚本

# -*- coding: utf-8 -*-
# @Time    : 2023/5/11 9:47
# @Author  : chenyinhua
# @File    : dingding_handle.py
# @Software: PyCharm
# @Desc: 钉钉通知封装

import hmac
import hashlib
import base64
import urllib.parse
import time
import json
import urllib.request
from loguru import logger
from requests import request


class DingTalkBot:
    """
    钉钉机器人
    """

    def __init__(self, webhook_url, secret=None):
        """
        :param secret: 安全设置的加签秘钥
        :param webhook_url: 机器人没有加签的WebHook_url
        """
        self.secret = secret
        timestamp = str(round(time.time() * 1000))
        sign = self.get_sign(timestamp)
        self.webhook_url = webhook_url + f'&timestamp={timestamp}&sign={sign}'  # 最终url,url+时间戳+签名
        self.headers = {
            "Content-Type": "application/json",
            "Charset": "UTF-8"
        }

    def get_sign(self, timestamp):
        """
        根据时间戳 + "sign" 生成密钥
        把timestamp+"\n"+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)。
        :return:
        """
        string_to_sign = f'{timestamp}\n{self.secret}'.encode('utf-8')
        hmac_code = hmac.new(
            self.secret.encode('utf-8'),
            string_to_sign,
            digestmod=hashlib.sha256).digest()

        sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
        return sign

    def send_text(self, content, mobiles=None, is_at_all=False):
        """
        发送文本消息
        :param content: 发送的内容
        :param mobiles: 被艾特的用户的手机号码,格式是列表,注意需要在content里面添加@人的手机号码
        :param is_at_all: 是否艾特所有人,布尔类型,true为艾特所有人,false为不艾特
        """
        if mobiles:
            if isinstance(mobiles, list):
                payload = {
                    "msgtype": "text",
                    "text": {
                        "content": content
                    },
                    "at": {
                        "atMobiles": mobiles,
                        "isAtAll": False
                    }
                }
                for mobile in mobiles:
                    payload["text"]["content"] += f"@{mobile}"
            else:
                raise TypeError("mobiles类型错误 不是list类型.")
        else:
            payload = {
                "msgtype": "text",
                "text": {
                    "content": content
                },
                "at": {
                    "atMobiles": "",
                    "isAtAll": is_at_all
                }
            }
        response = request(url=self.webhook_url, json=payload, headers=self.headers, method="POST")
        logger.debug(f"send_text发送钉钉消息的响应数据:{response.json()}")
        return response

    def send_link(self, title, text, message_url, pic_url=None):
        """
        发送链接消息
        :param title: 消息标题
        :param text: 消息内容,如果太长只会部分展示
        :param message_url: 点击消息跳转的url地址
        :param pic_url: 图片url
        """
        payload = {
            "msgtype": "link",
            "link": {
                "title": title,
                "text": text,
                "picUrl": pic_url,
                "messageUrl": message_url
            }
        }
        response = request(url=self.webhook_url, json=payload, headers=self.headers, method="POST")
        logger.debug(f"send_link发送钉钉消息的响应数据:{response.json()}")
        return response

    def send_markdown(self, title, text, mobiles=None, is_at_all=False):
        """
        发送markdown消息
        目前仅支持md语法的子集,如标题,引用,文字加粗,文字斜体,链接,图片,无序列表,有序列表
        :param title: 消息标题,首屏回话透出的展示内容
        :param text: 消息内容,markdown格式
        :param mobiles: 被艾特的用户的手机号码,格式是列表,注意需要在text里面添加@人的手机号码
        :param is_at_all: 是否艾特所有人,布尔类型,true为艾特所有人,false为不艾特
        """
        if mobiles:
            if isinstance(mobiles, list):
                payload = {
                    "msgtype": "markdown",
                    "markdown": {
                        "title": title,
                        "text": text
                    },
                    "at": {
                        "atMobiles": mobiles,
                        "isAtAll": False
                    }
                }
                for mobile in mobiles:
                    payload["markdown"]["text"] += f" @{mobile}"
            else:
                raise TypeError("mobiles类型错误 不是list类型.")
        else:
            payload = {
                "msgtype": "markdown",
                "markdown": {
                    "title": title,
                    "text": text
                },
                "at": {
                    "atMobiles": "",
                    "isAtAll": is_at_all
                }
            }
        response = request(url=self.webhook_url, json=payload, headers=self.headers, method="POST")
        logger.debug(f"send_markdown发送钉钉消息的响应数据:{response.json()}")
        return response

    def send_action_card_single(self, title, text, single_title, single_url, btn_orientation=0):
        """
        发送消息卡片(整体跳转ActionCard类型)
        :param title: 消息标题
        :param text: 消息内容,md格式消息
        :param single_title: 单个按钮的标题
        :param single_url: 点击singleTitle按钮后触发的URL
        :param btn_orientation: 0-按钮竖直排列,1-按钮横向排列
        """
        payload = {
            "msgtype": "actionCard",
            "actionCard": {
                "title": title,
                "text": text,
                "singleTitle": single_title,
                "singleURL": single_url,
                "btnOrientation": btn_orientation,
            }

        }
        response = request(url=self.webhook_url, json=payload, headers=self.headers, method="POST")
        logger.debug(f"send_action_card_single发送钉钉消息的响应数据:{response.json()}")
        return response

    def send_action_card_split(self, title, text, btns, btn_orientation=0):
        """
        发送消息卡片(独立跳转ActionCard类型)
        :param title: 消息标题
        :param text: 消息内容,md格式消息
        :param btns: 列表嵌套字典类型,"btns": [{"title": "内容不错", "actionURL": "https://www.dingtalk.com/"}, ......]
        :param btn_orientation: 0-按钮竖直排列,1-按钮横向排列
        """
        payload = {
            "msgtype": "actionCard",
            "actionCard": {
                "title": title,
                "text": text,
                "btns": [],
                "btnOrientation": btn_orientation,
            }

        }
        for btn in btns:
            payload["actionCard"]["btns"].append({
                "title": btn.get("title"),
                "actionURL": btn.get("action_url")
            })
        response = request(url=self.webhook_url, json=payload, headers=self.headers, method="POST")
        logger.debug(f"send_action_card_split发送钉钉消息的响应数据:{response.json()}")
        return response

    def send_feed_card(self, links_msg):
        """
        发送多组消息卡片(FeedCard类型)
        :param links_msg: 列表嵌套字典类型,每一个字段包括如下参数:title(单条信息文本), messageURL(点击单条信息后的跳转链接), picURL(单条信息后面图片的url)
        """
        payload = {
            "msgtype": "feedCard",
            "feedCard": {
                "links": []
            }
        }
        for link in links_msg:
            payload["feedCard"]["links"].append(
                {
                    "title": link.get("title"),
                    "messageURL": link.get("messageURL"),
                    "picURL": link.get("picURL")
                }
            )
        response = request(url=self.webhook_url, json=payload, headers=self.headers, method="POST")
        logger.debug(f"send_feed_card发送钉钉消息的响应数据:{response.json()}")
        return response


if __name__ == '__main__':
    my_secret = '**********'
    my_url = 'https://oapi.dingtalk.com/robot/send?access_token=**********'

    dingding = DingTalkBot(secret=my_secret, webhook_url=my_url)
    dingding.send_text(content="发送钉钉消息的响应数据12", mobiles=['18163986265', "13263325383"], is_at_all=True)
    dingding.send_link(title="chytest", text="时代的长河", message_url="https://www.gitlink.org.cn/chenyh")
    dingding.send_markdown(title="test markdown",
                           text="# 一级标题 \n## 二级标题 \n> 引用文本  \n**加粗**  \n*斜体*  \n[百度链接](https://www.baidu.com)\n\n\n\n",
                           mobiles=['18774970063'])
    dingding.send_action_card_single(title="测试消息的标题",
                              text="### 乔布斯 20 年前想打造的苹果咖啡厅 Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
                              single_title="阅读全文", single_url="https://www.gitlink.org.cn/chenyh", btn_orientation=0)
    dingding.send_action_card_split(title="测试消息的标题", text="### 乔布斯 20 年前想打造的苹果咖啡厅 Apple Store",
                                    btns=[{"title": "内容不错", "actionURL": "https://www.dingtalk.com/"},
                                          {"title": "不感兴趣", "actionURL": "https://www.dingtalk.com/"}],
                                    btn_orientation=1)
    links = [
        {
            "title": "时代的火车向前开",
            "messageURL": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
            "picURL": "https://tse1-mm.cn.bing.net/th/id/OIP-C.nelCIKYD30NJW6W68-ZHxAHaJA?pid=ImgDet&rs=1"
        },
        {
            "title": "工作厌倦了怎么办?",
            "messageURL": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
            "picURL": "https://tse1-mm.cn.bing.net/th/id/OIP-C.nelCIKYD30NJW6W68-ZHxAHaJA?pid=ImgDet&rs=1"
        },
        {
            "title": "也许你对于选用的字体的选择应该更慎重一点",
            "messageURL": "https://www.dingtalk.com/s?__biz=MzA4NjMwMTA2Ng==&mid=2650316842&idx=1&sn=60da3ea2b29f1dcc43a7c8e4a7c97a16&scene=2&srcid=09189AnRJEdIiWVaKltFzNTw&from=timeline&isappinstalled=0&key=&ascene=2&uin=&devicetype=android-23&version=26031933&nettype=WIFI",
            "picURL": "https://tse1-mm.cn.bing.net/th/id/OIP-C.nelCIKYD30NJW6W68-ZHxAHaJA?pid=ImgDet&rs=1"
        },

    ]
    dingding.send_feed_card(links)



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

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

相关文章

从Facebook到Diem币:社交媒体巨头在加密货币领域的演变

大家都知道Facebook是一个全球知名的社交媒体平台,几乎每个人都在其中与朋友分享照片、发表状态或留言。 然而,随着时间的推移,Facebook不仅仅局限于社交交流,而是逐渐涉足更广阔的领域,其中之一就是加密货币。在本文…

三菱FX5U系列PLC本体自带模拟量输入输出使用方法介绍及示例

三菱FX5U系列PLC本体自带模拟量输入输出使用方法介绍及示例 如下图所示,三菱FX5U本体自带2路模拟量输入和1路模拟量输出,打开CPU左侧的保护盖板即可看到接线端子的位置, 如下图所示,查看手册,可以看到模拟量输入的相关说明: 输入:DC0-10V 软元件:SD6020(通道1)、SD…

杂记(二)2023.5.11

目录 流程图应该如何绘制? coverage 泡鲁达拿铁是什么?泡鲁达英文是什么?什么来历? 为什么我的泡鲁达咖啡会送我一些面包条呢? 介绍一下欧内斯特 梅的《历史的教训》 介绍一下陆奥宗光 介绍一下陆奥宗光的《蹇蹇录…

多处最优服务次序问题——算法设计与分析(C实现)

问题描述:设有n个顾客同时等待一项服务。顾客i需要的服务时间为,共有s处可以提供此项服务。应该如何安排n个顾客的服务次序,才能使平均等待时间达到最小?平均等待时间是n个顾客的等待服务时间的总和除以n。 算法设计:对…

数据库软件基础搭建的思考(WAMPserver)

本文的目的是介绍关于构建个人小型医学数据库的软件基础方面的一些实践和思考,做到局域网访问,乃至外网访问。 wampserver简要介绍 WampServer是一款由法国人开发的Apache Web服务器、PHP解释器以及MySQL数据库的整合软件包。它可以在Windows操作系统…

设备联网调试三板斧

在实际的工业互联网项目中,设备联网所占的比重越来越大。有的一期项目为了简单快速上线,让客户直观体会到工业互联网的效果,直接会把设备联网放在一期项目的重点。那么在做此类项目时,设备联网调试就显得尤为重要。专业的厂家和工…

DosBox在winserver2016云桌面最大化界面无法铺满全屏的问题剖析

现象引出和问题猜想 有一款用户软件叫DosBox,在实体机win11的时候最大化的时候,程序界面可以铺满全屏,但是在winserver2016云桌面进行最大化的时候,最大化的时候,界面无法铺满全屏: (实体机最大…

递归到动态规划:省去枚举行为

如果在动态规划的过程中没有枚举行为,那严格位置依赖和傻缓存的方式并没有太大区别,但是当有枚举行为的时候(一个位置依赖于多个位置),那严格位置依赖是有优化空间的,枚举行为也许可以省去,题目…

Docker(一)

Docker Docker简述 传统虚拟机技术基于安装在主操作系统上的虚拟机管理系统,创建虚拟机(虚拟出各种硬件),在虚拟机上安装从操作系统,在从操作系统中安装和部署各种应用。这种方式占用资源很大并且步骤冗余。在此基础之…

跟着LearnOpenGL学习2--三角形绘制

文章目录 一、前言二、图形渲染管线2.1、顶点数据2.2、顶点着色器2.3、形状(图元)装配2.4、几何着色器2.5、光栅化2.6、片段着色器2.7、测试与混合 三、渲染流程3.1、顶点输入3.2、顶点着色器3.3、编写、编译着色器3.4、片段着色器3.5、着色器程序3.6、链…

专业专注,极致体验,高端隐形智能晾衣机品牌邦先生官宣浙江卫视知名主持人沈涛为品牌代言人

5月11日,高端隐形晾衣架领导品牌邦先生正式宣布,浙江卫视知名主持人沈涛为品牌代言人,以更高标准的晾晒,共同迎接智能晾晒大时代,用科技力量创造美好智慧家居生活。 专业实力品牌邦先生王牌主持沈涛 作为浙江卫视的“王…

GPT Prompt(提示词)写法与教程,相关站点与工具

文章目录 1、Prompt工程师(提示工程师)2、提示词教程3、提示词工具(中文)4、提示词工具(英文) 1、Prompt工程师(提示工程师) Prompt工程师,也称为AI提示工程师&#xff…

纸质文件怎么扫描成电子版?简单小妙招快来拿捏

随着科技的发展,越来越多的人将纸质文件转换为电子版,以方便存储和共享。本文将介绍纸质文件如何扫描成电子版,以及如何利用手机进行扫描转换。 纸质文件扫描成电子版 将纸质文件扫描成电子版是一种常见的方式。首先,您需要一台扫…

JAVA-代码块和内部类

文章目录 目录 文章目录 前言 1.代码块 1.1什么是代码块? 1.2代码块的分类及作用: 1.静态代码块 2.成员代码块(又叫做构造代码块) 3.局部代码块 2.内部类 2.1 什么是内部类? 2.2 内部类的分类 1.成员内部类 2.静态内部类 3.匿名内部类 4.局部内部类 总结 前言 作者简介:我是最…

MySQL索引优化(超详细)

Mysql索引优化 1 索引介绍 1.1 什么时MySQL的索引 ​ MySQL官方对于索引的定义:索引是帮助MySQL高效获取数据的数据结构。 ​ MySQL在存储数据之外,数据库系统中还维护着满足特定查找算法的数据结构,这些数据结构以某种引用(指向)表中的数据&#xff…

Prometheus

Prometheus简介 prometheus是一个监控、告警的开源系统。Prometheus收集并存储时序的指标数据。指标数据存储伴随一个timestamp和可选择key-values 队列标签 Prometheus特性: 一个时序的多维数据模型,被mertic name和 key/value pairs标签唯一定义 P…

将DenseNet换成Resnet——更换深度学习骨干网络

最近我在学习一个手写公式识别的网络,这个网络的backbone使用的是DenseNet,我想将其换成ResNet 至于为什么要换呢,因为我还没换过骨干网络,就像单纯拿来练练手,增加我对网络的熟悉程度,至于会不会对模型的性…

【时间序列数据挖掘】ARIMA模型

目录 0、前言 一、移动平均模型MA 二、自回归模型AR 三、自回归移动平均模型ARMA 四、自回归移动平均模型ARIMA 【总结】 0、前言 传统时间序列分析模型: ARIMA模型是一个非常灵活的模型,对于时间序列的好多特征都能够进行描述,比如说平…

5.11黄金最新行情走势分析及多空交易策略

近期有哪些消息面影响黄金走势?本周黄金多空该如何研判? ​黄金消息面解析:北京时间周三(5月10日)20:30,美国劳工部公布4月通胀报告,整体与核心CPI年率都走低,支持美联储6月份保持利率不变。数据显示&…

RabbitMQ详解(一):Linux安装

消息队列概念 消息队列是在消息的传输过程中保存消息的容器。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。 常见的消息队列 RabbitMQ 基于AMQP(高级消息队列协议)基础上…