本文同步发布于字节话云公众号。
背景
很多人都有自己的 GitHub 项目,可能需要将仓库中的事件自动通知到钉钉中。尽管钉钉群里可以添加专门的 GitHub 机器人,但它所支持的事件类型非常有限,基本只有 push 相关的事件。想要支持更多的事件,不如自己动手打造一个机器人。
于是,实现了一个 GitHub to DingTask 的项目。该项目通过阿里云的函数计算服务部署,无需任何金钱成本,就能部署这个通知服务。
实现思路
核心思路就是在钉钉群中添加自定义机器人,实现一段 WebHook 的逻辑,根据接收到的 GitHub 事件类型的不同向自定义机器人发送不同的消息。而这段 WebHook 逻辑需要有公网地址可被调用,各大云服务的函数计算服务每月会提供一定的免费调用次数,是存放 WebHook 逻辑的首选。
实现原理
钉钉添加自定义机器人
在钉钉群里添加自定义机器人,在机器人的安全设置中选择“加签”,记下密钥并点击“完成”,在完成界面中记下机器人的 WebHook 地址。
通知逻辑
GitHub 的 WebHook 事件的事件体是个 Json 字典,那么可以构造一个 DingTalkNotifier
类接收事件体 payload
,并提供 notify()
方法用于根据事件内容将具体消息通知到钉钉群。而通知到钉钉群有现成的开源 Python SDK——DingtalkChatbot可以使用,就省得我们翻阅钉钉自定义机器人的技术文档去封装实现了。
WebHook 通知的核心代码如下:
import logging
import conf
from dingtalkchatbot.chatbot import DingtalkChatbot
class DingTalkNotifier(object):
def __init__(self, payload: dict):
self.payload = payload
self.action = self.payload.get('action')
self.action_prep = 'to' if self.action in ('created', 'opened', 'submitted', None) else 'of'
self.sender = sender = payload.get('sender') or {}
self.sender_full_name = sender.get('login')
self.sender_page = sender.get('html_url')
self._md_sender = f'[{self.sender_full_name}]({self.sender_page})'
self.repo = repo = payload.get('repository') or {}
self.repo_full_name = repo.get('full_name')
self.repo_page = repo.get('html_url')
self.repo_language = repo.get('language')
self.repo_star_count = repo.get('stargazers_count')
self._md_repo = f'[{self.repo_full_name}]({self.repo_page})'
self.bot = DingtalkChatbot(conf.webhook, conf.secret)
def notify(self):
logging.info(f'Preparing notification: {self.payload}')
if 'pull_request' in self.payload:
self._notify_pull_request()
elif 'head_commit' in self.payload:
self._notify_push()
elif 'issue' in self.payload:
self._notify_issue()
elif 'starred_at' in self.payload:
self._notify_star()
elif 'forkee' in self.payload:
self._notify_fork()
elif 'discussion' in self.payload:
self._notify_discussion()
def _notify_pull_request(self):
pr = self.payload['pull_request']
pr_page = pr['html_url']
pr_number = pr['number']
pr_title = pr['title']
pr_body = pr['body'] or ''
review = self.payload.get('review')
comment = self.payload.get('comment')
if review:
pr_review_page = review['html_url']
review_body = review['body'] or ''
self.bot.send_markdown(
title='Pull Request Review',
text=f'{self._md_sender} has {self.action} a pull request review {self.action_prep} {self._md_repo}\n\n'
f'[#{pr_number} {pr_title}]({pr_review_page})\n\n'
f'> {review_body}'
)
elif comment:
comment_page = comment['html_url']
comment_body = comment['body'] or ''
self.bot.send_markdown(
title='Issue Comment',
text=f'{self._md_sender} has {self.action} a pull request review comment '
f'{self.action_prep} {self._md_repo}\n\n'
f'[#{pr_number} {pr_title}]({comment_page})\n\n'
f'> {comment_body}'
)
else:
self.bot.send_markdown(
title='Pull Request',
text=f'{self._md_sender} has {self.action} a pull request {self.action_prep} {self._md_repo}\n\n'
f'[#{pr_number} {pr_title}]({pr_page})\n\n'
f'> {pr_body}'
)
在上述代码中:
-
DingTalkNotifier
的构造函数接收payload
变量,即 GitHub WebHook 的事件体。构造函数中将事件体中常见的sender
和repository
两块数据做了初步解析,以供后续发送消息时组成需要的内容。此外,使用DingtalkChatbot(conf.webhook, conf.secret)
传入钉钉机器人的 WebHook 地址和密钥,初始化了钉钉机器人类,用来发送钉钉消息。 -
notify()
方法用来发送钉钉消息。该方法中,根据self.payload
的不同特征,决定调用不同的消息发送方法。例如,当事件体中包含pull_request
键时,就调用self._notify_pull_request()
发送Pull Request消息。 -
_notify_pull_request()
方法是具体的消息通知实现。该方法从事件体中获取需要的信息,进一步判断事件体的类型,再使用self.bot.send_markdown()
向钉钉群发送 MarkDown 格式的消息。
部署通知服务
写好的通知逻辑,就需要将此逻辑部署到服务器上,并提供可供调用的 URL,以供 GitHub WebHook 调用。为了方便,也为了免费,这里选择阿里云的函数计算服务(FC)进行部署。FC 的优点在于每月有 100 万次的免费调用额度,且提供了 HTTP 触发器,也就意味着提供了可供调用的 URL,就省得再去申请域名了。
FC 有专门的部署工具,叫做 Serverless Devs 。在安装和配置好此工具后,将项目中 notification/conf.py
中的 webhook
和 secret
变量值替换为钉钉机器人的 WebHook 和密钥,就可以执行如下命令进行部署:
s github-notification deploy
部署完成后,访问 FC 控制台,获取公网访问地址,将之作为 GitHub 仓库的 WebHook,就能够愉快地接受来自 GitHub 仓库的消息通知了。
当 GitHub 仓库产生事件时,钉钉群的机器人就会推送相关的消息,效果如下: