从零打造 GitHub 钉钉机器人

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

背景

很多人都有自己的 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 仓库产生事件时,钉钉群的机器人就会推送相关的消息,效果如下:


你可能感兴趣的:(从零打造 GitHub 钉钉机器人)