forked from slackapi/hubot-slack
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Dumping almost everything and starting over. Have the basics of conne…
…cting and receiving messages working
- Loading branch information
1 parent
f181a19
commit c14b4d1
Showing
3 changed files
with
59 additions
and
234 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,257 +1,85 @@ | ||
{Robot, Adapter, TextMessage} = require 'hubot' | ||
https = require 'https' | ||
SlackClient = require 'slack-client' | ||
Util = require 'util' | ||
|
||
class Slack extends Adapter | ||
class SlackBot extends Adapter | ||
constructor: (robot) -> | ||
super robot | ||
@channelMapping = {} | ||
|
||
|
||
################################################################### | ||
# Slightly abstract logging, primarily so that it can | ||
# be easily altered for unit tests. | ||
################################################################### | ||
log: console.log.bind console | ||
logError: console.error.bind console | ||
|
||
|
||
################################################################### | ||
# Communicating back to the chat rooms. These are exposed | ||
# as methods on the argument passed to callbacks from | ||
# robot.respond, robot.listen, etc. | ||
################################################################### | ||
send: (envelope, strings...) -> | ||
@log "Sending message" | ||
channel = envelope.reply_to || @channelMapping[envelope.room] || envelope.room | ||
|
||
strings.forEach (str) => | ||
str = @escapeHtml str | ||
args = JSON.stringify | ||
username : @robot.name | ||
channel : channel | ||
text : str | ||
link_names : @options.link_names if @options?.link_names? | ||
|
||
@post "/services/hooks/hubot", args | ||
|
||
reply: (envelope, strings...) -> | ||
@log "Sending reply" | ||
|
||
user_name = envelope.user?.name || envelope?.name | ||
|
||
strings.forEach (str) => | ||
@send envelope, "#{user_name}: #{str}" | ||
|
||
topic: (params, strings...) -> | ||
# TODO: Set the topic | ||
|
||
|
||
custom: (message, data)-> | ||
@log "Sending custom message" | ||
channel = message.reply_to || @channelMapping[message.room] || message.room | ||
data = [data] unless Array.isArray data | ||
attachments = [] | ||
for item in data | ||
attachments.push | ||
text : @escapeHtml item.text | ||
fallback : @escapeHtml item.fallback | ||
pretext : @escapeHtml item.pretext | ||
color : item.color | ||
fields : item.fields | ||
mrkdwn_in : item.mrkdwn_in | ||
args = JSON.stringify | ||
username : message.username || @robot.name | ||
icon_url : message.icon_url | ||
icon_emoji : message.icon_emoji | ||
channel : channel | ||
attachments : attachments | ||
link_names : @options.link_names if @options?.link_names? | ||
@post "/services/hooks/hubot", args | ||
################################################################### | ||
# HTML helpers. | ||
################################################################### | ||
escapeHtml: (string) -> | ||
try | ||
string | ||
# Escape entities | ||
.replace(/&/g, '&') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
|
||
# Linkify. We assume that the bot is well-behaved and | ||
# consistently sending links with the protocol part | ||
.replace(/((\bhttp)\S+)/g, '<$1>') | ||
catch e | ||
@logError "Failed to escape HTML: #{e}" | ||
return '' | ||
|
||
unescapeHtml: (string) -> | ||
try | ||
string | ||
# Unescape entities | ||
.replace(/&/g, '&') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
|
||
# Convert markup into plain url string. | ||
.replace(/<((\bhttps?)[^|]+)(\|(.*))+>/g, '$1') | ||
.replace(/<((\bhttps?)(.*))?>/g, '$1') | ||
catch e | ||
@logError "Failed to unescape HTML: #{e}" | ||
return '' | ||
|
||
|
||
################################################################### | ||
# Parsing inputs. | ||
################################################################### | ||
|
||
parseOptions: -> | ||
@options = | ||
token : process.env.HUBOT_SLACK_TOKEN | ||
team : process.env.HUBOT_SLACK_TEAM | ||
name : process.env.HUBOT_SLACK_BOTNAME or 'slackbot' | ||
mode : process.env.HUBOT_SLACK_CHANNELMODE or 'blacklist' | ||
# Make sure channel settings don't include leading hashes | ||
channels: (process.env.HUBOT_SLACK_CHANNELS?.split(',') or []).map (channel) -> | ||
channel.replace /^#/, '' | ||
link_names: process.env.HUBOT_SLACK_LINK_NAMES or 0 | ||
|
||
getMessageFromRequest: (req) -> | ||
# Check the token | ||
return if not req.param('token') or (req.param('token') isnt @options.token) | ||
|
||
# Parse the payload | ||
hubotMsg = req.param 'text' | ||
room = req.param 'channel_name' | ||
mode = @options.mode | ||
channels = @options.channels | ||
|
||
@unescapeHtml hubotMsg if hubotMsg and (mode is 'blacklist' and room not in channels or mode is 'whitelist' and room in channels) | ||
|
||
getAuthorFromRequest: (req) -> | ||
# Return an author object | ||
id : req.param 'user_id' | ||
name : req.param 'user_name' | ||
|
||
userFromParams: (params) -> | ||
# hubot < 2.4.2: params = user | ||
# hubot >= 2.4.2: params = {user: user, ...} | ||
user = {} | ||
if params.user | ||
user = params.user | ||
else | ||
user = params | ||
|
||
if user.room and not user.reply_to | ||
user.reply_to = user.room | ||
|
||
user | ||
################################################################### | ||
# The star. | ||
################################################################### | ||
run: -> | ||
self = @ | ||
@parseOptions() | ||
# Take our options from the environment, and set otherwise suitable defaults | ||
options = | ||
token: process.env.HUBOT_SLACK_TOKEN | ||
autoReconnect: true | ||
autoMark: true | ||
|
||
@log "Slack adapter options:", @options | ||
@robot.logger.info Util.inspect(options) | ||
return @robot.logger.error "No services token provided to Hubot" unless options.token | ||
|
||
return @logError "No services token provided to Hubot" unless @options.token | ||
return @logError "No team provided to Hubot" unless @options.team | ||
@options = options | ||
|
||
@robot.on 'slack-attachment', (payload)=> | ||
@custom(payload.message, payload.content) | ||
# Create our slack client object | ||
@client = new SlackClient options.token, options.autoReconnect, options.autoMark | ||
|
||
# Listen to incoming webhooks from slack | ||
self.robot.router.post "/hubot/slack-webhook", (req, res) -> | ||
self.log "Incoming message received" | ||
# Setup event handlers | ||
# TODO: I think hubot would like to know when people come online | ||
# TODO: Handle eventual events at (re-)connection time for unreads and provide a config for whether we want to process them | ||
@client.on 'error', @.error | ||
@client.on 'loggedIn', @.loggedIn | ||
@client.on 'open', @.open | ||
@client.on 'close', @.close | ||
@client.on 'message', @.message | ||
|
||
hubotMsg = self.getMessageFromRequest req | ||
author = self.getAuthorFromRequest req | ||
author = self.robot.brain.userForId author.id, author | ||
author.reply_to = req.param 'channel_id' | ||
author.room = req.param 'channel_name' | ||
self.channelMapping[req.param 'channel_name'] = req.param 'channel_id' | ||
# Start logging in | ||
@client.login() | ||
|
||
if hubotMsg and author | ||
# Pass to the robot | ||
self.receive new TextMessage(author, hubotMsg) | ||
error: (error) => | ||
@robot.logger.error "Received error #{error.toString()}" | ||
|
||
# Just send back an empty reply, since our actual reply, | ||
# if any, will be async above | ||
res.end "" | ||
loggedIn: (self, team) => | ||
@robot.logger.info "Logged in as #{self.name} of #{team.name}, but not yet connected" | ||
|
||
# Provide our name to Hubot | ||
self.robot.name = @options.name | ||
@robot.name = self.name | ||
|
||
# Tell Hubot we're connected so it can load scripts | ||
@log "Successfully 'connected' as", self.robot.name | ||
self.emit "connected" | ||
|
||
|
||
################################################################### | ||
# Convenience HTTP Methods for sending data back to slack. | ||
################################################################### | ||
get: (path, callback) -> | ||
@request "GET", path, null, callback | ||
|
||
post: (path, body, callback) -> | ||
@request "POST", path, body, callback | ||
open: => | ||
@robot.logger.info 'Slack client connected' | ||
|
||
request: (method, path, body, callback) -> | ||
self = @ | ||
|
||
host = "#{@options.team}.slack.com" | ||
headers = | ||
Host: host | ||
# Tell Hubot we're connected so it can load scripts | ||
@emit "connected" | ||
|
||
path += "?token=#{@options.token}" | ||
close: => | ||
@robot.logger.info 'Slack client closed' | ||
|
||
reqOptions = | ||
agent : false | ||
hostname : host | ||
port : 443 | ||
path : path | ||
method : method | ||
headers : headers | ||
message: (message) => | ||
if message.hidden then return | ||
if not message.text and not message.attachments then return | ||
|
||
if method is "POST" | ||
body = new Buffer body | ||
reqOptions.headers["Content-Type"] = "application/x-www-form-urlencoded" | ||
reqOptions.headers["Content-Length"] = body.length | ||
channel = @client.getChannelGroupOrDMByID message.channel | ||
user = @client.getUserByID message.user | ||
# TODO: Handle message.username for bot messages? | ||
# TODO: Do we need to ignore our own messages? Probably! | ||
|
||
request = https.request reqOptions, (response) -> | ||
data = "" | ||
response.on "data", (chunk) -> | ||
data += chunk | ||
# Build message text to respond to, including all attachments | ||
txt = '' | ||
if message.text then txt += message.text | ||
|
||
if message.attachments | ||
for k, attach of message.attachments | ||
if k > 0 then txt += "\n" | ||
txt += attach.fallback | ||
|
||
response.on "end", -> | ||
if response.statusCode >= 400 | ||
self.logError "Slack services error: #{response.statusCode}" | ||
self.logError data | ||
# TODO: Need to process the user into a full hubot user using @robot.brain.userForId user etc | ||
|
||
#console.log "HTTPS response:", data | ||
callback? null, data | ||
@robot.logger.debug "Received message: #{txt} in channel: #{channel.name}, from: #{user.name}" | ||
|
||
response.on "error", (err) -> | ||
self.logError "HTTPS response error:", err | ||
callback? err, null | ||
@receive new TextMessage(user, txt) | ||
|
||
if method is "POST" | ||
request.end body, "binary" | ||
else | ||
request.end() | ||
# TODO: Send | ||
|
||
request.on "error", (err) -> | ||
self.logError "HTTPS request error:", err | ||
self.logError err.stack | ||
callback? err | ||
# TODO: Reply | ||
|
||
# TODO: Topic | ||
|
||
################################################################### | ||
# Exports to handle actual usage and unit testing. | ||
################################################################### | ||
exports.use = (robot) -> | ||
new Slack robot | ||
|
||
# Export class for unit tests | ||
exports.Slack = Slack | ||
new SlackBot robot |