教你如何使用Python开发一个钉钉群应答机器人


Posted in Python onJune 21, 2021

前提

搭建钉钉应答机器人,需要先准备或拥有以下权限:

  • 钉钉企业的管理员或子管理员(如果不是企业管理员,可以自己创建一个企业,很方便的)
  • 有公网通信地址(内网穿透也可以);

钉钉群机器人开发文档:https://developers.dingtalk.com/document/app/overview-of-group-robots

创建「机器人」应用

登录「钉钉开发者后台」,选择「应用开发」——「企业内部开发」—— 「机器人」

教你如何使用Python开发一个钉钉群应答机器人

输入好机器人的基本信息之后,就会生成创建一个「钉钉机器人」

教你如何使用Python开发一个钉钉群应答机器人

我们的后端应用通过其提供的「AgentId」、「AppKey」、「AppSecret」就能够与钉钉机器人进行通信。

接收消息

在钉钉机器人的设定中,当用户@机器人时,钉钉会通过机器人开发者的服务器地址,用 POST 请求方法把消息内容发送出去,其 HTTP header 如下所示:

{
  "Content-Type": "application/json; charset=utf-8",
  "timestamp": "1577262236757",
  "sign":"xxxxxxxxxx"
}

其中,timestamp是消息发送时的时间戳,sign是签名值,我们需要对这两个值进行校验。

如果timestamp与系统当前时间相差1小时以上,则为非法请求。

如果sign签名值与后台计算的值不一样,也为非法请求。

其中sign签名值的计算方法为:header中的timestamp + “\n” + 机器人的appSecret当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,得到最终的签名值。

其 Python 实现代码如下所示:

import hmac
import hashlib
import base64

timestamp = '1577262236757'
app_secret = 'this is a secret'
app_secret_enc = app_secret.encode('utf-8')
string_to_sign = '{}\n{}'.format(timestamp, app_secret)
string_to_sign_enc = string_to_sign.encode('utf-8')
hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
sign = base64.b64encode(hmac_code).decode('utf-8')
print(sign)

其发送的消息格如下所示:

{
    "conversationId": "xxx",
    "atUsers": [
        {
            "dingtalkId": "xxx",
            "staffId":"xxx"
        }
    ],
    "chatbotCorpId": "dinge8a565xxxx",
    "chatbotUserId": "$:LWCP_v1:$Cxxxxx",
    "msgId": "msg0xxxxx",
    "senderNick": "杨xx",
    "isAdmin": true,
    "senderStaffId": "user123",
    "sessionWebhookExpiredTime": 1613635652738,
    "createAt": 1613630252678,
    "senderCorpId": "dinge8a565xxxx",
    "conversationType": "2",
    "senderId": "$:LWCP_v1:$Ff09GIxxxxx",
    "conversationTitle": "机器人测试-TEST",
    "isInAtList": true,
    "sessionWebhook": "https://oapi.dingtalk.com/robot/sendBySession?session=xxxxx",
    "text": {
        "content": " 你好"
    },
    "msgtype": "text"
}

其中,一些参数的说明如下图所示:

教你如何使用Python开发一个钉钉群应答机器人

教你如何使用Python开发一个钉钉群应答机器人

我们接收到钉钉的消息后,可以根据实际的业务需求解析出相应字段的数据来进行处理。

响应消息

钉钉机器人支持我们通过「text」、「Markdown」、「整体跳转actionCard」、「独立跳转actionCard」和「feedCard」这5种消息类型发送消息到群里。

下面我们通过实际的代码来展示接收钉钉机器人的消息,以及发送 5 种消息类型到钉钉群里。

创建一个后端应用

接下来,我们通过创建一个 Django 应用来接收的处理用户发送给钉钉机器人的消息。

首先,创建一个 Django 项目和应用:

django-admin startproject DdRobot
python manage.py startapp app_robot

教你如何使用Python开发一个钉钉群应答机器人

然后打开 “C:\DdRobot\DdRobot\settings.py” 文件,修改 ALLOWED_HOSTS 变量:

ALLOWED_HOSTS = ['*']

将 app_robot 添加到 INSTALLED_APPS 变量列表中:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app_robot',
]

创建校验时间戳和签名函数

因为钉钉机器人会在请求头里面传入timestamp时间戳和sign签名供我们对请求的合法性进行校验,所以为了机器人的安全,我们需要编写 2 个函数对它们进行校验(在DdRobot/app_robot/views.py文件中进行)。

首先,是时间戳的校验:

def check_timestamp(timestamp):
    now_timestamp = int(time.time()*1000)
    if now_timestamp - int(timestamp) > 3600000:
        return False
    else:
        return True

然后是签名值的校验,签名值的计算方法和示例代码钉钉已经提供,我们借用即可:

def check_sign(timestamp,sign):
    import hmac
    import hashlib
    import base64

    # now_timestamp = str(int(time.time()*1000))
    app_secret = 'teTLGS3xZVLp6Z99mXvgVpINOUyJqFsKJ3jLb7crFdjRsJ3_77E-kxhlIbBGbNjX'
    app_secret_enc = app_secret.encode('utf-8')
    string_to_sign = '{}\n{}'.format(timestamp, app_secret)
    string_to_sign_enc = string_to_sign.encode('utf-8')
    hmac_code = hmac.new(app_secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
    new_sign = base64.b64encode(hmac_code).decode('utf-8')
    # print(sign)
    # print(new_sign)
    if sign == new_sign:
        return True
    else:
        return False

对于这 2 个值,校验成功我们都返回 True,校验失败我们都返回 False。

创建视图函数

接着,我们创建一个视图函数,用来接收钉钉传输过来的消息,以及响应给钉钉。

@csrf_exempt
def resp_dd(request):
	pass

在 resp_dd() 函数中,首先从请求头中读取钉钉传输过来的时间戳和签名值,然后进行校验:

@csrf_exempt
def resp_dd(request):
    timestamp = request.headers.get('timestamp','')
    sign = request.headers.get('sign','')
    # 校验时间戳
    if check_timestamp(timestamp) is False:
        return JsonResponse({'status':False,'data':'非法请求'})
    # 校验签名
    if check_sign(timestamp,sign) is False:
        return JsonResponse({'status':False,'data':'非法请求'})

若是时间戳和签名值校验无误,我们继续从请求 body 里面获取消息信息:

@csrf_exempt
def resp_dd(request):
    timestamp = request.headers.get('timestamp','')
    sign = request.headers.get('sign','')
    # 校验时间戳
    if check_timestamp(timestamp) is False:
        return JsonResponse({'status':False,'data':'非法请求'})
    # 校验签名
    if check_sign(timestamp,sign) is False:
        return JsonResponse({'status':False,'data':'非法请求'})
    body = json.loads(request.body)
    # 获取用户id
    # user_id = body['senderStaffId'] 机器人上线后才会返回
    user_id = body['senderId']
    # 获取发送的消息
    msg_type = body['msgtype']
    if msg_type == 'text':
        content = body['text']['content']

目前钉钉机器人只支持text文本内容的消息接收,所以在此处我们只对消息类型为text的消息进行处理。

获取到钉钉机器人发送过来的信息之后,我们就可以根据自己的业务逻辑进行处理,然后返回特定的消息类型了。

在这里,我们只对消息进行简单的处理:

  • 当发送来的消息文本为text时,机器人回复文本消息;
  • 当发送来的消息文本为markdown时,机器人回复一个 Markdown 的示例消息;
  • 当发送来的消息文本为整体跳转时,机器人回复一个「整体跳转卡片」的示例消息;
  • 当发送来的消息文本为独立跳转时,机器人回复一个「独立跳转卡片」的示例消息;
  • 当发送来的消息文本为feed时,机器人回复一个「feedCard」的示例消息;

先来定义 5 个不同消息类型的响应格式。

文本消息类型

# 响应文字
    resp_text = {
        "at": {
            "atUserIds": [
                user_id
            ],
            "isAtAll": False
        },
        "text": {
            "content": "你刚刚发的消息是:[{}]".format(content)
        },
        "msgtype": "text"
    }

Markdown消息类型:

# 响应Markdown
    resp_markdown = {
        "msgtype": "markdown",
            "markdown": {
            "title":"州的先生机器人助理",
            "text": "## 这是什么? \n 这是一个钉钉机器人 \n ![](https://zmister.com/wp-content/uploads/2019/06/login_logo.png)"
        },
        "at": {
            "atUserIds": [
              user_id
            ],
            "isAtAll": False
        }
    }

整体跳转卡片消息类型:

# 响应整体跳转actionCard
    resp_actioncard = {
        "msgtype": "actionCard",
        "actionCard": {
            "title": "州的先生 Python 实战教程合集", 
            "text": "![](https://zmister.com/wp-content/uploads/2019/06/login_logo.png) \n #### 州的先生 Python 实战教程合集 \n\n 学习Python的一个好方法就是用实际的项目来熟练语言",
            "singleTitle" : "阅读全文",
            "singleURL" : "http://mrdoc.zmister.com"
        }
    }

独立跳转卡片消息类型:

resp_actioncard_2 = {
        "msgtype": "actionCard",
        "actionCard": {
            "title": "州的先生 Python 实战教程合集", 
            "text": "![](https://zmister.com/wp-content/uploads/2019/06/login_logo.png) \n #### 州的先生 Python 实战教程合集 \n\n 学习Python的一个好方法就是用实际的项目来熟练语言",
            "hideAvatar": "0", 
            "btnOrientation": "0", 
            "btns": [
                {
                    "title": "去看看", 
                    "actionURL": "http://mrdoc.zmister.com"
                }, 
                {
                    "title": "不感兴趣", 
                    "actionURL": "https://zmister.com/"
                }
            ]
        }
    }

Feed卡片消息类型:

# 响应feedCard
    resp_feedcard = {
        "msgtype": "feedCard",
        "feedCard": {
            "links": [
                {
                    "title": "时代的火车向前开1", 
                    "messageURL": "http://mrdoc.zmister.com", 
                    "picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
                },
                {
                    "title": "时代的火车向前开2", 
                    "messageURL": "https://zmister.com/", 
                    "picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
                }
            ]
        }
    }

其他的消息响应空:

# 响应空,不回复
    resp_empty = {
        "msgtype": "empty"
    }

定义好几个消息响应类型数据后,我们对获取到的 content 变量进行判断返回响应即可:

if content[1:] == 'text':
        return JsonResponse(resp_text)
    elif content[1:] == 'markdown':
        return JsonResponse(resp_markdown)
    elif content[1:] == '整体跳转':
        return JsonResponse(resp_actioncard)
    elif content[1:] == '独立跳转':
        return JsonResponse(resp_actioncard_2)
    elif content[1:] == 'feed':
        return JsonResponse(resp_feedcard)
    else:
        return JsonResponse(resp_empty)

这样,我们这个钉钉机器人的后端处理函数就写好了。

配置路由

写好视图函数之后,我们配置一下这个函数的 URL 路由。

在 “C:\DdRobot\DdRobot\urls.py” 文件中把内容修改为如下代码所示:

from django.contrib import admin
from django.urls import path
from app_robot import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('dd_robot/',views.resp_dd, name="resp_dd"),
]

这样 http://ip地址/dd_robot/ 就是钉钉机器人的消息接收地址。

配置钉钉机器人

回到钉钉开发者平台的网页,在钉钉机器人的「开发管理」页面,我们需要把服务器的出口IP 和钉钉机器人的消息接收地址填写好:

教你如何使用Python开发一个钉钉群应答机器人

调试钉钉机器人

在配置好机器人的「服务器出口IP」与「消息接收地址」之后,我们点击网页菜单的「版本管理与发布」,点击「调试按钮」,进入到钉钉机器人的调试群:

教你如何使用Python开发一个钉钉群应答机器人

这回在「钉钉机器人名称-TEST」的群里面添加创建的钉钉机器人:

教你如何使用Python开发一个钉钉群应答机器人

我们可以在这个群里面@创建的群机器人进行测试:

教你如何使用Python开发一个钉钉群应答机器人

在测试没问题之后,我们就可以点击「上线」按钮。钉钉机器人上线之后,就可以在钉钉群内添加这个机器人。

教你如何使用Python开发一个钉钉群应答机器人

这样,我们就实现了从 0 到 1 使用 Python 开发钉钉群机器人。

基本的框架和流程大抵如此,具体的业务逻辑则需要根据不同的需求进行额外处理。比如:

查询天气,就得解析消息中的城市,然后请求天气接口获取天气数据,进行消息的响应;

淘宝客,就得解析消息中的文本,进行分词或其他处理,再查询数据库中的商品优惠券数据或是直接请求淘客接口获取商品优惠券数据;

员工绩效,就得接入钉钉的应用开发,借助钉钉开发的用户接口进行数据查询和响应。

到此这篇关于教你如何使用Python开发一个钉钉群应答机器人的文章就介绍到这了,更多相关Python开发钉钉群应答机器人内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
删除目录下相同文件的python代码(逐级优化)
May 25 Python
Python科学计算之NumPy入门教程
Jan 15 Python
python+pygame简单画板实现代码实例
Dec 13 Python
10分钟教你用Python实现微信自动回复功能
Nov 28 Python
Python用Try语句捕获异常的实例方法
Jun 26 Python
python实现两个经纬度点之间的距离和方位角的方法
Jul 05 Python
Python Django框架url反向解析实现动态生成对应的url链接示例
Oct 18 Python
python argparser的具体使用
Nov 10 Python
Python数组拼接np.concatenate实现过程
Apr 18 Python
Pytorch十九种损失函数的使用详解
Apr 29 Python
Python pandas对excel的操作实现示例
Jul 21 Python
python热力图实现的完整实例
Jun 25 Python
详解Python requests模块
Jun 21 #Python
Python pandas读取CSV文件的注意事项(适合新手)
python简单验证码识别的实现过程
Python pygame实现中国象棋单机版源码
Python并发编程实例教程之线程的玩法
Jun 20 #Python
python迷宫问题深度优先遍历实例
Jun 20 #Python
Python虚拟环境virtualenv是如何使用的
You might like
phpexcel导出excel的颜色和网页中的颜色显示不一致
2012/12/11 PHP
PHP判断上传文件类型的解决办法
2015/10/20 PHP
[原创]PHP简单开启curl的方法(测试可行)
2016/01/11 PHP
Add a Picture to a Microsoft Word Document
2007/06/15 Javascript
JQuery实现倒计时按钮具体方法
2013/11/14 Javascript
JavaScript避免代码的重复执行经验技巧分享
2014/04/17 Javascript
jQuery解决$符号命名冲突
2016/06/18 Javascript
js的各种排序算法实现(总结)
2016/07/23 Javascript
JS访问DOM节点方法详解
2016/11/29 Javascript
微信浏览器禁止页面下拉查看网址实例详解
2017/06/28 Javascript
基于node.js制作简单爬虫教程
2017/06/29 Javascript
jQuery实现base64前台加密解密功能详解
2017/08/29 jQuery
无限循环轮播图之运动框架(原生JS实现)
2017/10/01 Javascript
图片懒加载imgLazyLoading.js使用详解
2020/09/15 Javascript
详解刷新页面vuex数据不消失和不跳转页面的解决
2018/01/30 Javascript
详解Immutable及 React 中实践
2018/03/01 Javascript
React Native使用fetch实现图片上传的示例代码
2018/03/07 Javascript
layui前端框架之table表数据的刷新方法
2018/08/17 Javascript
js尾调用优化的实现
2019/05/23 Javascript
[07:27]DOTA2卡尔工作室 英雄介绍水晶室女篇
2013/06/21 DOTA
分析Python的Django框架的运行方式及处理流程
2015/04/08 Python
遍历python字典几种方法总结(推荐)
2016/09/11 Python
Python列表(List)知识点总结
2019/02/18 Python
详解如何使用CSS3中的结构伪类选择器和伪元素选择器
2020/01/06 HTML / CSS
美国知名艺术画网站:Art.com
2017/02/09 全球购物
路由表示做什么用的?在linux环境中怎么来配置一条默认路由?
2013/06/07 面试题
班主任工作经验材料
2014/02/02 职场文书
外贸员简历中的自我评价
2014/03/04 职场文书
数控专业毕业生自荐信范文
2014/03/04 职场文书
低碳日宣传活动总结
2014/07/09 职场文书
商场周年庆活动方案
2014/08/19 职场文书
电子商务专业求职信范文
2015/03/19 职场文书
2016关于读书活动的心得体会
2016/01/14 职场文书
汽车销售合同文本
2019/08/08 职场文书
Pandas数据类型之category的用法
2021/06/28 Python
PHP中国际化的字符串排序和比较对象详解
2021/08/23 PHP