diff --git a/README.md b/README.md index 269e929..a14b23a 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,27 @@ Bonus: Add a notification hook to Heroku so a notification is sent to a room whe robot.messageRoom("1234_room@conf.hipchat.com", "message"); ``` +## Features specific to hubot-hipchat + +In addition to the response methods provided by the normal hubot, such as ```resp.send```, hubot-hipchat provides these hipchat specific functions: +* ```resp.sendHtml``` takes any number of strings and sticks them together as one html message post. For example, you can send a hyperlink by doing this: +``` +robot.respond /send hyperlink/i, (resp)-> + resp.sendHtml "hipchat site" +``` + +* ```resp.sendFile``` takes an object that contains either file data or a path to a file along with other file info and posts the file to a room with an optional message: +``` +robot.respond /send file/i, (resp) -> + file_info = + name : "the name people will see in the room" + path : 'path/to/file.text' + type: "text" # required + msg : "optional message to post when file is uploaded" + + resp.sendFile file_info +``` + ## Adapter configuration This adapter uses the following environment variables: @@ -135,6 +156,10 @@ Optional. Set to `debug` to enable detailed debug logging. Optional. Seting to `false` will prevent the HipChat adapter from auto-reconnecting if it detects a server error or disconnection. +### HUBOT\_HIPCHAT\_TOKEN + +Optional. Set to the value of an API token for HipChat. Generate an API token [here](https://cs10.hipchat.com/account/api) (and give it the necessary scopes to post in rooms, the easiest way is just give it access to all scopes). This needs to be set in order to use the sendFile and sendHtml methods. + ## Running locally To run locally on OSX or Linux you'll need to set the required environment variables and run the `bin/hubot` script. An example script to run the bot might look like: diff --git a/package.json b/package.json index 704f183..67c6158 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ "license": "MIT", "main": "./src/hipchat", "engines": { - "node" : "~0.12.2" + "node": "~0.12.2" }, "dependencies": { + "mime": "^1.3.4", "node-xmpp-client": "^2.1.0", + "request": "^2.67.0", "rsvp": "~1.2.0", "underscore": "~1.4.4" } diff --git a/src/hipchat.coffee b/src/hipchat.coffee index d2868c2..df5f555 100644 --- a/src/hipchat.coffee +++ b/src/hipchat.coffee @@ -1,52 +1,142 @@ -{Adapter, TextMessage, EnterMessage, LeaveMessage, TopicMessage, User} = require "hubot" +fs = require "fs" HTTPS = require "https" +{Adapter, TextMessage, EnterMessage, LeaveMessage, TopicMessage, User} = require "hubot" {inspect} = require "util" +requestLib = require "request" # requestLib to avoid confusion with adapter's request method +mime = require "mime" Connector = require "./connector" promise = require "./promises" +HipChatResponse = require './response' class HipChat extends Adapter constructor: (robot) -> super robot @logger = robot.logger + @room_endpoint = "http://www.hipchat.com/v2/room" + @robot.Response = HipChatResponse reconnectTimer = null emote: (envelope, strings...) -> @send envelope, strings.map((str) -> "/me #{str}")... - send: (envelope, strings...) -> + extractJid: (envelope) -> {user, room} = envelope user = envelope if not user # pre-2.4.2 style + # most common case - we're replying to a user in a room or 1-1 + user?.reply_to or + # allows user objects to be passed in + user?.jid or + if user?.search?(/@/) >= 0 + user # allows user to be a jid string + else + room # this will happen if someone uses robot.messageRoom(jid, ...) - target_jid = - # most common case - we're replying to a user in a room or 1-1 - user?.reply_to or - # allows user objects to be passed in - user?.jid or - if user?.search?(/@/) >= 0 - user # allows user to be a jid string - else - room # this will happen if someone uses robot.messageRoom(jid, ...) + send: (envelope, strings...) -> + target_jid = @extractJid(envelope) + if not target_jid return @logger.error "ERROR: Not sure who to send to: envelope=#{inspect envelope}" for str in strings @connector.message target_jid, str + sendHtml: (envelope, strings...) -> + target_jid = @extractJid(envelope) + + if not target_jid + return @logger.error "Not sure who to send html message to: envelope=#{inspect envelope}" + + if not @options.token + return @logger.error "Must set HUBOT_HIPCHAT_TOKEN to send html messages" + + room_id = @room_map[target_jid].id + fullMsg = strings.join('') + + params = + url: "#{@room_endpoint}/#{room_id}/notification" + headers : + 'content-type' : 'text/html' + auth: + bearer : @options.token + body: fullMsg + + requestLib.post params, (err,resp,body) => + if err || resp.statusCode >= 400 + return @logger.error "HipChat API error: #{resp.statusCode}" + + # Send a file from hubot + # file_info = + # name : the name to share the file with + # path : send file from this path (a string) + # data : send this base64 encoded data as a file + # type (required) : the type of the file (text, pdf, json, csv etc...) + # msg (optional) : a simple text msg to be posted along with the file + sendFile: (envelope, file_info) -> + target_jid = @extractJid(envelope) + + if not target_jid + return @logger.error "Not sure who to send file to: envelope=#{inspect envelope}" + + if not @options.token + return @logger.error "Must set HUBOT_HIPCHAT_TOKEN to send html messages" + + room_id = @room_map[target_jid].id + url = "#{@room_endpoint}/#{room_id}/share/file" + mimeType = mime.lookup(file_info.type) + ext = mime.extension(mimeType) + + if not file_info.type || not mimeType + return @logger.error "A valid type must be provided to sendFile. Type was: #{file_info.type}" + + if not file_info.msg + file_info.msg = '' + + if file_info.path + fs.readFile file_info.path, (err, data) => + if err + return @logger.error "File Read Error: could not read from file path: #{file_info.path}" + + @sendMultipart url, file_info.name + '.' + ext, data, mimeType, file_info.msg + + else if file_info.data + @sendMultipart url, file_info.name + '.' + ext, file_info.data, mimeType, file_info.msg + + else + return @logger.error "Must specify either data or path for sendFile" + + sendMultipart: (path, name, data, mimeType, msg) -> + + # Must have filename="name" etc... in double quotes not single + quotedName = '"' + name + '"' + params = + method: 'POST' + url: path + auth: + bearer: @options.token + multipart: [ + { + "Content-Type": "application/json; charset UTF-8", + "Content-Disposition": 'attachment; name="metadata"', + "body": JSON.stringify "message": msg + } + , + { + "Content-Type": "file/" + mimeType, + "Content-Disposition": 'attachment; name="file"; filename=' + quotedName, + "body": data + } + ] + + requestLib params, (err, resp, body) => + if resp.statusCode >= 400 + return @logger.error "HipChat API errror: #{resp.statusCode}" + + topic: (envelope, message) -> - {user, room} = envelope - user = envelope if not user # pre-2.4.2 style - target_jid = - # most common case - we're replying to a user in a room or 1-1 - user?.reply_to or - # allows user objects to be passed in - user?.jid or - if user?.search?(/@/) >= 0 - user # allows user to be a jid string - else - room # this will happen if someone uses robot.messageRoom(jid, ...) + target_jid = extractJid(envelope) if not target_jid return @logger.error "ERROR: Not sure who to send to: envelope=#{inspect envelope}" @@ -165,9 +255,17 @@ class HipChat extends Adapter init .done (users) => saveUsers(users) - # Join requested rooms - if @options.rooms is "All" or @options.rooms is "@All" - connector.getRooms (err, rooms, stanza) => + + connector.getRooms (err, rooms, stanza) => + + # Save room data to make api calls + if rooms + @room_map = {} + for room in rooms + @room_map[room.jid] = room + + # Join all rooms + if @options.rooms is "All" or @options.rooms is "@All" if rooms for room in rooms if !@options.rooms_join_public && room.guest_url != '' @@ -176,10 +274,11 @@ class HipChat extends Adapter joinRoom(room.jid) else @logger.error "Can't list rooms: #{errmsg err}" - # Join all rooms - else - for room_jid in @options.rooms.split "," - joinRoom(room_jid) + # Join requested rooms + else + for room_jid in @options.rooms.split "," + joinRoom(room_jid) + .fail (err) => @logger.error "Can't list users: #{errmsg err}" if err diff --git a/src/response.coffee b/src/response.coffee new file mode 100644 index 0000000..3f7db7a --- /dev/null +++ b/src/response.coffee @@ -0,0 +1,11 @@ +{Response} = require 'hubot' + +class HipChatResponse extends Response + + sendFile: (file_info) -> + @robot.adapter.sendFile(@envelope, file_info) + + sendHtml: (strings...) -> + @robot.adapter.sendHtml(@envelope, strings...) + +module.exports = HipChatResponse \ No newline at end of file