为了将内容维护工作转接到通过向 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 ---------------------------------------- """