从零打造 GitHub 钉钉机器人

1,485 阅读4分钟

本文同步发布于字节话云公众号。

背景

很多人都有自己的 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 的事件体。构造函数中将事件体中常见的 senderrepository 两块数据做了初步解析,以供后续发送消息时组成需要的内容。此外,使用 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 中的 webhooksecret 变量值替换为钉钉机器人的 WebHook 和密钥,就可以执行如下命令进行部署:

s github-notification deploy

部署完成后,访问 FC 控制台,获取公网访问地址,将之作为 GitHub 仓库的 WebHook,就能够愉快地接受来自 GitHub 仓库的消息通知了。

当 GitHub 仓库产生事件时,钉钉群的机器人就会推送相关的消息,效果如下: