为hubot机器人脚本增加python扩展

#move from old blog 编辑

为hubot机器人脚本增加python扩展

昨天顺利把hubot跑起来了, 能回答了. 也通过nodejs的exec命令执行shell的方式, 将消息以参数的形式传给process.py处理, 以形成用py写脚本的形式.
不过上面方式有缺陷:
1, nodejs不是真正的调用py, 同时py执行的返回或直接print或写stdout(print在某种程度上就是stdout), 然后nodejs什么都不用干, 就直接相当于把stdout使用msg.send回复给gtalk了.
2, 整体结构不优美, nojs跟py还得靠exec执行shell的形式, 这种调用方式挺丑陋.

在github上找到一个脚本, 也是为了用python来写hubot的脚本, 实现方式也是用stdout和stdin结合, 达到nodejs收到gtalk消息后, 将消息传给py处理. 拿来后, 又做了一些修改, 具体过程是:

*** 封装一个Python 类, 接收stdin, 输出stdout.

  • 在nodejs里启动这个py类的listen监听stdin, robot收到消息时write到stdin,
  • py从stdin中读到消息, 交给指定的handler
  • handler处理完成后, 输出stdout, 同时触发nodejs的event, 读取stdout通过robot发送回馈信息. **

pyscript.coffee脚本如下:

class PythonScript

    pyScriptPath = __dirname + '/test.py'
    python_script = require('child_process').spawn('python', [pyScriptPath])
    python_script.stdout.on 'data', (data) =>
        receive_from_python(data.toString())

    module.exports = (robot) ->
        @robot = robot
        #robot.respond /(.*)/i, (msg) ->
        #    newRegex = new RegExp("^[@]?#{robot.name}[:,]? ?(.*)", 'i')
        #    match = newRegex.exec msg.message.text
        #    send_to_python(match[1], msg.message.room, 'respond')
        #    @robot.msg = msg

        robot.hear /(.*)/i, (msg) ->
            send_to_python(msg.message.text, msg.message.room, 'hear')
            @robot.msg = msg

    send_to_python = (message, room, method) ->
        dict =
            type : method,
            message : message,
            room : room
        python_script.stdin.write(JSON.stringify(dict) + '\n')
        console.log JSON.stringify(dict)

    receive_from_python = (json) ->
        data = JSON.parse(json)
        #@robot.messageRoom data.room, data.message # 恶心的问题, data.room在send_to_python调用传的参数msg.message.room是undefined, 导致这里不能这样用
        @robot.msg.send data.message   # 于是在入口的地方直接把msg对象赋给@robot里的, 在这里就能夸函数调用msg.send了.

PythonScript类封装如下: hubot_script.py

handlers = [
    (r'/hubot/sys/(.*)', syscmdhandler),
    (r'/hubot/chat/(.*)', chathandler),
]

class HubotScript:
    def __init__(self):
        self.start_listening()

    # 创建一个listen, 监听标准输入, 有输入时执行后面逻辑
    def start_listening(self):
        while True:
            line = sys.stdin.readline()
            self.receive(line)

    def receive(self, json_str):
        # 这里一定需要捕获错误, 否则出错会直接跳出 start_listening中的循环, 监听就结束了
        try:
            json_dict = json.loads(json_str)
            json_dict['message'] = '/' + '/'.join(json_dict['message'].split(' ')) # 搞成类似url的形式, 方便handlers里的regex匹配
            self.dispatch(json_dict)
        except Exception, e:
            print e

    def send(self, message):
        if message:
            #print json.dumps(message)
            sys.stdout.write(json.dumps(message) + '\n')
            sys.stdout.flush()

    # Message Dispatch
    def dispatch(self, json_dict):
        #msg_type = json_dict['type']
        #if msg_type == 'hear':
        #    self.dispatch_generic(json_dict, _hear_handlers)
        #elif msg_type == 'respond':
        #    self.dispatch_generic(json_dict, _resp_handlers)
        self.dispatch_generic(json_dict, handlers)
        
    def dispatch_generic(self, message, regexes):
        for regex, handler in regexes:
            p = re.match(regex, message['message'])
            if p:
                action = ' '.join(p.groups()[0].split('/'))
                response = message
                #response_text = handler(self, message)
                response_text = handler(self, action)
                if response_text:
                    if len(response_text) > 3000: # nodejs的JSON.parse不能处理太长的str
                        response_text = response_text[:3000]
                    response['message'] = response_text
                    self.send(response)
def hear(regex): # 测试用decorator
    def decorator(handler):
        handlers.append((regex, handler))
    return decorator

附带一个测试程序, test.py:

\#coding=utf8
from hubot_script import *

class TestScript(HubotScript):

    @hear('def')
    def test_handler(self, message):
        return 'hear'

    #@respond('abc')
    #def test_handlera(self, message):
    #    return 'respond'

if __name__ == '__main__':
    test = TestScript()

** 至此** **我已经将hubot, gtalk, python集成到一起了, 我的hubot的fork在
https://github.com/liutaihua/hubot.git 运行方式:
clone之后, 首先进入hubot:
cd hubot && npm install

然后得在进入node_modules/hubot-gtalk/为hubot-gtalk这个adapter安装依赖:
cd node_modules/hubot-gtalk/ && npm install

最后运行
./bin/hubot -a gtalk

完.