教你如何使用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 禁止函数修改列表的实现方法
Aug 03 Python
python+opencv实现的简单人脸识别代码示例
Nov 14 Python
解决Python网页爬虫之中文乱码问题
May 11 Python
python中的二维列表实例详解
Jun 19 Python
TensorFlow实现Logistic回归
Sep 07 Python
Python模拟浏览器上传文件脚本的方法(Multipart/form-data格式)
Oct 22 Python
django框架CSRF防护原理与用法分析
Jul 22 Python
python设置随机种子实例讲解
Sep 12 Python
Python帮你识破双11的套路
Nov 11 Python
python opencv实现gif图片分解的示例代码
Dec 13 Python
python之生成多层json结构的实现
Feb 27 Python
python实现简单聊天功能
Jul 07 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
Centos7安装swoole扩展操作示例
2020/03/26 PHP
PHP利用curl发送HTTP请求的实例代码
2020/07/09 PHP
图片自动缩小 点击放大
2008/07/07 Javascript
js日历功能对象
2012/01/12 Javascript
JQUERY的AJAX请求缓存里的数据问题处理
2016/02/23 Javascript
jQuery验证插件validate使用方法详解
2020/09/13 Javascript
原生js实现回复评论功能
2017/01/18 Javascript
基于jquery日历价格、库存等设置插件
2020/07/05 jQuery
Javascript之图片的延迟加载的实例详解
2017/07/24 Javascript
关于angularJs清除浏览器缓存的方法
2017/11/28 Javascript
利用Javascript获取选择文本所在的句子详解
2017/12/03 Javascript
JS实现简单的星期格式转换功能示例
2018/07/23 Javascript
vue中使用sessionStorage记住密码功能
2018/07/24 Javascript
Vue2.2.0+新特性整理及注意事项
2018/08/22 Javascript
对angular 实时更新模板视图的方法$apply详解
2018/10/09 Javascript
vue-cli3 项目从搭建优化到docker部署的方法
2019/01/28 Javascript
详解js location.href和window.open的几种用法和区别
2019/12/02 Javascript
vue图片上传组件使用详解
2019/12/23 Javascript
微信小程序实现选择地址省市区三级联动
2020/06/21 Javascript
[50:21]Liquid vs Winstrike 2018国际邀请赛小组赛BO2 第二场
2018/08/19 DOTA
python中的编码知识整理汇总
2016/01/26 Python
Python tkinter模块中类继承的三种方式分析
2017/08/08 Python
Python中实现最小二乘法思路及实现代码
2018/01/04 Python
Python中GIL的使用详解
2018/10/03 Python
解决pandas .to_excel不覆盖已有sheet的问题
2018/12/10 Python
python超时重新请求解决方案
2019/10/21 Python
Python进程间通信multiprocess代码实例
2020/03/18 Python
Opencv图像处理:如何判断图片里某个颜色值占的比例
2020/06/03 Python
详解h5页面在不同ios设备上的问题总结
2019/03/01 HTML / CSS
荷兰网上鞋店:Ziengs.nl
2017/01/02 全球购物
高中毕业自我鉴定
2013/12/16 职场文书
求职简历的自我评价
2014/01/31 职场文书
服装设计师求职信
2014/06/04 职场文书
预备党员自我批评思想汇报
2014/10/10 职场文书
有限公司股东合作协议书
2014/10/29 职场文书
PostGIS的安装与入门使用指南
2022/01/18 PostgreSQL