1 | {Robot, Adapter, EnterMessage, LeaveMessage, TopicMessage} = require 'brobbot'
|
2 | {SlackTextMessage, SlackRawMessage, SlackBotMessage} = require './message'
|
3 | {SlackRawListener, SlackBotListener} = require './listener'
|
4 |
|
5 | SlackClient = require 'slack-client'
|
6 | Util = require 'util'
|
7 | Q = require 'q'
|
8 |
|
9 | class SlackBot extends Adapter
|
10 | @MAX_MESSAGE_LENGTH: 4000
|
11 | @MIN_MESSAGE_LENGTH: 1
|
12 |
|
13 | constructor: (robot) ->
|
14 | @robot = robot
|
15 | @readyDefer = Q.defer()
|
16 | @ready = @readyDefer.promise
|
17 |
|
18 | run: ->
|
19 |
|
20 | options =
|
21 | token: process.env.BROBBOT_SLACK_TOKEN
|
22 | autoReconnect: true
|
23 | autoMark: true
|
24 |
|
25 | return @robot.logger.error "No services token provided to Brobbot" unless options.token
|
26 | return @robot.logger.error "v2 services token provided, please follow the upgrade instructions" unless (options.token.substring(0, 5) == 'xoxb-')
|
27 |
|
28 | @options = options
|
29 |
|
30 |
|
31 | @client = new SlackClient options.token, options.autoReconnect, options.autoMark
|
32 |
|
33 |
|
34 | @client.on 'error', @.error
|
35 | @client.on 'loggedIn', @.loggedIn
|
36 | @client.on 'open', @.open
|
37 | @client.on 'close', @.clientClose
|
38 | @client.on 'message', @.message
|
39 | @client.on 'userChange', @.userChange
|
40 |
|
41 |
|
42 | @client.login()
|
43 |
|
44 | error: (error) =>
|
45 | @robot.logger.error "Received error #{JSON.stringify error}"
|
46 | @robot.logger.error error.stack
|
47 | @robot.logger.error "Exiting in 1 second"
|
48 | setTimeout process.exit.bind(process, 1), 1000
|
49 |
|
50 | loggedIn: (self, team) =>
|
51 | @robot.logger.info "Logged in as #{self.name} of #{team.name}, but not yet connected"
|
52 |
|
53 |
|
54 | @self = self
|
55 |
|
56 |
|
57 | @robot.name = self.name
|
58 |
|
59 | for id, user of @client.users
|
60 | @userChange user
|
61 |
|
62 | userChange: (user) =>
|
63 | newUser = {id: user.id, name: user.name, real_name: user.real_name, email_address: user.profile.email}
|
64 |
|
65 | @robot.brain.addUser(newUser).then =>
|
66 | @robot.brain.userForId user.id
|
67 |
|
68 | open: =>
|
69 | @robot.logger.info 'Slack client now connected'
|
70 |
|
71 |
|
72 | @readyDefer.resolve()
|
73 |
|
74 | clientClose: =>
|
75 | @robot.logger.info 'Slack client closed'
|
76 | @client.removeListener 'error', @.error
|
77 | @client.removeListener 'loggedIn', @.loggedIn
|
78 | @client.removeListener 'open', @.open
|
79 | @client.removeListener 'close', @.clientClose
|
80 | @client.removeListener 'message', @.message
|
81 | process.exit 0
|
82 |
|
83 | message: (msg) =>
|
84 |
|
85 | return if msg.user == @self.id
|
86 |
|
87 | channel = @client.getChannelGroupOrDMByID msg.channel if msg.channel
|
88 |
|
89 | if msg.hidden or (not msg.text and not msg.attachments) or msg.subtype is 'bot_message' or not msg.user or not channel
|
90 |
|
91 |
|
92 | if msg.user
|
93 | getUser = @robot.brain.userForId msg.user
|
94 | else
|
95 |
|
96 |
|
97 | user = {}
|
98 | user.name = msg.username if msg.username?
|
99 | getUser = Q user
|
100 |
|
101 | getUser.then (user) =>
|
102 | user.room = channel.name if channel
|
103 |
|
104 | rawText = msg.getBody()
|
105 | text = @removeFormatting rawText
|
106 |
|
107 | if msg.subtype is 'bot_message'
|
108 | @robot.logger.debug "Received bot message: '#{text}' in channel: #{channel?.name}, from: #{user?.name}"
|
109 | @receive new SlackBotMessage user, text, rawText, msg
|
110 | else
|
111 | @robot.logger.debug "Received raw message (subtype: #{msg.subtype})"
|
112 | @receive new SlackRawMessage user, text, rawText, msg
|
113 |
|
114 | else
|
115 |
|
116 | @robot.brain.userForId(msg.user).then (user) =>
|
117 | user.room = channel.name
|
118 |
|
119 |
|
120 | if msg.subtype is 'channel_join' or msg.subtype is 'group_join'
|
121 | @robot.logger.debug "#{user.name} has joined #{channel.name}"
|
122 | @receive new EnterMessage user
|
123 |
|
124 | else if msg.subtype is 'channel_leave' or msg.subtype is 'group_leave'
|
125 | @robot.logger.debug "#{user.name} has left #{channel.name}"
|
126 | @receive new LeaveMessage user
|
127 |
|
128 | else if msg.subtype is 'channel_topic' or msg.subtype is 'group_topic'
|
129 | @robot.logger.debug "#{user.name} set the topic in #{channel.name} to #{msg.topic}"
|
130 | @receive new TopicMessage user, msg.topic, msg.ts
|
131 |
|
132 | else
|
133 |
|
134 | rawText = msg.getBody()
|
135 | text = @removeFormatting rawText
|
136 |
|
137 | @robot.logger.debug "Received message: '#{text}' in channel: #{channel.name}, from: #{user.name}"
|
138 |
|
139 |
|
140 | if msg.getChannelType() == 'DM'
|
141 | text = "#{@robot.name} #{text}"
|
142 |
|
143 | @receive new SlackTextMessage user, text, rawText, msg
|
144 |
|
145 | removeFormatting: (text) ->
|
146 |
|
147 | text = text.replace ///
|
148 | <
|
149 | ([@
|
150 | ([^>|]+)
|
151 | (?:\|
|
152 | ([^>]+)
|
153 | )?
|
154 | >
|
155 | ///g, (m, type, link, label) =>
|
156 |
|
157 | switch type
|
158 |
|
159 | when '@'
|
160 | if label then return label
|
161 | user = @client.getUserByID link
|
162 | if user
|
163 | return "@#{user.name}"
|
164 |
|
165 | when '#'
|
166 | if label then return label
|
167 | channel = @client.getChannelByID link
|
168 | if channel
|
169 | return "\##{channel.name}"
|
170 |
|
171 | when '!'
|
172 | if link in ['channel','group','everyone']
|
173 | return "@#{link}"
|
174 |
|
175 | else
|
176 | link = link.replace /^mailto:/, ''
|
177 | if label and -1 == link.indexOf label
|
178 | "#{label} (#{link})"
|
179 | else
|
180 | link
|
181 | text = text.replace /</g, '<'
|
182 | text = text.replace />/g, '>'
|
183 | text = text.replace /&/g, '&'
|
184 | text
|
185 |
|
186 | send: (envelope, messages...) ->
|
187 | channel = @client.getChannelGroupOrDMByName envelope.room
|
188 |
|
189 | for msg in messages
|
190 | continue if msg.length < SlackBot.MIN_MESSAGE_LENGTH
|
191 |
|
192 | @robot.logger.debug "Sending to #{envelope.room}: #{msg}"
|
193 |
|
194 | if msg.length <= SlackBot.MAX_MESSAGE_LENGTH
|
195 | channel.send msg
|
196 |
|
197 |
|
198 | else
|
199 | submessages = []
|
200 |
|
201 | while msg.length > 0
|
202 | if msg.length <= SlackBot.MAX_MESSAGE_LENGTH
|
203 | submessages.push msg
|
204 | msg = ''
|
205 |
|
206 | else
|
207 |
|
208 | maxSizeChunk = msg.substring(0, SlackBot.MAX_MESSAGE_LENGTH)
|
209 |
|
210 | lastLineBreak = maxSizeChunk.lastIndexOf('\n')
|
211 | lastWordBreak = maxSizeChunk.match(/\W\w+$/)?.index
|
212 |
|
213 | breakIndex = if lastLineBreak > -1
|
214 | lastLineBreak
|
215 | else if lastWordBreak
|
216 | lastWordBreak
|
217 | else
|
218 | SlackBot.MAX_MESSAGE_LENGTH
|
219 |
|
220 | submessages.push msg.substring(0, breakIndex)
|
221 |
|
222 |
|
223 | breakIndex++ if breakIndex isnt SlackBot.MAX_MESSAGE_LENGTH
|
224 |
|
225 | msg = msg.substring(breakIndex, msg.length)
|
226 |
|
227 | channel.send m for m in submessages
|
228 |
|
229 | reply: (envelope, messages...) ->
|
230 | @robot.logger.debug "Sending reply"
|
231 |
|
232 | for msg in messages
|
233 |
|
234 | @send envelope, "#{envelope.user.name}: #{msg}"
|
235 |
|
236 | topic: (envelope, strings...) ->
|
237 | channel = @client.getChannelGroupOrDMByName envelope.room
|
238 | channel.setTopic strings.join "\n"
|
239 |
|
240 | module.exports = SlackBot
|