diff --git a/README.rst b/README.rst index e3b5818..a7f99ed 100644 --- a/README.rst +++ b/README.rst @@ -9,9 +9,9 @@ 2、聚合Trello、JIRA等项目协调服务,实现项目信息同步; -3、机器人支持Webhook自定义接入,就可以实现更多可能性,例如:将运维报警、自动化测试结果报告、工作&生活日程安排(上班打卡、下班吃饭、健身、读书、生日、纪念日...)的提醒; +3、机器人支持Webhook自定义接入,就可以实现更多可能性,例如:将运维报警、产品数据、自动化测试报告、工作&生活日程安排(上班打卡、下班吃饭、健身、读书、生日、纪念日...)的提醒; -目前自定义机器人支持文本(text)、链接(link)、markdown三种消息格式,五种消息类型,详细信息请参考\ `自定义机器人官方文档 `__ +目前自定义机器人支持文本(text)、链接(link)、markdown三种消息格式,五种消息类型,详细信息请参考\ `自定义机器人官方文档 `__ 二、安装使用 ============ @@ -46,12 +46,13 @@ - 支持image表情消息; - 支持Markdown消息; - 支持ActionCard消息; -- 支持消息发送失败时自动通知(默认fail_notice=False不通知,开发者可根据返回的消息发送结果自行判断处理) -- 支持设置消息链接打开方式(默认pc_slide=False,跳转至浏览器打开,pc_slide=True,则在PC端侧边栏打开) -- 支持钉钉官方消息发送频率限制限制:每个机器人每分钟最多发送20条; +- 支持消息发送失败时自动通知(默认fail_notice=False不通知,开发者可直接根据返回的消息发送结果自行处理) +- 支持设置消息链接打开方式(默认pc_slide=False跳转至浏览器打开;pc_slide=True则在PC端侧边栏打开) +- 支持钉钉官方消息发送频率限制,即每个机器人每分钟最多发送20条,不用担心触发限流; +- 支持新版钉钉机器人加密设置; - 支持Python2、Python3; -- 支持钉钉企业内部机器人\ `自定义outgoing机器人消息发送 `__; -- 支持最新版钉钉机器人加密设置密钥验证; +- 支持钉钉企业内部机器人\ `即outgoing机器人的消息发送 `__; + 三、各消息类型使用示例 ====================== @@ -171,9 +172,10 @@ 四、常见注意事项 =========================== -- 1、at_mobiles列表上的手机号默认自动添加到消息文本末尾,可将参数改为is_auto_at=False取消自动化添加,在消息文本自定义@的位置,支持同时@多个手机号,以便突出对应的人去关注对应的内容; -- 2、图片链接是Http,在网页版钉钉无法正常显示,在客户端则可以,需要更改为使用Https; -- 3、消息链接打开方式可以在初始化机器人时设置(默认pc_slide=False,跳转至浏览器打开,pc_slide=True,则在PC端侧边栏打开); +- 1、at_mobiles列表上的手机号默认自动添加到消息文本末尾,才有@的效果,如需自定义在消息文本@的位置,可将参数is_auto_at设置为False即可,同时支持@多个手机号; +- 2、如果钉钉机器人消息中的图片链接使用Http协议,可以在钉钉客户端正常加载显示,但无法在钉钉网页版显示,需要更改为Https协议; +- 3、钉钉机器人消息中的链接默认跳转至浏览器打开(即:pc_slide=False),如果需要直接在客户端侧边栏打开,设置pc_slide为True即可; +- 4、当前钉钉自定义机器人官方尚不支持应答机制(即在群里成员在聊天@机器人),此功能需要使用钉钉企业内部开发机器人实现(即Outgoing机器人; diff --git a/dingtalkchatbot/__about__.py b/dingtalkchatbot/__about__.py index a938888..111f3a2 100644 --- a/dingtalkchatbot/__about__.py +++ b/dingtalkchatbot/__about__.py @@ -1,7 +1,7 @@ __title__ = 'DingtalkChatbot' __description__ = '一个钉钉自定义机器人消息的Python封装库' __url__ = 'https://github.com/zhuifengshen/DingtalkChatbot' -__version__ = '1.5.3' +__version__ = '1.5.7' __author__ = 'devin' __author_email__ = '1324556701@qq.com' __license__ = 'MIT' diff --git a/dingtalkchatbot/chatbot.py b/dingtalkchatbot/chatbot.py index 79b0201..774f279 100644 --- a/dingtalkchatbot/chatbot.py +++ b/dingtalkchatbot/chatbot.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # _*_ coding:utf-8 _*_ # create time: 07/01/2018 11:35 -__author__ = 'Devin -- http://zhangchuzhao.site' +__author__ = 'Devin - https://zhuifengshen.github.io' import re import sys @@ -63,7 +63,7 @@ def __init__(self, webhook, secret=None, pc_slide=False, fail_notice=False): :param fail_notice: 消息发送失败提醒,默认为False不提醒,开发者可以根据返回的消息发送结果自行判断和处理 """ super(DingtalkChatbot, self).__init__() - self.headers = {'Content-Type': 'application/json; charset=utf-8'} + self.headers = {'Content-Type': 'application/json; charset=utf-8', 'Connection': 'close'} # fix issue #53 self.queue = queue.Queue(20) # 钉钉官方限流每分钟发送20条信息 self.webhook = webhook self.secret = secret @@ -112,9 +112,9 @@ def send_text(self, msg, is_at_all=False, at_mobiles=[], at_dingtalk_ids=[], is_ text类型 :param msg: 消息内容 :param is_at_all: @所有人时:true,否则为false(可选) - :param at_mobiles: 被@人的手机号(注意:可以在msg内容里自定义@手机号的位置,也支持同时@多个手机号,可选) - :param at_dingtalk_ids: 被@人的dingtalkId(可选) - :param is_auto_at: 是否自动在msg内容末尾添加@手机号,默认自动添加,可设置为False取消(可选) + :param at_mobiles: 被@用户的手机号 + :param at_dingtalk_ids: 被@用户的UserId(企业内部机器人可用,可选) + :param is_auto_at: 是否自动在msg内容末尾添加@手机号,默认自动添加,也可设置为False,然后自行在msg内容中自定义@手机号的位置,才有@效果,支持同时@多个手机号(可选) :return: 返回消息发送结果 """ data = {"msgtype": "text", "at": {}} @@ -136,14 +136,14 @@ def send_text(self, msg, is_at_all=False, at_mobiles=[], at_dingtalk_ids=[], is_ if at_dingtalk_ids: at_dingtalk_ids = list(map(str, at_dingtalk_ids)) - data["at"]["atDingtalkIds"] = at_dingtalk_ids + data["at"]["atUserIds"] = at_dingtalk_ids logging.debug('text类型:%s' % data) return self.post(data) def send_image(self, pic_url): """ - image类型(表情) + image类型 :param pic_url: 图片链接 :return: 返回消息发送结果 """ @@ -192,9 +192,9 @@ def send_markdown(self, title, text, is_at_all=False, at_mobiles=[], at_dingtalk :param title: 首屏会话透出的展示内容 :param text: markdown格式的消息内容 :param is_at_all: @所有人时:true,否则为:false(可选) - :param at_mobiles: 被@人的手机号(默认自动添加在text内容末尾,可取消自动化添加改为自定义设置,可选) - :param at_dingtalk_ids: 被@人的dingtalkId(可选) - :param is_auto_at: 是否自动在text内容末尾添加@手机号,默认自动添加,可设置为False取消(可选) + :param at_mobiles: 被@人的手机号 + :param at_dingtalk_ids: 被@用户的UserId(企业内部机器人可用,可选) + :param is_auto_at: 是否自动在text内容末尾添加@手机号,默认自动添加,也可设置为False,然后自行在text内容中自定义@手机号的位置,才有@效果,支持同时@多个手机号(可选) :return: 返回消息发送结果 """ if all(map(is_not_null_and_blank_str, [title, text])): @@ -220,7 +220,7 @@ def send_markdown(self, title, text, is_at_all=False, at_mobiles=[], at_dingtalk if at_dingtalk_ids: at_dingtalk_ids = list(map(str, at_dingtalk_ids)) - data["at"]["atDingtalkIds"] = at_dingtalk_ids + data["at"]["atUserIds"] = at_dingtalk_ids logging.debug("markdown类型:%s" % data) return self.post(data) @@ -326,8 +326,8 @@ def post(self, data): error_data = { "msgtype": "text", "text": { - "content": "[注意-自动通知]钉钉机器人消息发送失败,时间:%s,原因:%s,请及时跟进,谢谢!" % ( - time_now, result['errmsg'] if result.get('errmsg', False) else '未知异常') + "content": "[异常通知]钉钉机器人消息发送失败,失败时间:%s,失败原因:%s,要发送的消息:%s,请及时跟进,谢谢!" % ( + time_now, result['errmsg'] if result.get('errmsg', False) else '未知异常', post_data) }, "at": { "isAtAll": False @@ -361,7 +361,7 @@ def __init__(self, title, text, btns, btn_orientation=0, hide_avatar=0): if isinstance(btn, CardItem): btn_list.append(btn.get_data()) if btn_list: - btns = btn_list # 兼容:1、传入CardItem示例列表;2、传入数据字典列表 + btns = btn_list # 兼容:1、传入CardItem列表;2、传入数据字典列表 self.btns = btns def get_data(self): @@ -404,7 +404,7 @@ def get_data(self): class FeedLink(object): """ - FeedCard类型单条消息格式 + FeedCard类型单条消息格式(已废弃,直接使用 CardItem 即可) """ def __init__(self, title, message_url, pic_url): """ diff --git a/dingtalkchatbot/chatbot_test.py b/dingtalkchatbot/chatbot_test.py index 48aca23..146dc47 100644 --- a/dingtalkchatbot/chatbot_test.py +++ b/dingtalkchatbot/chatbot_test.py @@ -5,16 +5,20 @@ from dingtalkchatbot.chatbot import DingtalkChatbot, is_not_null_and_blank_str, ActionCard, FeedLink, CardItem -__author__ = 'Devin -- http://zhangchuzhao.site' - +__author__ = 'Devin - https://zhuifengshen.github.io' class TestDingtalkChatbot(unittest.TestCase): """DingtalkChatbot 测试用例""" @classmethod def setUpClass(cls): + # 1.无加签 cls.webhook = 'https://oapi.dingtalk.com/robot/send?access_token=77eb420ff2761ad516d974e1428c3e198b84faabc9c9ef8e86b2c71ac60bd0ea' cls.xiaoding = DingtalkChatbot(cls.webhook) + # 2.有加签 + #cls.webhook = 'https://oapi.dingtalk.com/robot/send?access_token=fab4f070e0214d2e3f7429acd18bc38848cc7043f9191ed1f96fa090ab25b943' + #cls.xiaoding = DingtalkChatbot(cls.webhook, secret='SEC225443235b43d49959eaca83b15b5b93ec747d662ad347a2b3483a7e67d8b96b') + def test_is_not_null_and_blank_str(self): """测试字符串不为空函数""" @@ -31,12 +35,12 @@ def test_send_text(self): def test_send_image(self): """测试发送表情图片消息函数""" - result = self.xiaoding.send_image(pic_url='http://uc-test-manage-00.umlife.net/jenkins/pic/flake8.png') + result = self.xiaoding.send_image(pic_url='http://www.sinaimg.cn/dy/slidenews/5_img/2013_28/453_28488_469248.jpg') self.assertEqual(result['errcode'], 0) def test_send_link(self): """测试发送链接消息函数""" - result = self.xiaoding.send_link(title='万万没想到,某小璐竟然...', text='故事是这样子的...', message_url='http://www.kwongwah.com.my/?p=454748', pic_url='https://pbs.twimg.com/media/CEwj7EDWgAE5eIF.jpg') + result = self.xiaoding.send_link(title='万万没想到,某小璐竟然...', text='故事是这样子的...', message_url='https://open.dingtalk.com/document/group/custom-robot-access', pic_url='http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png') self.assertEqual(result['errcode'], 0) def test_send_markdown(self): @@ -49,20 +53,20 @@ def test_send_markdown(self): self.assertEqual(result['errcode'], 0) def test_send_actioncard(self): - """测试发送整体跳转ActionCard消息功能(CardItem新API)""" - btns1 = [CardItem(title="查看详情", url="https://www.dingtalk.com/")] + """1.测试发送整体跳转ActionCard消息功能(基于CardItem新API)""" + btns1 = [CardItem(title="查看详情", url="https://open.dingtalk.com/document/group/custom-robot-access")] actioncard1 = ActionCard(title='万万没想到,竟然...', - text='![markdown](http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png) \n### 故事是这样子的...', + text='![markdown](http://www.sinaimg.cn/dy/slidenews/5_img/2013_28/453_28488_469248.jpg) \n### 故事是这样子的...', btns=btns1, btn_orientation=1, hide_avatar=1) result = self.xiaoding.send_action_card(actioncard1) self.assertEqual(result['errcode'], 0) - """测试发送单独跳转ActionCard消息功能""" - btns2 = [CardItem(title="支持", url="https://www.dingtalk.com/"), CardItem(title="反对", url="http://www.back china.com/news/2018/01/11/537468.html")] + """2.测试发送单独跳转ActionCard消息功能(基于CardItem新API)""" + btns2 = [CardItem(title="支持", url="https://www.dingtalk.com/"), CardItem(title="反对", url="https://www.baidu.com/")] actioncard2 = ActionCard(title='万万没想到,竟然...', - text='![markdown](http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png) \n### 故事是这样子的...', + text='![markdown](http://www.sinaimg.cn/dy/slidenews/5_img/2013_28/453_28488_469248.jpg) \n### 故事是这样子的...', btns=btns2, btn_orientation=1, hide_avatar=1) @@ -70,7 +74,7 @@ def test_send_actioncard(self): self.assertEqual(result['errcode'], 0) def test_send_actioncard_old_api(self): - """测试发送整体跳转ActionCard消息功能(数据列表btns旧API)""" + """ 1.测试发送整体跳转ActionCard消息功能(基于字典旧API)""" btns1 = [{"title": "查看详情", "actionURL": "https://www.dingtalk.com/"}] actioncard1 = ActionCard(title='万万没想到,竟然...', text='![markdown](http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png) \n### 故事是这样子的...', @@ -80,9 +84,9 @@ def test_send_actioncard_old_api(self): result = self.xiaoding.send_action_card(actioncard1) self.assertEqual(result['errcode'], 0) - """测试发送单独跳转ActionCard消息功能""" + """2.测试发送单独跳转ActionCard消息功能(基于字典旧API)""" btns2 = [{"title": "支持", "actionURL": "https://www.dingtalk.com/"}, - {"title": "反对", "actionURL": "http://www.back china.com/news/2018/01/11/537468.html"}] + {"title": "反对", "actionURL": "https://www.baidu.com/"}] actioncard2 = ActionCard(title='万万没想到,竟然...', text='![markdown](http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png) \n### 故事是这样子的...', btns=btns2, @@ -92,19 +96,19 @@ def test_send_actioncard_old_api(self): self.assertEqual(result['errcode'], 0) def test_send_feedcard(self): - """测试发送FeedCard类型消息功能(CardItem新API)""" - carditem1 = CardItem(title="氧气美女", url="https://www.dingtalk.com/", pic_url="http://pic1.win4000.com/wallpaper/2020-03-11/5e68b0557f3a6.jpg") - carditem2 = CardItem(title="氧眼美女", url="https://www.dingtalk.com/", pic_url="http://pic1.win4000.com/wallpaper/2020-03-11/5e68b0557f3a6.jpg") - carditem3 = CardItem(title="氧神美女", url="https://www.dingtalk.com/", pic_url="http://pic1.win4000.com/wallpaper/2020-03-11/5e68b0557f3a6.jpg") + """测试发送FeedCard类型消息功能(基于CardItem新API)""" + carditem1 = CardItem(title="氧气美女", url="https://www.dingtalk.com/", pic_url="http://www.sinaimg.cn/dy/slidenews/5_img/2013_28/453_28488_469248.jpg") + carditem2 = CardItem(title="氧眼美女", url="https://www.dingtalk.com/", pic_url="http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png") + carditem3 = CardItem(title="氧神美女", url="https://www.dingtalk.com/", pic_url="http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png") cards = [carditem1, carditem2, carditem3] result = self.xiaoding.send_feed_card(cards) self.assertEqual(result['errcode'], 0) def test_send_feedcard_old_api(self): - """测试发送FeedCard类型消息功能(FeedLink旧API)""" - feedlink1 = FeedLink(title="氧气美女", message_url="https://www.dingtalk.com/", pic_url="http://pic1.win4000.com/wallpaper/2020-03-11/5e68b0557f3a6.jpg") - feedlink2 = FeedLink(title="氧眼美女", message_url="https://www.dingtalk.com/", pic_url="http://pic1.win4000.com/wallpaper/2020-03-11/5e68b0557f3a6.jpg") - feedlink3 = FeedLink(title="氧神美女", message_url="https://www.dingtalk.com/", pic_url="http://pic1.win4000.com/wallpaper/2020-03-11/5e68b0557f3a6.jpg") + """测试发送FeedCard类型消息功能(基于FeedLink旧API)""" + feedlink1 = FeedLink(title="氧气美女", message_url="https://www.dingtalk.com/", pic_url="http://www.sinaimg.cn/dy/slidenews/5_img/2013_28/453_28488_469248.jpg") + feedlink2 = FeedLink(title="氧眼美女", message_url="https://www.dingtalk.com/", pic_url="http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png") + feedlink3 = FeedLink(title="氧神美女", message_url="https://www.dingtalk.com/", pic_url="http://www.songshan.es/wp-content/uploads/2016/01/Yin-Yang.png") links = [feedlink1, feedlink2, feedlink3] result = self.xiaoding.send_feed_card(links) self.assertEqual(result['errcode'], 0)