为了将内容维护工作转接到通过向 Bot 下达指令完成,一开始打算准备采用 Hubot 作为中转机器人,后来因为安装过程中的种种不适以及对 CoffeeScript 的莫名恐惧,最终放弃这一方案。其实之前分别基于 Flask 和 Tornado 实现过 Telegram 和微信公众号的简单自动回复机器人,其中 API 响应环节一般都是非常简单明确的,比较乱的反而是对指令的解析。例如:
def parse(cmd):
cmds = cmd.split(' ')
if cmds[0] == 'share':
print("Share {} to Hub".format(cmds[1]))
elif cmds[0] == 'update':
if cmds[1] == 'title':
print("Update title of {} as {}".format(cmds[2], cmds[3]))
elif cmds[0] == 'unpublic':
print("Unpublic link {}".format(cmds[1]))
else:
print("Commands Help.")
cmd1 = "share https://pyhub.cc"
cmd2 = "update title https://pyhub.cc Python头条"
cmd3 = "unpublic https://pyhub.cc"
parse(cmd1)
parse(cmd2)
parse(cmd3)
"""
Share https://pyhub.cc to Hub
Update title of https://pyhub.cc as Python头条
Unpublic link https://pyhub.cc
"""
这其中还省略了很多指令准确性的检查,随着指令数量、形式的增加,可读性、可维护性都会变得非常糟糕。刚好在查看 Hubot 文档的时候找到了一个 Python 版本的 slackbot,采用修饰器和正则作为指令解析、分配的方法,看起来非常简洁:
from slackbot.bot import respond_to
import re
@respond_to('hi', re.IGNORECASE)
def hi(message):
message.reply('I can understand hi or HI!')
# react with thumb up emoji
message.react('+1')
@respond_to('Give me (.*)')
def giveme(message, something):
message.reply('Here is {}'.format(something))
根据这一思路,写了一个简单的指令处理工具:
import reclass Jarvis(object):
dispatcher = {}
max_patt_len = 0
@classmethod
def cmd(cls, pattern, flags=0):
def wrapper(func):
cls.max_patt_len = max(cls.max_patt_len, len(pattern))
cls.dispatcher[pattern] = (func, re.compile(pattern, flags))
return func
return wrapper
def resolve(self, msg):
for pattern in self.dispatcher:
m = self.dispatcher[pattern][1].match(msg)
if m:
return self.dispatcher[pattern][0](self, *(m.groups()))
break
else:
return self.help()
通过 dispatcher 记录每条指令绑定的 handler 函数,这样做的还可以同时将几个同义指令绑定到同一个 handler 函数上:
class M(Javis):
@Javis.cmd(r'hello')
@Javis.cmd(r'hi')
def hello(self):
return "Hello"m = M()
print(m.resolve('hi'))
print(m.resolve('hello'))
为了方便查看全部指令,还可以根据 handler 函数的文档或函数明自动生成所有指令的说明文档:
def help(self):
body = ""
for patt in sorted(self.dispatcher.keys()):
body += "{:.<{max_patt_len}}{}\n".format(patt,
self.dispatcher[patt][0].__doc__ or self.dispatcher[patt][0].__name__, max_patt_len=self.max_patt_len+4)
return "\nCommands:\n{0}\n{1}{0}\n".format("-"*40, body)
class M(Jarvis):
@Jarvis.cmd(r'hello')
@Jarvis.cmd(r'hi')
def hello(self):
return "Hello"
@Jarvis.cmd(r'del (.*)')
def delete(self, lid):
"""根据lid删除记录"""
return "Record {} Deleted!".format(lid)
print(m.help())
"""
Commands:
----------------------------------------
del (.*)....根据lid删除记录
hello.......hello
hi..........hello
----------------------------------------
"""