Errbot 后端插件(Backend)开发 – 飞书版

日常工作当中使用最多的其实是聊天工具,那么将自动化的工作流程与聊天工具结合,那就能更加提高工作的效率在聊天的过程中就把事情做好了。另外可以使用聊天群交流,那么其他人是可以看到整个过程的,新来的人也可以通过查看聊天记录了解日常工作和执行过程,自然而然的工作流程的熟悉和达到积累组织知识的目的。

简介

DevOps

DevOpsDevelopment和Operations的组合词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。同时DevOps它更是一套项目部门之间沟通与协作问题的方法论与实践指导。

ChatOps

日常工作当中使用最多的其实是聊天工具,那么将自动化的工作流程与聊天工具结合,那就能更加提高工作的效率在聊天的过程中就把事情做好了。另外可以使用聊天群交流,那么其他人是可以看到整个过程的,新来的人也可以通过查看聊天记录了解日常工作和执行过程,自然而然的工作流程的熟悉和达到积累组织知识的目的。

Errbot

这是一款聊天聊天机器人,使用它可以快速将已经自动化后台工具集成到个人喜欢的聊天工具,如 slack等。更多的细节可以查阅官方文档。接着为了集成Errbot到飞书上面需要单独开发后端插件来实现,Errbot 的后端插件开发过程可以参考官方说明文档 。概括的来说这个插件的开发过程,更多的是对于 Errbot 提供的 基类 的继承和实现其中的抽象方法的过程。

飞书机器人

飞书是一款聊天工具,同时它提供了一个开发平台,提供强大的开放接口,开发者可以基于提供的接口能力快速开发出一个聊天机器人。其中如何快速开发机器人,它提供了一个简单的教程

实践 – 开发结合飞书开发Errbot后端(Backend)

首先项目源代码可以在此处找到:https://github.com/ouranoshong/errbot-backend-lark。另外README.md文档里面有个安装教程,可以按照上面描述的流程安装一遍体验一下整个过程。

下列是目录结构展示:

├── LICENSE
├── README.md
├── _lark
├── lark.plug
├── lark.py
├── requirements.txt
└── tests

开发流程说明

创建 lark.plug 文件,并在其中加入一下内容,规定后端插件的名称是 Lark :

[Core]
Name = Lark
Module = lark

[Documentation]
Description = This is the lark backend for ErrBot.

在项目的根目录下创建一个模块文件与 lark.plug  的  module 字段对应,文件名是 lark.py .  主要介绍这个文件的几个方法,具体的可以查看源文件

在文件中增加一个类,继承ErrBot基类和LarkServer。其中 ErrBot 类是由 ErrBot 提供的需要为其实现抽象方法。然后为了代码的方便管理使用模板方法设计模式单独编写了LarkServer的类,在这个类中是结合了高性能 python 框架 sanic实现的 HTTP 服务,用于接收聊天服务通知的聊天消息。

class LarkBackend(ErrBot, LarkServer)

serve_once 这个是机器人启动调用的入口,实例化 LarkClientLarkBot 以供其它方法调用

    def serve_once(self):
        self.lc = LarkClient(app_id=self.app_id, app_secret=self.app_secret)
        self.bot_identifier = LarkBot(self.lc)
        try:
            self.connect_callback()  # notify that the connection occured
            self.run()
        except KeyboardInterrupt:
            log.info("Interrupt received, shutting down..")
            return True
        except Exception:
            log.exception("Error running Lark server!")
        finally:
            log.debug("Triggering disconnect callback")
            self.disconnect_callback()

_message_receive_handler 这个方法是对 LarkServer 类中抽象方法的实现,主要是将接收到的飞书通知数据转换为 Message 对象,然后再调用 callback_message 方法,调用其它的消息处理逻辑。这里针对不同 chat_type 然后给 Message 对象的 frmto 初始化不同的对象,如 p2p ,即私聊消息通知事件则是转化为 LarkPersonLarkBot 对象,如果是 group 类型,即群聊的消息通知事件则转化为 LarkRoomOccupant  和 LarkRoom 对象。

	def _message_receive_handler(self, data: dict):
        """Callback event handler for the 'message' event"""
        log.debug("Saw an event: %s", pprint.pformat(data))

        event = data.get("event", {})
        message = event.get("message", {})
        msg_type = message.get("message_type", "")
        if msg_type != "text":
            log.warning("only support 'text' msg_type from now on, got:{msg_type}")
            return
        
        text = self._get_text_without_mentions(message)
        chat_type = message.get("chat_type", "").strip()
        sender = event.get("sender", {})
        sender_id = sender.get("sender_id", {})

        msg = Message(text, extras={"lark_event": data})
        if chat_type == 'p2p':
            msg.frm = LarkPerson(self.lc, sender_id.get("open_id"))
            msg.to = self.bot_identifier
        elif chat_type == "group":
            msg.frm = LarkRoomOccupant(self.lc, sender_id.get("open_id"), message.get("chat_id"), self)
            msg.to = LarkRoom(self.lc, message.get("chat_id"), bot=self)
        else:
            log.error(
                f"unknown chat_type:{chat_type} not in ['p2p', 'group']")

        self.callback_message(msg)

send_message 根据 msg 参数不同,构建不同的消息,最后通过调用飞书平台的接口完成消息回复。

    def send_message(self, msg: Message):
        super().send_message(msg)
        to_humanreadable = "<unknown>"
        receive_id = None
        receive_id_type = None
        memtion_id = None
        mention_name = None
        log.debug("message to: {}".format(msg.to))
        try:
            if isinstance(msg.to, RoomOccupant):
                memtion_id = msg.to.open_id
                memtion_name = msg.to.fullname
                receive_id = msg.to.room.chat_id
                to_humanreadable = msg.to.room.name
                receive_id_type = "chat_id"
            elif msg.is_group:
                receive_id = msg.to.chat_id
                to_humanreadable = msg.to.name
                receive_id_type = "chat_id"
            else:
                receive_id = msg.to.person
                memtion_id = receive_id
                to_humanreadable = msg.to.fullname
                memtion_name = to_humanreadable
                receive_id_type = "open_id"

            msgtype = "direct" if msg.is_direct else "channel"
            # chat_id > open_id
            log.debug('Sending %s message to %s (%s).', msgtype, to_humanreadable, receive_id)
            body = msg.body

            if memtion_id is not None:
                body = f'<at user_id="{memtion_id}">{memtion_name}</at>:\n{body}'

            log.debug('Message size: %d.', len(body))

            response = (self.lc.send_message(json.dumps({"text": body}), receive_id_type, receive_id, "text"))
            log.info('send message response %s', response)

build_identifier 根据 txt_rep 参数构建出不同唯一标识且可以被识别的对象,这个也是 ErrBot 中有所说明的方法之一。

    def build_identifier(self, txt_rep: str) -> Identifier:
        """
        txt_rep: {open_id}:{open_chat_id}:{chat_type}
        """
        log.debug("build idetifier txt: {}".format(txt_rep))

        # only open_id can be used to identify a unique person
        open_id, open_chat_id, chat_type = self._extract_identifiers_from_str(txt_rep)
        if open_id and chat_type == "p2p":
            return LarkPerson(lc=self.lc, open_id=open_id)
        if open_id and open_chat_id and chat_type == "group":
            return LarkRoomOccupant(lc=self.lc, open_id=open_id, chat_id=open_chat_id, bot=self)
        if open_chat_id:
            return LarkRoom(lc=self.lc, chat_id=open_chat_id, bot=self)

        raise Exception(
            "You found a bug. I expected at least one of open_id, open_chat_id, or chat_type "
            "to be resolved but none of them were. This shouldn't happen so, please file a bug."
        )

总结

  1. 了解并熟悉使用飞书开发平台接口快速开发一个聊天机器人,另外发现了飞书聊天工具中对 markdown 语法并不完善,使用时请注意只使用飞书已支持 markdown 语法,否则无法正常显示的风险;
  2. ErrBot 的插件化设计,其本身的有很强的扩展能力,快速集成到日常工作的聊天工具中,同时使用这个可以更快的开发需要的自动化流程,提高工作的效率。
Share