-
Notifications
You must be signed in to change notification settings - Fork 97
Hubot 脚本语法
- Hubot 脚本语法
- Sharing Scripts
- Listener Metadata
- Middleware
- Listener Middleware
- Receive Middleware
- Response Middleware
- Testing Hubot Scripts
Hubot 是一个可扩展的机器人。您可以在 scripts
下编写自定义脚本,或为社区分享您的脚本。
当创建 hubot,生成器将创建 scripts
目录,可以在该目录添加自定义脚本。创建脚本需要以下步骤:
- hubot 默认从
src/scripts
和scripts
载入脚本 - 创建
coffee
或js
文件 - 导出函数:
module.exports = (robot) ->
# your code here
聊天机器人一般通过消息进行交互。robot 的 hear
和 respond
方法可以接受/发送消息至聊天室。它需要一个扩展正则表达式和 callback 函数作为参数。例如:
module.exports = (robot) ->
robot.hear /badger/i, (res) ->
# your code here
robot.respond /open the pod bay doors/i, (res) ->
# your code here
robot.hear /badger/
回调函数可以被任何匹配的消息调用。例如:
- Stop badgering the witness
- badger me
- what exactly is a badger anyways
robot.respond /open the pod bay doors/i
回调函数只能被发送给 bot 的消息调用。如果 bot 名为 HAL,别名为 /,以下消息触发回调函数:
- hal open the pod bay doors
- HAL: open the pod bay doors
- @HAL open the pod bay doors
- /open the pod bay doors
以下消息不能触发回调函数:
- HAL: please open the pod bay doors # 消息文本不匹配
- has anyone ever mentioned how lovely you are when you open the pod bay doors? # 缺少机器人名称
res
参数是 Response
实例 (之前用 msg
)。如果要从机器人返回消息,可使用 send
, reply
, emote
方法。
- send 将消息发送到整个房间或群组;
- reply 将消息回复给某人;
- emote 强调消息,需 adapter 支持。
module.exports = (robot) ->
robot.hear /badger/i, (res) ->
res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
robot.respond /open the pod bay doors/i, (res) ->
res.reply "I'm afraid I can't let you do that."
robot.hear /I like pie/i, (res) ->
res.emote "makes a freshly baked pie"
使用 res.match
捕获正则表达式匹配的消息内容,并返回包含结果的数组。这是 JavaScript 内置功能,数组索引 0 为匹配的全部内容。让我们来更新之前的脚本:
robot.respond /open the (.*) doors/i, (res) ->
doorType = res.match[1]
if doorType is "pod bay"
res.reply "I'm afraid I can't let you do that."
else
res.reply "Opening #{doorType} doors"
如果有人说 "HAL: open the pod bay doors",则 res.match[0]
为 "open the pod bay doors", res.match[1]
为 "pod bay"。
Hubot 可通过 HTTP 集成第三方 API。robot.http
是 node-scoped-http-client 的实例。示例如下:
robot.http("https://midnight-train")
.get() (err, res, body) -> # err错误消息, res响应头, body响应内容
if err
res.send "Encountered an error :( #{err}"
return
# your code here, knowing it was successful
res.send "Got back #{body}"
POST 示例如下:
data = JSON.stringify({
foo: 'bar'
})
robot.http("https://midnight-train")
.header('Content-Type', 'application/json')
.post(data) (err, res, body) ->
# your code here
res
是 http.ServerResponse 实例。我们只需要关注 statusCode
, getHeader
方法。使用 statusCode
检查 HTTP 状态码,非 200 意味着发生错误。使用 getHeader
获取响应头,可检查速率限制:
robot.http("https://midnight-train")
.get() (err, res, body) ->
# pretend there's error checking code here
if res.statusCode isnt 200
res.send "Request didn't come back HTTP 200 :("
return
rateLimitRemaining = parseInt res.getHeader('X-RateLimit-Limit') if res.getHeader('X-RateLimit-Limit')
if rateLimitRemaining and rateLimitRemaining < 1
res.send "Rate Limit hit, stop believing for awhile"
# rest of your code
调用 API 最简洁的方式是返回 JSON,它不需要额外依赖。当调用 robot.http
,你可以通过 Accept
头设置返回的数据格式。一旦 body
返回,可使用 JSON.parse
解析:
robot.http("https://midnight-train")
.header('Accept', 'application/json')
.get() (err, res, body) ->
# error checking code here
data = JSON.parse body
res.send "#{data.passenger} taking midnight train going #{data.destination}"
body
可能返回非 JSON 数据,例如 API 遇到错误返回 HTML。为安全起见,你需要检查 Content-Type
,并捕捉解析错误。
robot.http("https://midnight-train")
.header('Accept', 'application/json')
.get() (err, res, body) ->
# err & response status checking code here
if response.getHeader('Content-Type') isnt 'application/json'
res.send "Didn't get back JSON :("
return
data = null
try
data = JSON.parse body
catch error
res.send "Ran into an error parsing JSON :("
return
# your code here
XML 没有封装好的解析库,所以解析比较困难。这超出了本文档的范围,以下库可供参考:
- xml2json (simplest to use, but has some limitations)
- jsdom (JavaScript implementation of the W3C DOM)
- xml2js
对于未提供 API 的页面,只能抓取页面。这超出了本文档的范围,以下库可供参考:
如上所述,hubot 使用 node-scoped-http-client 为创建 HTTP/HTTPS 请求提供简单接口。本节,使用 node 内置的 http 和 https 库,它为几种常用的操作提供了简单的 DSL。
如果你需要直接控制 http/https 选项,可以向 robot.http
传入第二个参数,该参数将通过 node-scoped-http-client 传给 http/https。
options =
# don't verify server certificate against a CA, SCARY!
rejectUnauthorized: false
robot.http("https://midnight-train", options)
另外,如果 node-scoped-http-client 不能满足你,可以使用 http, https, request 或其他node库。
常见模式是使用 hear 或 respond 命令,并从数组中取随机项进行回复。用 JavaScript 和 CoffeeScript 实现比较麻烦,所以 Hubot 提供了该方法:
lulz = ['lol', 'rofl', 'lmao']
res.send res.random lulz
Hubot 可响应房间主题的更改,需要 adapter 支持。
module.exports = (robot) ->
robot.topic (res) ->
res.send "#{res.message.text}? That's a Paddlin'"
Hubot 可响应用户进入和离开,需要 adapter 支持。
enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
module.exports = (robot) ->
robot.enter (res) ->
res.send res.random enterReplies
robot.leave (res) ->
res.send res.random leaveReplies
虽然上述功能可以满足大部分用户的需求(hear, respond, enter, leave, topic),但有时您可能需要非常特殊的逻辑匹配。此时,可以使用 listen
方法指定自定义匹配函数,代替之前的正则表达式。
如果执行 listener 回调函数,则匹配函数必须返回真值。匹配函数的返回值被传入回调函数,并作为 response.match
的值。
module.exports = (robot) ->
robot.listen(
(message) -> # Match function
# Occassionally respond to things that Steve says
message.user.name is "Steve" and Math.random() > 0.8
(response) -> # Standard listener callback
# Let Steve know how happy you are that he exists
response.reply "HI STEVE! YOU'RE MY BEST FRIEND! (but only like #{response.match * 100}% of the time)"
)
复杂的匹配示例可参考模式设计文档。
Hubot可使用 process.env
访问环境变量。通常用于提供脚本参数,惯例是使用 HUBOT_
前缀。
answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING or 42
module.exports = (robot) ->
robot.respond /what is the answer to the ultimate question of life/, (res) ->
res.send "#{answer}, but what is the question?"
注意,请确保设置变量默认值,保证未定义变量时脚本可以正常载入。建议脚本最好不需要其他配置便可以工作。
这将会退出,如果未定义变量:
answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
unless answer?
console.log "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
process.exit(1)
module.exports = (robot) ->
robot.respond /what is the answer to the ultimate question of life/, (res) ->
res.send "#{answer}, but what is the question?"
更新 robot.respond
来检查变量:
answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
module.exports = (robot) ->
robot.respond /what is the answer to the ultimate question of life/, (res) ->
unless answer?
res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
return
res.send "#{answer}, but what is the question?"
Hubot 使用 npm 管理依赖。额外依赖需要添加至 package.json
。例如,添加lolimadeupthispackage:
"dependencies": {
"hubot": "2.5.5",
"lolimadeupthispackage": "1.2.3"
},
如果您使用 hubot-scripts 脚本,注意需要将脚本文档注明的依赖写入 package.json
。请确保以有效的 JSON 格式添加依赖。
Hubot 可使用 JavaScript 内置 setTimeout 设置超时。它是一个回调方法,调用回调函数前需要等待。
module.exports = (robot) ->
robot.respond /you are a little slow/, (res) ->
setTimeout () ->
res.send "Who you calling 'slow'?"
, 60 * 1000
Hubot 可使用 setInterval 设置间隔。它是一个回调方法,调用回调函数前需要等待。
module.exports = (robot) ->
robot.respond /annoy me/, (res) ->
res.send "Hey, want to hear the most annoying sound in the world?"
setInterval () ->
res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
, 1000
setTimeout
和 setInterval
返回已创建超时或间隔的 ID。该 ID 用于 clearTimeout
和 clearInterval
。
module.exports = (robot) ->
annoyIntervalId = null
robot.respond /annoy me/, (res) ->
if annoyIntervalId
res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
return
res.send "Hey, want to hear the most annoying sound in the world?"
annoyIntervalId = setInterval () ->
res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
, 1000
robot.respond /unannoy me/, (res) ->
if annoyIntervalId
res.send "GUYS, GUYS, GUYS!"
clearInterval(annoyIntervalId) ->
annoyIntervalId = null
else
res.send "Not annoying you right now, am I?"
Hubot 支持 express web 框架来响应 HTTP 请求。它监听 EXPRESS_PORT
或 PORT
环境变量指定的端口,默认 8080
。robot.router
是 express 应用实例。它可以通过指定 EXPRESS_USER
和 EXPRESS_PASSWORD
保护用户名和密码。设置 EXPRESS_STATIC
来提供静态文件。
最常见的用途是为 Webhook 推送服务提供 HTTP 端,并在 chat 中显示。
module.exports = (robot) ->
# the expected value of :room is going to vary by adapter, it might be a numeric id, name, token, or some other value
robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
room = req.params.room
data = if req.body.payload? then JSON.parse req.body.payload else req.body
secret = data.secret
robot.messageRoom room, "I have a secret: #{secret}"
res.send 'OK'
使用 curl 测试;请查看 error handling 部分。
// raw json, must specify Content-Type: application/json
curl -X POST -H "Content-Type: application/json" -d '{"secret":"C-TECH Astronomy"}' http://127.0.0.1:8080/hubot/chatsecrets/general
// defaults Content-Type: application/x-www-form-urlencoded, must st payload=...
curl -d 'payload=%7B%22secret%22%3A%22C-TECH+Astronomy%22%7D' http://127.0.0.1:8080/hubot/chatsecrets/general
所有端点 URL 应以 /hubot
字符串开始(无论 robot 名称是什么)。这种一致性使它更容易创建 webhooks(可复制URL)并保证网址有效性(robot 名称可能不是安全的 URL 字符串)。
Hubot 还可以响应事件,用于在脚本间传递数据。robot.emit
和 robot.on
方法通过封装 EventEmitter 模块获得。
示例:有一个脚本用于处理与服务的交互,然后发射事件。例如,我们有个脚本用于从 GitHub post-commit hook 接收数据,然后发射 commits 通知,最后由另一个脚本用这些 commits 执行操作。
# src/scripts/github-commits.coffee
module.exports = (robot) ->
robot.router.post "/hubot/gh-commits", (req, res) ->
robot.emit "commit", {
user : {}, #hubot user object
repo : 'https://github.com/github/hubot',
hash : '2e1951c089bd865839328592ff673d2f08153643'
}
# src/scripts/heroku.coffee
module.exports = (robot) ->
robot.on "commit", (commit) ->
robot.send commit.user, "Will now deploy #{commit.hash} from #{commit.repo}!"
#deploy code goes here
如果你提供一个事件,建议你在数据中包含 hubot user
或 room
对象。这将允许 hubot 提醒聊天室中的 user
或 room
。
没有代码是完美的,错误和异常是可预知的。未捕获的异常会导致 hubot 崩溃。Hubot 现在包含一个 uncaughtException handler
,为脚本提供一些异常处理能力。
# src/scripts/does-not-compute.coffee
module.exports = (robot) ->
robot.error (err, res) ->
robot.logger.error "DOES NOT COMPUTE"
if res?
res.reply "DOES NOT COMPUTE"
在这里你可以做任何事,但你需要记录错误作为额外的措施,特别是异步代码。否则,可能你会遇到递归错误,但不知道如何解决。
在内部,有一个 'error' 事件发出,错误处理程序接收该事件。uncaughtException handler 处理未知状态的进程。因此,你应该定义自己的异常,并发射它们。第一个参数是抛出的错误,第二个是生成该错误可选的错误消息。
robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
room = req.params.room
data = null
try
data = JSON.parse req.body.payload
catch err
robot.emit 'error', err
# rest of the code here
robot.hear /midnight train/i, (res)
robot.http("https://midnight-train")
.get() (err, res, body) ->
if err
res.reply "Had problems taking the midnight train"
robot.emit 'error', err, res
return
# rest of code here
第二个示例,你需要思考想要用户看到什么消息。如果直接返回 error handler 给用户,你不需要添加自定义消息,并将错误消息提供给 get()
请求。这依赖于你想用户如何报告异常。
Hubot 脚本在文件开始的注释中编写文档,例如:
# Description:
# <description of the scripts functionality>
#
# Dependencies:
# "<module name>": "<module version>"
#
# Configuration:
# LIST_OF_ENV_VARS_TO_SET
#
# Commands:
# hubot <trigger> - <what the respond trigger does>
# <trigger> - <what the hear trigger does>
#
# Notes:
# <optional notes required for the script>
#
# Author:
# <github username of the original script author>
其中最重要的是 Commands
部分。加载时,Hubot 查看每个脚本的 Commands
部分,并创建所有命令的列表。help.coffee
允许用户查看所有命令的帮助。因此,文档的 Commands
很重要。
关于文档commands,有些最佳实践:
- 写在一行。帮助命令会被排序,因此不要换行,插入第二行没有任何意义。
- 将 Hubot 写为 hubot,即使你的 hubot 被命名为其他名称。它会自动用正确的替换当前名称。这使得共享脚本而无需修改文档。
-
robot.respond
文档,前缀永远是hubot
。Hubot 将自动替换它为 robot 名称或别名。 - 查看 man 手册。特别是,括号表示的可选部分,'...' 为任意数量的参数等。
其他部分与bot开发有关,特别是依赖关系,配置变量和注释。hubot-scripts 的贡献者应该包含与运行脚本相关的所有部分。
Hubot 使用内存存储键值对,并导出为 robot.brain
用于通过脚本持久化存储相关数据。
robot.respond /have a soda/i, (res) ->
# Get number of sodas had (coerced to a number).
sodasHad = robot.brain.get('totalSodas') * 1 or 0
if sodasHad > 4
res.reply "I'm too fizzy.."
else
res.reply 'Sure!'
robot.brain.set 'totalSodas', sodasHad+1
robot.respond /sleep it off/i, (res) ->
robot.brain.set 'totalSodas', 0
msg.reply 'zzzzz'
如果脚本需要查看用户数据,以下 robot.brain
方法可以查看多个用户的 id, name 或用户名匹配的 'fuzzy':userForName
, userForId
, userForFuzzyName
, usersForFuzzyName
。
module.exports = (robot) ->
robot.respond /who is @?([\w .\-]+)\?*$/i, (res) ->
name = res.match[1].trim()
users = robot.brain.usersForFuzzyName(name)
if users.length is 1
user = users[0]
# Do something interesting here..
res.send "#{name} is user - #{user}"
Hubot从三个地方载入脚本:
- Hubot安装目录下的
scripts/
目录; -
hubot-scripts
包的社区脚本在hubot-scripts.json
文件中指定; - 脚本从外部 npm 包载入,并在
external-scripts.json
文件指定。
scripts/
目录的脚本按字母顺序加载。例如:
scripts/1-first.coffee
scripts/_second.coffee
scripts/third.coffee
Once you've built some new scripts to extend the abilities of your robot friend, you should consider sharing them with the world! At the minimum, you need to package up your script and submit it to the Node.js Package Registry. You should also review the best practices for sharing scripts below.
Start by checking if an NPM package for a script like yours already exists. If you don't see an existing package that you can contribute to, then you can easily get started using the hubot
script yeoman generator.
Creating a script package for hubot is very simple. Start by installing the hubot
yeoman generator:
% npm install -g yo generator-hubot
Once you've got the hubot generator installed, creating a hubot script is similar to creating a new hubot. You create a directory for your hubot script and generate a new hubot:script
in it. For example, if we wanted to create a hubot script called "my-awesome-script":
% mkdir hubot-my-awesome-script
% cd hubot-my-awesome-script
% yo hubot:script
At this point, the you'll be asked a few questions about the author for the script, name of the script (which is guessed by the directory name), a short description, and keywords to find it (we suggest having at least hubot, hubot-scripts
in this list).
If you are using git, the generated directory includes a .gitignore, so you can initialize and add everything:
% git init
% git add .
% git commit -m "Initial commit"
You now have a hubot script repository that's ready to roll! Feel free to crack open the pre-created src/awesome-script.coffee
file and start building up your script! When you've got it ready, you can publish it to npmjs by following their documentation!
You'll probably want to write some unit tests for your new script. A sample test script is written to
test/awesome-script-test.coffee
, which you can run with grunt
. For more information on tests,
see the Testing Hubot Scripts section.
In addition to a regular expression and callback, the hear
and respond
functions also accept an optional options Object which can be used to attach arbitrary metadata to the generated Listener object. This metadata allows for easy extension of your script's behavior without modifying the script package.
The most important and most common metadata key is id
. Every Listener should be given a unique name (options.id; defaults to null
). Names should be scoped by module (e.g. 'my-module.my-listener'). These names allow other scripts to directly address individual listeners and extend them with additional functionality like authorization and rate limiting.
Additional extensions may define and handle additional metadata keys. For more information, see the Listener Middleware section.
Returning to an earlier example:
module.exports = (robot) ->
robot.respond /annoy me/, id:'annoyance.start', (msg)
# code to annoy someone
robot.respond /unannoy me/, id:'annoyance.stop', (msg)
# code to stop annoying someone
These scoped identifiers allow you to externally specify new behaviors like:
- authorization policy: "allow everyone in the
annoyers
group to executeannoyance.*
commands" - rate limiting: "only allow executing
annoyance.start
once every 30 minutes"
There are three kinds of middleware: Receive, Listener and Response.
Receive middleware runs once, before listeners are checked. Listener middleware runs for every listener that matches the message. Response middleware runs for every response sent to a message.
Similar to Express middleware, Hubot executes middleware in definition order. Each middleware can either continue the chain (by calling next
) or interrupt the chain (by calling done
). If all middleware continues, the listener callback is executed and done
is called. Middleware may wrap the done
callback to allow executing code in the second half of the process (after the listener callback has been executed or a deeper piece of middleware has interrupted).
Middleware is called with:
-
context
- See the each middleware type's API to see what the context will expose.
-
next
- a Function with no additional properties that should be called to continue on to the next piece of middleware/execute the Listener callback
-
next
should be called with a single, optional argument: either the provideddone
function or a new function that eventually callsdone
. If the argument is not given, the provideddone
will be assumed.
done
- a Function with no additional properties that should be called to interrupt middleware execution and begin executing the chain of completion functions.
-
done
should be called with no arguments
Every middleware receives the same API signature of context
, next
, and
done
. Different kinds of middleware may receive different information in the
context
object. For more details, see the API for each type of middleware.
For synchronous middleware (never yields to the event loop), hubot will automatically catch errors and emit an an error
event, just like in standard listeners. Hubot will also automatically call the most recent done
callback to unwind the middleware stack. Asynchronous middleware should catch its own exceptions, emit an error
event, and call done
. Any uncaught exceptions will interrupt all execution of middleware completion callbacks.
Listener middleware inserts logic between the listener matching a message and the listener executing. This allows you to create extensions that run for every matching script. Examples include centralized authorization policies, rate limiting, logging, and metrics. Middleware is implemented like other hubot scripts: instead of using the hear
and respond
methods, middleware is registered using listenerMiddleware
.
A fully functioning example can be found in hubot-rate-limit.
A simple example of middleware logging command executions:
module.exports = (robot) ->
robot.listenerMiddleware (context, next, done) ->
# Log commands
robot.logger.info "#{context.response.message.user.name} asked me to #{context.response.message.text}"
# Continue executing middleware
next()
In this example, a log message will be written for each chat message that matches a Listener.
A more complex example making a rate limiting decision:
module.exports = (robot) ->
# Map of listener ID to last time it was executed
lastExecutedTime = {}
robot.listenerMiddleware (context, next, done) ->
try
# Default to 1s unless listener provides a different minimum period
minPeriodMs = context.listener.options?.rateLimits?.minPeriodMs? or 1000
# See if command has been executed recently
if lastExecutedTime.hasOwnProperty(context.listener.options.id) and
lastExecutedTime[context.listener.options.id] > Date.now() - minPeriodMs
# Command is being executed too quickly!
done()
else
next ->
lastExecutedTime[context.listener.options.id] = Date.now()
done()
catch err
robot.emit('error', err, context.response)
In this example, the middleware checks to see if the listener has been executed in the last 1,000ms. If it has, the middleware calls done
immediately, preventing the listener callback from being called. If the listener is allowed to execute, the middleware attaches a done
handler so that it can record the time the listener finished executing.
This example also shows how listener-specific metadata can be leveraged to create very powerful extensions: a script developer can use the rate limiting middleware to easily rate limit commands at different rates by just adding the middleware and setting a listener option.
module.exports = (robot) ->
robot.hear /hello/, id: 'my-hello', rateLimits: {minPeriodMs: 10000}, (msg) ->
# This will execute no faster than once every ten seconds
msg.reply 'Why, hello there!'
Listener middleware callbacks receive three arguments, context
, next
, and
done
. See the middleware API for a description
of next
and done
. Listener middleware context includes these fields:
-
listener
-
options
: a simple Object containing options set when defining the listener. See Listener Metadata. - all other properties should be considered internal
-
-
response
- all parts of the standard response API are included in the middleware API. See Send & Reply.
- middleware may decorate (but not modify) the response object with additional information (e.g. add a property to
response.message.user
with a user's LDAP groups) - note: the textual message (
response.message.text
) should be considered immutable in listener middleware
Receive middleware runs before any listeners have executed. It's suitable for blacklisting commands that have not been updated to add an ID, metrics, and more.
This simple middlware bans hubot use by a particular user, including hear
listeners. If the user attempts to run a command explicitly, it will return
an error message.
BLACKLISTED_USERS = [
'12345' # Restrict access for a user ID for a contractor
]
robot.receiveMiddleware (context, next, done) ->
if context.response.message.user.id in BLACKLISTED_USERS
# Don't process this message further.
context.response.message.finish()
# If the message starts with 'hubot' or the alias pattern, this user was
# explicitly trying to run a command, so respond with an error message.
if context.response.message.text?.match(robot.respondPattern(''))
context.response.reply "I'm sorry @#{context.response.message.user.name}, but I'm configured to ignore your commands."
# Don't process further middleware.
done()
else
next(done)
Receive middleware callbacks receive three arguments, context
, next
, and
done
. See the middleware API for a description
of next
and done
. Receive middleware context includes these fields:
-
response
- this response object will not have a
match
property, as no listeners have been run yet to match it. - middleware may decorate the response object with additional information (e.g. add a property to
response.message.user
with a user's LDAP groups) - middleware may modify the
response.message
object
- this response object will not have a
Response middleware runs against every message hubot sends to a chat room. It's helpful for message formatting, preventing password leaks, metrics, and more.
This simple example changes the format of links sent to a chat room from markdown links (like example) to the format supported by Slack, https://example.com|example.
module.exports = (robot) ->
robot.responseMiddleware (context, next, done) ->
return unless context.plaintext?
context.strings = (string.replace(/\[([^\[\]]*?)\]\((https?:\/\/.*?)\)/, "<$2|$1>") for string in context.strings)
next()
Response middleware callbacks receive three arguments, context
, next
, and
done
. See the middleware API for a description
of next
and done
. Receive middleware context includes these fields:
-
response
- This response object can be used to send new messages from the middleware. Middleware will be called on these new responses. Be careful not to create infinite loops.
-
strings
- An array of strings being sent to the chat room adapter. You can edit these, or use
context.strings = ["new strings"]
to replace them.
- An array of strings being sent to the chat room adapter. You can edit these, or use
-
method
- A string representing which type of response message the listener sent, such as
send
,reply
,emote
ortopic
.
- A string representing which type of response message the listener sent, such as
-
plaintext
-
true
orundefined
. This will be set totrue
if the message is of a normal plaintext type, such assend
orreply
. This property should be treated as read-only.
-
hubot-test-helper 是一个用于 Hubot 脚本单元测试的框架(建议使用最新版本Node)。
为 Hubot 实例安装 hubot-test-helper:
% npm install hubot-test-helper --save-dev
还需要安装以下包:
- 一个JavaScript测试框架,例如 Mocha
- 一个断言库,例如 chai 或 expect.js
可能需要安装:
- coffee-script (如果需要测试CoffeeScript)
- mock库,如 Sinon.js (如果你的脚本执行 webservice 调用或其他异步操作)
这里有一个简单的脚本,以及测试命令在 Hubot sample script。该脚本使用 Mocha, chai, coffee-script 和 hubot-test-helper:
test/example-test.coffee
Helper = require('hubot-test-helper')
chai = require 'chai'
expect = chai.expect
helper = new Helper('../scripts/example.coffee')
describe 'example script', ->
beforeEach ->
@room = helper.createRoom()
afterEach ->
@room.destroy()
it 'doesn\'t need badgers', ->
@room.user.say('alice', 'did someone call for a badger?').then =>
expect(@room.messages).to.eql [
['alice', 'did someone call for a badger?']
['hubot', 'Badgers? BADGERS? WE DON\'T NEED NO STINKIN BADGERS']
]
it 'won\'t open the pod bay doors', ->
@room.user.say('bob', '@hubot open the pod bay doors').then =>
expect(@room.messages).to.eql [
['bob', '@hubot open the pod bay doors']
['hubot', '@bob I\'m afraid I can\'t let you do that.']
]
it 'will open the dutch doors', ->
@room.user.say('bob', '@hubot open the dutch doors').then =>
expect(@room.messages).to.eql [
['bob', '@hubot open the dutch doors']
['hubot', '@bob Opening dutch doors']
]
sample output
% mocha --compilers "coffee:coffee-script/register" test/*.coffee
example script
✓ doesn't need badgers
✓ won't open the pod bay doors
✓ will open the dutch doors
3 passing (212ms)