製作一个 Hubot 的Plurk Adapter
应用事项提醒
此应用範例以 CoffeeScript 编写,主要程式架构採用 Github 释放的 Hubot 专案,以下有几个主要注意事项,请读者注意,
- Hubot 一开始的架构,除了内建的两个 Adapter 之外,其余都要以 Node.js 的 Module 方式才能运作。
- Hubot 的 bin/hubot 里面写着 npm install 所以不管你怎幺改原始码也不能改变第一点的状况。
其实上述都在讨论同一件事情: Node.JS 的模组,而且模组不能设定相对路径之类的来安装,一定要包装成 npm 相符和格式才有办法正确执行。
建立 Adapter
首先需要準备两个档案,
- package.json
- plurk.coffee
有这两个就足以变成 Node.JS 的模组了,在开始 Coding 之前,先来设定一下 package.json 相依性,
{ "name": "hubot-plurk", "version": "0.1.1", "main": "./plurk", "dependencies": { "hubot": ">=2.0.5", "oauth": "", "cron":"" } }
这边会使用到 Node.JS 的 cron 模组(Module),而登入 Plurk 需要 OAuth 标準授权才行,所以也需要加载 OAuth 模组。
注意:Hubot 在读取非内建模组时,会自动在前面加上 hubot- 的前置。
建立 Robot 跟 API
本篇应用基本上程式码大部分参考 Hubot - Twitter Adapter 来製作,主要差异只有在使用 OAuth 这个模组,Twitter 有 Streaming 可用而 Plurk 则得用 Comet 方式来达到即时读取。
plurk.coffee 程式码,
Robot = require("hubot").robot() Adapter = require("hubto").adapter() EventEmitter = require("events").EventEmitter oauth = require("oauth") cronJob = require("cron").CronJob class Plurk exntends Adapter class PlurkStreaming exnteds EventEmitter
先弄个基本架构,主要 Class 皆参考 Twitter Adapter 命名方式,接着再将 Plurk 这个 Class 增加几个 Method,基本上只要有 run, send, reply 就够了,而 run 用来做初始化的部分。
class Plurk entends Adapter send: (plurk_id, strings…) -> reply: (plurk_id, strings…) -> run: ->
看起来有点东西,接着来处理主要的Plurk API 结合部分,
class PlurkStreaming extends EventEmitter constructor: (options) -> plurk: (callback) -> #观察河道 getChannel: -> #取得 Comet 网址 reply: (plurk_id, message) -> #回噗 acceptFriends: -> #接受好友 get: (path, callback) -> #GET 请求 post: (path, body, callback)-> #POST 请求(其实是装饰) request: (method, path, body, callback)-> #主要的 OAuth 请求 comet: (server, callback)-> #噗浪的 Comet 传回是 JavaScript Callback 要另外处理后才会变成 JSON
然后把注意力集中到 constructor 上,先把建构子弄好。
constructor: (options) -> super() if options.key? and options.secret? and options.token? and options.token_secret? @key = options.key @secret = options.secret @token = options.token @token_secret = options.token_secret #建立 OAuth 连接 @consumer = new oauth.OAuth( "http://www.plurk.com/OAuth/request_token", "http://www.plurk.com/OAuth/access_token", @key, @secret, "1.0", "http://www.plurk.com/OAuth/authorize". "HMAC-SHA1" ) @domain = "www.plurk.com" #初始化取得Comet网址 do @getChannel else throw new Error("参数不足,需要 Key, Secret, Token, Token Secret")
接着来处理 request 这个 method。
request: (method, path, body, callback) -> #记录一下这次的 Request console.log("http://#{@domain}#{path}") # Callback 这边先不丢进去,要用另一种方式处理 request = @consumer.get("http://#{@domain}#{path}", @token, @token_secret, null) request.on "response", (res) -> res.on "data", (chunk) -> parseResponse(chunk+'', callback) res.on "end", (data) -> console.log "End Request: #{path}" res.on "error", (data) -> console.log "Error: " + data request.end() #处理资料 parseResponse = (data, callback) -> if data.length > 0 #用 Try/Catch 避免处理 JSON 出错导致整个中断 try callback null, JSON.parse(data) catch err console.log("Error Parse JSON:" + data, err) #继续执行 callback null, data || {}
大致上就是这样,上面程式的架构已经将整个 Hubot Plurk Adapter 完成。因为在测试时竟然因为噗浪 Lag 而没读到完整的 Comet 资料,然后造成程式异常,为了避免这个问题发生,因此需要加上为了完美呈现需要再加上 Comet 的处理,所以要使用到 EventEmitter 的功能。
comet: (server, callback) -> #在 Callback 里面会找不到自身,所以设定区域变数 self = @ #记录一下这次的 Request console.log("[Comet] #{server}") Callback 这边先不丢进去,要用另一种方式处理 request = @consumer.get("http://#{@domain}#{path}", @token, @token_secret, null) request.on "response", (res) -> res.on "data", (chunk) -> parseResponse(chunk+'', callback) res.on "end", (data) -> console.log "End Request: #{path}" #请求结束,发出事件通知可以进行下一次请求 self.emit "nextPlurk" res.on "error", (data) -> console.log "Error: " + data request.end() #处理资料 parseResponse = (data, callback) -> if data.length > 0 #用 try/catch 避免失败中断 try #去掉 JavaScript 的 Callback data = data.match(/CometChannel.scriptCallback((.+));s*/) jsonData = "" if data? jsonData = JSON.parse(data[1]) else #如果没有任何 Match 尝试直接 parse jsonData = JSON.parse(data) catch err console.log("[Comet] Error:", data, err) #用 Try/Catch 避免处理 JSON 出错导致整个中断 try #只传入 json 的 data 部分 callback null, jsonData.data catch err console.log("[Comet]Error Parse JSON:" + data, err) #继续执行 callback null, data || {}
后面的 get 跟 post 就简单多了!
get: (path, callback) -> @request("GET", path, null, callback) post: (path, body, callback) -> @request("POST", path, body, callback)
接着处理取的 Comet 网址的 getChannel
getChannel: -> self = @ @get "/APP/Realtime/getUserChannel", (error, data) -> if !error #检查是否有 comet server if data.comet_server? self.channel = data.comet_server #如果没有 Channel Ready 就尝试连接会失败 self.emit('channel_ready')
那幺,先来处理 Plurk Adaper 好处理的部份
send: (plruk_id, strings…)-> #跟 Reply 一样,直接交给 reply 做 @reply plurk_id, strings… reply: (plurk_id, strings…) -> strings.forEach (message) => @bot.reply(plruk_id, message)
接着把 run 处理好就可以上线运作搂!
run: -> self = @ options = key: process.env.HUBOT_PLURK_KEY secret: process.env.HUBOT_PLURK_SECRET token: process.env.HUBOT_PLURK_TOKEN token_secret: process.env.HUBOT_PLURK_TOKEN_SECRET #创建刚刚的 API bot = new PlurkStreaming(options) #依照 Twitter 的 new Robot.TextMessage 会没有反应,所以参考 hubot-minecraft 的方式 r = @robot.constructor #处理噗浪河道讯息 @doPlurk = (data)-> #检查是否为回噗 if data.response? data.content_raw = data.response.content_raw data.user_id = data.response.user_id #确定有噗浪ID跟讯息 if data.plurk_id? and data.content_raw self.receive new r.TextMessage(data.plurk_id, data.content_raw) #取得 Comet Server 完成,开始第一次 Comet 连接 bot.on "channel_ready", () -> bot.plurk self.doPlurk #上一次 Comet 完成,继续 Polling bot.on "nextPlurk", ()-> bot.plurk self.doPlurk #定时接受好友邀请 do bot.acceptFriends @bot = bot
终于,完成 Adapter!Hubot 专案里面的 scripts 资料夹内是互动部分,不需要像 Adapter 如此大费周章处理,只新增档案并且设计好对白,之后就会回噗了,我开发用的机器人在此,大家可以去跟他玩玩,[http://plurk.com/elct9620_bot](http://plurk.com/elct9620_bot)
原始资料提供
- [製作一个 Hubot 的噗浪 Adapter](http://revo-skill.frost.tw/blog/2012/03/18/create-a-hubot-plurk-adapter/)