简介
DevOps
DevOps(Development和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
这个是机器人启动调用的入口,实例化 LarkClient
和 LarkBot
以供其它方法调用
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
对象的 frm
和 to
初始化不同的对象,如 p2p
,即私聊消息通知事件则是转化为 LarkPerson
和 LarkBot
对象,如果是 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."
)
总结
- 了解并熟悉使用飞书开发平台接口快速开发一个聊天机器人,另外发现了飞书聊天工具中对
markdown
语法并不完善,使用时请注意只使用飞书已支持markdown
语法,否则无法正常显示的风险; ErrBot
的插件化设计,其本身的有很强的扩展能力,快速集成到日常工作的聊天工具中,同时使用这个可以更快的开发需要的自动化流程,提高工作的效率。