lita-slack 1.7.2 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b66cc314ceaee4c634dd501c8a7913548f8c437b
4
- data.tar.gz: 25e02797ea080058b6b149a6000f1aaa5366c840
3
+ metadata.gz: ca96105086115398590afb975e36f44633faf31d
4
+ data.tar.gz: 21f62a27417aa3fa13fbfc4f4c4091319ee84265
5
5
  SHA512:
6
- metadata.gz: 96be42cc5971af17763d58f6a9ed7d48b47a7fcd5b766a5f0801c35445de9d4e00f988d5b7adb244745728541456f31ad1e4936c8a9c8a63e2484adb644e70d3
7
- data.tar.gz: 3962f0e8955b7eb199e7c6017c560baaabcb87119262b95f79f1efc70d79c8bdde4eb9b25d2ea3e906e356359a7a427456a0efb3e474e6070c7294085835439b
6
+ metadata.gz: c93fb3be8f2403c50766c7d6cb0127e528efd05e3da10f21aeda34364bef857f0b01ff7b7dcee1b9666f8bc2cd44306a87bd9540a613c241aea8691c1e793aab
7
+ data.tar.gz: 5dd83d261ce894a9992ac06a1d7af3830cb98cebf8e70bb045f0ae67151628bb81a84b4baca1248d9e37c0454f4f84857d2a83aecb61f0223b73b750a143a7f8
@@ -1,7 +1,8 @@
1
1
  language: ruby
2
2
  sudo: false
3
+ cache: bundler
3
4
  rvm:
4
- - 2.0.0
5
+ - "2.0"
5
6
  script: bundle exec rake
6
7
  before_install:
7
8
  - gem update --system
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # lita-slack
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/lita-slack.png)](http://badge.fury.io/rb/lita-slack)
4
- [![Build Status](https://travis-ci.org/kenjij/lita-slack.png?branch=master)](https://travis-ci.org/kenjij/lita-slack)
4
+ [![Build Status](https://travis-ci.org/litaio/lita-slack.png?branch=master)](https://travis-ci.org/litaio/lita-slack)
5
5
 
6
6
  **lita-slack** is an adapter for [Lita](https://www.lita.io/) that allows you to use the robot with [Slack](https://slack.com/). The current adapter is not compatible with pre-1.0.0 versions, as it now uses Slack's [Real Time Messaging API](https://api.slack.com/rtm).
7
7
 
@@ -21,7 +21,11 @@ gem "lita-slack"
21
21
 
22
22
  ### Optional attributes
23
23
 
24
+ * `link_names` (Boolean) – Set to `true` to turn all Slack usernames in messages sent by Lita into links.
25
+ * `parse` (String) – Specify the parsing mode. See https://api.slack.com/docs/formatting#parsing_modes.
24
26
  * `proxy` (String) – Specify a HTTP proxy URL. (e.g. "http://squid.example.com:3128")
27
+ * `unfurl_links` (Boolean) – Set to `true` to automatically add previews for all links in messages sent by Lita.
28
+ * `unfurl_media` (Boolean) – Set to `false` to prevent automatic previews for media files in messages sent by Lita.
25
29
 
26
30
  **Note**: When using lita-slack, the adapter will overwrite the bot's name and mention name with the values set on the server, so `config.robot.name` and `config.robot.mention_name` will have no effect.
27
31
 
@@ -35,7 +39,13 @@ Each Slack user has a unique ID that never changes even if their real name or us
35
39
  Lita.configure do |config|
36
40
  config.robot.adapter = :slack
37
41
  config.robot.admins = ["U012A3BCD"]
42
+
38
43
  config.adapters.slack.token = "abcd-1234567890-hWYd21AmMH2UHAkx29vb5c1Y"
44
+
45
+ config.adapters.slack.link_names = true
46
+ config.adapters.slack.parse = "full"
47
+ config.adapters.slack.unfurl_links = false
48
+ config.adapters.slack.unfurl_media = false
39
49
  end
40
50
  ```
41
51
 
@@ -47,12 +57,14 @@ Lita will join your default channel after initial setup. To have it join additio
47
57
 
48
58
  * `:connected` - When the robot has connected to Slack. No payload.
49
59
  * `:disconnected` - When the robot has disconnected from Slack. No payload.
50
- * `:slack_user_created` - When the robot creates/updates a user's info - name, mention name, etc., as directed by Slack. The payload has a single object, a `Lita::Slack::Adapters::SlackUser` object, under the `:slack_user` key.
51
60
  * `:slack_channel_created` - When the robot creates/updates a channel's or group's info, as directed by Slack. The payload has a single object, a `Lita::Slack::Adapters::SlackChannel` object, under the `:slack_channel` key.
61
+ * `:slack_reaction_added` - When a reaction has been added to a previous message. The payload includes `:user` (a `Lita::User` for the sender of the message in question), `:name` (the string name of the reaction added), `:item` (a hash of raw data from Slack about the message), and `:event_ts` (a string timestamp used to identify the message).
62
+ * `:slack_reaction_removed` - When a reaction has been removed from a previous message. The payload is the same as the `:slack_reaction_added` message.
63
+ * `:slack_user_created` - When the robot creates/updates a user's info - name, mention name, etc., as directed by Slack. The payload has a single object, a `Lita::Slack::Adapters::SlackUser` object, under the `:slack_user` key.
52
64
 
53
65
  ## Chat service API
54
66
 
55
- lita-slack supports Lita 4.6's chat service API for Slack-specific functionality. You can access this API object by calling the `Lita::Robot#chat_service`. See the API docs for `Lita::Slack::Adapters::ChatService` for details about the provided methods.
67
+ lita-slack supports Lita 4.6's chat service API for Slack-specific functionality. You can access this API object by calling the `Lita::Robot#chat_service`. See the API docs for `Lita::Adapters::Slack::ChatService` for details about the provided methods.
56
68
 
57
69
  ## API documentation
58
70
 
@@ -9,12 +9,20 @@ module Lita
9
9
  # Required configuration attributes.
10
10
  config :token, type: String, required: true
11
11
  config :proxy, type: String
12
+ config :parse, type: [String]
13
+ config :link_names, type: [true, false]
14
+ config :unfurl_links, type: [true, false]
15
+ config :unfurl_media, type: [true, false]
12
16
 
13
17
  # Provides an object for Slack-specific features.
14
18
  def chat_service
15
19
  ChatService.new(config)
16
20
  end
17
21
 
22
+ def mention_format(name)
23
+ "@#{name}"
24
+ end
25
+
18
26
  # Starts the connection.
19
27
  def run
20
28
  return if rtm_connection
@@ -23,10 +31,16 @@ module Lita
23
31
  rtm_connection.run
24
32
  end
25
33
 
26
- def send_messages(target, strings)
27
- return unless rtm_connection
34
+ # Returns UID(s) in an Array or String for:
35
+ # Channels, MPIMs, IMs
36
+ def roster(target)
37
+ api = API.new(config)
38
+ room_roster target.id, api
39
+ end
28
40
 
29
- rtm_connection.send_messages(channel_for(target), strings)
41
+ def send_messages(target, strings)
42
+ api = API.new(config)
43
+ api.send_messages(channel_for(target), strings)
30
44
  end
31
45
 
32
46
  def set_topic(target, topic)
@@ -53,6 +67,45 @@ module Lita
53
67
  target.room
54
68
  end
55
69
  end
70
+
71
+ def channel_roster(room_id, api)
72
+ response = api.channels_info room_id
73
+ response['channel']['members']
74
+ end
75
+
76
+ # Returns the members of a group, but only can do so if it's a member
77
+ def group_roster(room_id, api)
78
+ response = api.groups_list
79
+ group = response['groups'].select { |hash| hash['id'].eql? room_id }.first
80
+ group.nil? ? [] : group['members']
81
+ end
82
+
83
+ # Returns the members of a mpim, but only can do so if it's a member
84
+ def mpim_roster(room_id, api)
85
+ response = api.mpim_list
86
+ mpim = response['groups'].select { |hash| hash['id'].eql? room_id }.first
87
+ mpim.nil? ? [] : mpim['members']
88
+ end
89
+
90
+ # Returns the user of an im
91
+ def im_roster(room_id, api)
92
+ response = api.mpim_list
93
+ im = response['ims'].select { |hash| hash['id'].eql? room_id }.first
94
+ im.nil? ? '' : im['user']
95
+ end
96
+
97
+ def room_roster(room_id, api)
98
+ case room_id
99
+ when /^C/
100
+ channel_roster room_id, api
101
+ when /^G/
102
+ # Groups & MPIMs have the same room ID pattern, check both if needed
103
+ roster = group_roster room_id, api
104
+ roster.empty? ? mpim_roster(room_id, api) : roster
105
+ when /^D/
106
+ im_roster room_id, api
107
+ end
108
+ end
56
109
  end
57
110
 
58
111
  # Register Slack adapter to Lita
@@ -13,6 +13,11 @@ module Lita
13
13
  def initialize(config, stubs = nil)
14
14
  @config = config
15
15
  @stubs = stubs
16
+ @post_message_config = {}
17
+ @post_message_config[:parse] = config.parse unless config.parse.nil?
18
+ @post_message_config[:link_names] = config.link_names ? 1 : 0 unless config.link_names.nil?
19
+ @post_message_config[:unfurl_links] = config.unfurl_links unless config.unfurl_links.nil?
20
+ @post_message_config[:unfurl_media] = config.unfurl_media unless config.unfurl_media.nil?
16
21
  end
17
22
 
18
23
  def im_open(user_id)
@@ -21,6 +26,26 @@ module Lita
21
26
  SlackIM.new(response_data["channel"]["id"], user_id)
22
27
  end
23
28
 
29
+ def channels_info(channel_id)
30
+ call_api("channels.info", channel: channel_id)
31
+ end
32
+
33
+ def channels_list
34
+ call_api("channels.list")
35
+ end
36
+
37
+ def groups_list
38
+ call_api("groups.list")
39
+ end
40
+
41
+ def mpim_list
42
+ call_api("mpim.list")
43
+ end
44
+
45
+ def im_list
46
+ call_api("im.list")
47
+ end
48
+
24
49
  def send_attachments(room_or_user, attachments)
25
50
  call_api(
26
51
  "chat.postMessage",
@@ -30,6 +55,16 @@ module Lita
30
55
  )
31
56
  end
32
57
 
58
+ def send_messages(channel_id, messages)
59
+ call_api(
60
+ "chat.postMessage",
61
+ **post_message_config,
62
+ as_user: true,
63
+ channel: channel_id,
64
+ text: messages.join("\n"),
65
+ )
66
+ end
67
+
33
68
  def set_topic(channel, topic)
34
69
  call_api("channels.setTopic", channel: channel, topic: topic)
35
70
  end
@@ -51,6 +86,7 @@ module Lita
51
86
 
52
87
  attr_reader :stubs
53
88
  attr_reader :config
89
+ attr_reader :post_message_config
54
90
 
55
91
  def call_api(method, post_data = {})
56
92
  response = connection.post(
@@ -79,7 +115,7 @@ module Lita
79
115
 
80
116
  def parse_response(response, method)
81
117
  unless response.success?
82
- raise "Slack API call to #{method} failed with status code #{response.status}."
118
+ raise "Slack API call to #{method} failed with status code #{response.status}: '#{response.body}'. Headers: #{response.headers}"
83
119
  end
84
120
 
85
121
  MultiJson.load(response.body)
@@ -16,6 +16,8 @@ module Lita
16
16
  handle_hello
17
17
  when "message"
18
18
  handle_message
19
+ when "reaction_added", "reaction_removed"
20
+ handle_reaction
19
21
  when "user_change", "team_join"
20
22
  handle_user_change
21
23
  when "bot_added", "bot_changed"
@@ -44,7 +46,7 @@ module Lita
44
46
  normalized_message = remove_formatting(normalized_message) unless normalized_message.nil?
45
47
 
46
48
  attachment_text = Array(data["attachments"]).map do |attachment|
47
- attachment["text"]
49
+ attachment["text"] || attachment["fallback"]
48
50
  end
49
51
 
50
52
  ([normalized_message] + attachment_text).compact.join("\n")
@@ -115,16 +117,13 @@ module Lita
115
117
  source.private_message! if channel && channel[0] == "D"
116
118
  message = Message.new(robot, body, source)
117
119
  message.command! if source.private_message?
120
+ message.extensions[:slack] = { timestamp: data["ts"] }
118
121
  log.debug("Dispatching message to Lita from #{user.id}.")
119
122
  robot.receive(message)
120
123
  end
121
124
 
122
125
  def from_self?(user)
123
- if data["subtype"] == "bot_message"
124
- robot_user = User.find_by_name(robot.name)
125
-
126
- robot_user && robot_user.id == user.id
127
- end
126
+ user.id == robot_id
128
127
  end
129
128
 
130
129
  def handle_bot_change
@@ -151,6 +150,7 @@ module Lita
151
150
 
152
151
  def handle_message
153
152
  return unless supported_subtype?
153
+ return if data["user"] == 'USLACKBOT'
154
154
 
155
155
  user = User.find_by_id(data["user"]) || User.create(data["user"])
156
156
 
@@ -159,6 +159,22 @@ module Lita
159
159
  dispatch_message(user)
160
160
  end
161
161
 
162
+ def handle_reaction
163
+ log.debug "#{type} event received from Slack"
164
+
165
+ # find or create user
166
+ user = User.find_by_id(data["user"]) || User.create(data["user"])
167
+
168
+ # avoid processing reactions added/removed by self
169
+ return if from_self?(user)
170
+
171
+ # build a payload following slack convention for reactions
172
+ payload = { user: user, name: data["reaction"], item: data["item"], event_ts: data["event_ts"] }
173
+
174
+ # trigger the appropriate slack reaction event
175
+ robot.trigger("slack_#{type}".to_sym, payload)
176
+ end
177
+
162
178
  def handle_unknown
163
179
  unless data["reply_to"]
164
180
  log.debug("#{type} event received from Slack and will be ignored.")
@@ -176,7 +192,7 @@ module Lita
176
192
 
177
193
  # Types of messages Lita should dispatch to handlers.
178
194
  def supported_message_subtypes
179
- %w(bot_message me_message)
195
+ %w(me_message)
180
196
  end
181
197
 
182
198
  def supported_subtype?
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "lita-slack"
3
- spec.version = "1.7.2"
3
+ spec.version = "1.8.0"
4
4
  spec.authors = ["Ken J.", "Jimmy Cuadra"]
5
5
  spec.email = ["kenjij@gmail.com", "jimmy@jimmycuadra.com"]
6
6
  spec.description = %q{Lita adapter for Slack.}
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  spec.add_runtime_dependency "eventmachine"
18
18
  spec.add_runtime_dependency "faraday"
19
19
  spec.add_runtime_dependency "faye-websocket", ">= 0.8.0"
20
- spec.add_runtime_dependency "lita", ">= 4.6.0"
20
+ spec.add_runtime_dependency "lita", ">= 4.7.1"
21
21
  spec.add_runtime_dependency "multi_json"
22
22
 
23
23
  spec.add_development_dependency "bundler", "~> 1.3"
@@ -25,6 +25,6 @@ Gem::Specification.new do |spec|
25
25
  spec.add_development_dependency "rack-test"
26
26
  spec.add_development_dependency "rake"
27
27
  spec.add_development_dependency "rspec", ">= 3.0.0"
28
- spec.add_development_dependency "simplecov"
28
+ spec.add_development_dependency "simplecov", ">= 0.9.2"
29
29
  spec.add_development_dependency "coveralls"
30
30
  end
@@ -60,7 +60,277 @@ describe Lita::Adapters::Slack::API do
60
60
 
61
61
  it "raises a RuntimeError" do
62
62
  expect { subject.im_open(user_id) }.to raise_error(
63
- "Slack API call to im.open failed with status code 422."
63
+ "Slack API call to im.open failed with status code 422: ''. Headers: {}"
64
+ )
65
+ end
66
+ end
67
+ end
68
+
69
+ describe "#channels_info" do
70
+ let(:channel_id) { 'C024BE91L' }
71
+ let(:stubs) do
72
+ Faraday::Adapter::Test::Stubs.new do |stub|
73
+ stub.post('https://slack.com/api/channels.info', token: token, channel: channel_id) do
74
+ [http_status, {}, http_response]
75
+ end
76
+ end
77
+ end
78
+
79
+ describe "with a successful response" do
80
+ let(:http_response) do
81
+ MultiJson.dump({
82
+ ok: true,
83
+ channel: {
84
+ id: 'C024BE91L'
85
+ }
86
+ })
87
+ end
88
+
89
+ it "returns a response with the Channel's ID" do
90
+ response = subject.channels_info(channel_id)
91
+
92
+ expect(response['channel']['id']).to eq(channel_id)
93
+ end
94
+ end
95
+
96
+ describe "with a Slack error" do
97
+ let(:http_response) do
98
+ MultiJson.dump({
99
+ ok: false,
100
+ error: 'channel_not_found'
101
+ })
102
+ end
103
+
104
+ it "raises a RuntimeError" do
105
+ expect { subject.channels_info(channel_id) }.to raise_error(
106
+ "Slack API call to channels.info returned an error: channel_not_found."
107
+ )
108
+ end
109
+ end
110
+
111
+ describe "with an HTTP error" do
112
+ let(:http_status) { 422 }
113
+ let(:http_response) { '' }
114
+
115
+ it "raises a RuntimeError" do
116
+ expect { subject.channels_info(channel_id) }.to raise_error(
117
+ "Slack API call to channels.info failed with status code 422: ''. Headers: {}"
118
+ )
119
+ end
120
+ end
121
+ end
122
+
123
+ describe "#channels_list" do
124
+ let(:channel_id) { 'C024BE91L' }
125
+ let(:stubs) do
126
+ Faraday::Adapter::Test::Stubs.new do |stub|
127
+ stub.post('https://slack.com/api/channels.list', token: token) do
128
+ [http_status, {}, http_response]
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "with a successful response" do
134
+ let(:http_response) do
135
+ MultiJson.dump({
136
+ ok: true,
137
+ channel: [{
138
+ id: 'C024BE91L'
139
+ }]
140
+ })
141
+ end
142
+
143
+ it "returns a response with the Channel's ID" do
144
+ response = subject.channels_list
145
+
146
+ expect(response['channel'].first['id']).to eq(channel_id)
147
+ end
148
+ end
149
+
150
+ describe "with a Slack error" do
151
+ let(:http_response) do
152
+ MultiJson.dump({
153
+ ok: false,
154
+ error: 'invalid_auth'
155
+ })
156
+ end
157
+
158
+ it "raises a RuntimeError" do
159
+ expect { subject.channels_list }.to raise_error(
160
+ "Slack API call to channels.list returned an error: invalid_auth."
161
+ )
162
+ end
163
+ end
164
+
165
+ describe "with an HTTP error" do
166
+ let(:http_status) { 422 }
167
+ let(:http_response) { '' }
168
+
169
+ it "raises a RuntimeError" do
170
+ expect { subject.channels_list }.to raise_error(
171
+ "Slack API call to channels.list failed with status code 422: ''. Headers: {}"
172
+ )
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "#groups_list" do
178
+ let(:channel_id) { 'G024BE91L' }
179
+ let(:stubs) do
180
+ Faraday::Adapter::Test::Stubs.new do |stub|
181
+ stub.post('https://slack.com/api/groups.list', token: token) do
182
+ [http_status, {}, http_response]
183
+ end
184
+ end
185
+ end
186
+
187
+ describe "with a successful response" do
188
+ let(:http_response) do
189
+ MultiJson.dump({
190
+ ok: true,
191
+ groups: [{
192
+ id: 'G024BE91L'
193
+ }]
194
+ })
195
+ end
196
+
197
+ it "returns a response with groupss Channel ID's" do
198
+ response = subject.groups_list
199
+
200
+ expect(response['groups'].first['id']).to eq(channel_id)
201
+ end
202
+ end
203
+
204
+ describe "with a Slack error" do
205
+ let(:http_response) do
206
+ MultiJson.dump({
207
+ ok: false,
208
+ error: 'invalid_auth'
209
+ })
210
+ end
211
+
212
+ it "raises a RuntimeError" do
213
+ expect { subject.groups_list }.to raise_error(
214
+ "Slack API call to groups.list returned an error: invalid_auth."
215
+ )
216
+ end
217
+ end
218
+
219
+ describe "with an HTTP error" do
220
+ let(:http_status) { 422 }
221
+ let(:http_response) { '' }
222
+
223
+ it "raises a RuntimeError" do
224
+ expect { subject.groups_list }.to raise_error(
225
+ "Slack API call to groups.list failed with status code 422: ''. Headers: {}"
226
+ )
227
+ end
228
+ end
229
+ end
230
+
231
+ describe "#mpim_list" do
232
+ let(:channel_id) { 'G024BE91L' }
233
+ let(:stubs) do
234
+ Faraday::Adapter::Test::Stubs.new do |stub|
235
+ stub.post('https://slack.com/api/mpim.list', token: token) do
236
+ [http_status, {}, http_response]
237
+ end
238
+ end
239
+ end
240
+
241
+ describe "with a successful response" do
242
+ let(:http_response) do
243
+ MultiJson.dump({
244
+ ok: true,
245
+ groups: [{
246
+ id: 'G024BE91L'
247
+ }]
248
+ })
249
+ end
250
+
251
+ it "returns a response with MPIMs Channel ID's" do
252
+ response = subject.mpim_list
253
+
254
+ expect(response['groups'].first['id']).to eq(channel_id)
255
+ end
256
+ end
257
+
258
+ describe "with a Slack error" do
259
+ let(:http_response) do
260
+ MultiJson.dump({
261
+ ok: false,
262
+ error: 'invalid_auth'
263
+ })
264
+ end
265
+
266
+ it "raises a RuntimeError" do
267
+ expect { subject.mpim_list }.to raise_error(
268
+ "Slack API call to mpim.list returned an error: invalid_auth."
269
+ )
270
+ end
271
+ end
272
+
273
+ describe "with an HTTP error" do
274
+ let(:http_status) { 422 }
275
+ let(:http_response) { '' }
276
+
277
+ it "raises a RuntimeError" do
278
+ expect { subject.mpim_list }.to raise_error(
279
+ "Slack API call to mpim.list failed with status code 422: ''. Headers: {}"
280
+ )
281
+ end
282
+ end
283
+ end
284
+
285
+ describe "#im_list" do
286
+ let(:channel_id) { 'D024BFF1M' }
287
+ let(:stubs) do
288
+ Faraday::Adapter::Test::Stubs.new do |stub|
289
+ stub.post('https://slack.com/api/im.list', token: token) do
290
+ [http_status, {}, http_response]
291
+ end
292
+ end
293
+ end
294
+
295
+ describe "with a successful response" do
296
+ let(:http_response) do
297
+ MultiJson.dump({
298
+ ok: true,
299
+ ims: [{
300
+ id: 'D024BFF1M'
301
+ }]
302
+ })
303
+ end
304
+
305
+ it "returns a response with IMs Channel ID's" do
306
+ response = subject.im_list
307
+
308
+ expect(response['ims'].first['id']).to eq(channel_id)
309
+ end
310
+ end
311
+
312
+ describe "with a Slack error" do
313
+ let(:http_response) do
314
+ MultiJson.dump({
315
+ ok: false,
316
+ error: 'invalid_auth'
317
+ })
318
+ end
319
+
320
+ it "raises a RuntimeError" do
321
+ expect { subject.im_list }.to raise_error(
322
+ "Slack API call to im.list returned an error: invalid_auth."
323
+ )
324
+ end
325
+ end
326
+
327
+ describe "with an HTTP error" do
328
+ let(:http_status) { 422 }
329
+ let(:http_response) { '' }
330
+
331
+ it "raises a RuntimeError" do
332
+ expect { subject.im_list }.to raise_error(
333
+ "Slack API call to im.list failed with status code 422: ''. Headers: {}"
64
334
  )
65
335
  end
66
336
  end
@@ -173,7 +443,137 @@ describe Lita::Adapters::Slack::API do
173
443
 
174
444
  it "raises a RuntimeError" do
175
445
  expect { subject.send_attachments(room, [attachment]) }.to raise_error(
176
- "Slack API call to chat.postMessage failed with status code 422."
446
+ "Slack API call to chat.postMessage failed with status code 422: ''. Headers: {}"
447
+ )
448
+ end
449
+ end
450
+ end
451
+
452
+ describe "#send_messages" do
453
+ let(:messages) { ["attachment text"] }
454
+ let(:http_response) { MultiJson.dump({ ok: true }) }
455
+ let(:room) { "C1234567890" }
456
+ let(:stubs) do
457
+ Faraday::Adapter::Test::Stubs.new do |stub|
458
+ stub.post(
459
+ "https://slack.com/api/chat.postMessage",
460
+ token: token,
461
+ as_user: true,
462
+ channel: room,
463
+ text: messages.join("\n"),
464
+ ) do
465
+ [http_status, {}, http_response]
466
+ end
467
+ end
468
+ end
469
+
470
+ context "with a simple text attachment" do
471
+ it "sends the attachment" do
472
+ response = subject.send_messages(room, messages)
473
+
474
+ expect(response['ok']).to be(true)
475
+ end
476
+ end
477
+
478
+ context "with configuration" do
479
+ before do
480
+ allow(config).to receive(:link_names).and_return(true)
481
+ end
482
+
483
+ def stubs(postMessage_options = {})
484
+ Faraday::Adapter::Test::Stubs.new do |stub|
485
+ stub.post(
486
+ "https://slack.com/api/chat.postMessage",
487
+ token: token,
488
+ link_names: 1,
489
+ as_user: true,
490
+ channel: room,
491
+ text: messages.join("\n"),
492
+ ) do
493
+ [http_status, {}, http_response]
494
+ end
495
+ end
496
+ end
497
+
498
+ it "sends the message with configuration" do
499
+ response = subject.send_messages(room, messages)
500
+
501
+ expect(response['ok']).to be(true)
502
+ end
503
+ end
504
+
505
+ context "with a different fallback message" do
506
+ let(:attachment) do
507
+ Lita::Adapters::Slack::Attachment.new(attachment_text, fallback: fallback_text)
508
+ end
509
+ let(:fallback_text) { "fallback text" }
510
+
511
+ it "sends the attachment" do
512
+ response = subject.send_messages(room, messages)
513
+
514
+ expect(response['ok']).to be(true)
515
+ end
516
+ end
517
+
518
+ context "with all the valid options" do
519
+ let(:attachment) do
520
+ Lita::Adapters::Slack::Attachment.new(attachment_text, common_hash_data)
521
+ end
522
+ let(:attachment_hash) do
523
+ common_hash_data.merge(fallback: attachment_text, text: attachment_text)
524
+ end
525
+ let(:common_hash_data) do
526
+ {
527
+ author_icon: "http://example.com/author.jpg",
528
+ author_link: "http://example.com/author",
529
+ author_name: "author name",
530
+ color: "#36a64f",
531
+ fields: [{
532
+ title: "priority",
533
+ value: "high",
534
+ short: true,
535
+ }, {
536
+ title: "super long field title",
537
+ value: "super long field value",
538
+ short: false,
539
+ }],
540
+ image_url: "http://example.com/image.jpg",
541
+ pretext: "pretext",
542
+ thumb_url: "http://example.com/thumb.jpg",
543
+ title: "title",
544
+ title_link: "http://example.com/title",
545
+ }
546
+ end
547
+
548
+ it "sends the attachment" do
549
+ response = subject.send_messages(room, messages)
550
+
551
+ expect(response['ok']).to be(true)
552
+ end
553
+ end
554
+
555
+ context "with a Slack error" do
556
+ let(:http_response) do
557
+ MultiJson.dump({
558
+ ok: false,
559
+ error: 'invalid_auth'
560
+ })
561
+ end
562
+
563
+ it "raises a RuntimeError" do
564
+ expect { subject.send_messages(room, messages) }.to raise_error(
565
+ "Slack API call to chat.postMessage returned an error: invalid_auth."
566
+ )
567
+ end
568
+ end
569
+
570
+ context "with an HTTP error" do
571
+ let(:http_status) { 422 }
572
+ let(:http_response) { '' }
573
+
574
+ it "raises a RuntimeError" do
575
+ expect { subject.send_messages(room, messages) }.to raise_error(
576
+ "Slack API call to chat.postMessage failed with status code 422: ''. Headers: {}"
177
577
  )
178
578
  end
179
579
  end
@@ -231,7 +631,7 @@ describe Lita::Adapters::Slack::API do
231
631
 
232
632
  it "raises a RuntimeError" do
233
633
  expect { subject.set_topic(channel, topic) }.to raise_error(
234
- "Slack API call to channels.setTopic failed with status code 422."
634
+ "Slack API call to channels.setTopic failed with status code 422: ''. Headers: {}"
235
635
  )
236
636
  end
237
637
  end
@@ -30,10 +30,11 @@ describe Lita::Adapters::Slack::MessageHandler, lita: true do
30
30
  "type" => "message",
31
31
  "channel" => "C2147483705",
32
32
  "user" => "U023BECGF",
33
- "text" => "Hello"
33
+ "text" => "Hello",
34
+ "ts" => "1234.5678"
34
35
  }
35
36
  end
36
- let(:message) { instance_double('Lita::Message', command!: false) }
37
+ let(:message) { instance_double('Lita::Message', command!: false, extensions: {}) }
37
38
  let(:source) { instance_double('Lita::Source', private_message?: false) }
38
39
  let(:user) { instance_double('Lita::User', id: 'U023BECGF') }
39
40
 
@@ -49,8 +50,12 @@ describe Lita::Adapters::Slack::MessageHandler, lita: true do
49
50
 
50
51
  it "dispatches the message to Lita" do
51
52
  expect(robot).to receive(:receive).with(message)
53
+ subject.handle
54
+ end
52
55
 
56
+ it "saves the timestamp in extensions" do
53
57
  subject.handle
58
+ expect(message.extensions[:slack][:timestamp]).to eq("1234.5678")
54
59
  end
55
60
 
56
61
  context "when the message is a direct message" do
@@ -468,19 +473,27 @@ describe Lita::Adapters::Slack::MessageHandler, lita: true do
468
473
  end
469
474
  end
470
475
 
471
- context "with a message from the robot itself" do
476
+ context "with a message from slackbot" do
472
477
  let(:data) do
473
478
  {
474
479
  "type" => "message",
475
- "subtype" => "bot_message"
480
+ "user" => "USLACKBOT"
476
481
  }
477
482
  end
478
- let(:user) { instance_double('Lita::User', id: 12345) }
479
483
 
480
- before do
481
- # TODO: This probably shouldn't be tested with stubs.
482
- allow(Lita::User).to receive(:find_by_id).and_return(user)
483
- allow(Lita::User).to receive(:find_by_name).and_return(user)
484
+ it "does not dispatch the message to Lita" do
485
+ expect(robot).not_to receive(:receive)
486
+
487
+ subject.handle
488
+ end
489
+ end
490
+
491
+ context "with a message from the robot itself" do
492
+ let(:data) do
493
+ {
494
+ "type" => "message",
495
+ "user" => robot_id
496
+ }
484
497
  end
485
498
 
486
499
  it "does not dispatch the message to Lita" do
@@ -578,6 +591,54 @@ describe Lita::Adapters::Slack::MessageHandler, lita: true do
578
591
  end
579
592
  end
580
593
 
594
+ context "with a reaction_added message" do
595
+ let(:data) do
596
+ {
597
+ "type" => "reaction_added",
598
+ "user" => "U023BECGF",
599
+ "item"=>{"type"=>"message", "channel"=>"C2147483705", "ts"=>"1234567.000008"},
600
+ "reaction"=>"+1",
601
+ "event_ts"=>"1234.5678"
602
+ }
603
+ end
604
+ let(:user) { instance_double('Lita::User', id: 'U023BECGF') }
605
+ let(:payload) { {user: user, name: data["reaction"], item: data["item"], event_ts: data["event_ts"]} }
606
+
607
+ before do
608
+ allow(Lita::User).to receive(:find_by_id).and_return(user)
609
+ allow(robot).to receive(:trigger).with(:slack_reaction_added, payload)
610
+ end
611
+
612
+ it "triggers the slack_reaction_added event with the correct payload" do
613
+ expect(robot).to receive(:trigger).with(:slack_reaction_added, payload)
614
+ subject.handle
615
+ end
616
+ end
617
+
618
+ context "with a reaction_removed message" do
619
+ let(:data) do
620
+ {
621
+ "type" => "reaction_removed",
622
+ "user" => "U023BECGF",
623
+ "item"=>{"type"=>"message", "channel"=>"C2147483705", "ts"=>"1234567.000008"},
624
+ "reaction"=>"+1",
625
+ "event_ts"=>"1234.5678"
626
+ }
627
+ end
628
+ let(:user) { instance_double('Lita::User', id: 'U023BECGF') }
629
+ let(:payload) { {user: user, name: data["reaction"], item: data["item"], event_ts: data["event_ts"]} }
630
+
631
+ before do
632
+ allow(Lita::User).to receive(:find_by_id).and_return(user)
633
+ allow(robot).to receive(:trigger).with(:slack_reaction_removed, payload)
634
+ end
635
+
636
+ it "triggers the slack_reaction_removed event with the correct payload" do
637
+ expect(robot).to receive(:trigger).with(:slack_reaction_removed, payload)
638
+ subject.handle
639
+ end
640
+ end
641
+
581
642
  context "with an unknown message" do
582
643
  let(:data) { { "type" => "???" } }
583
644
 
@@ -27,6 +27,12 @@ describe Lita::Adapters::Slack, lita: true do
27
27
  end
28
28
  end
29
29
 
30
+ describe "#mention_format" do
31
+ it "returns the name prefixed with an @" do
32
+ expect(subject.mention_format("carl")).to eq("@carl")
33
+ end
34
+ end
35
+
30
36
  describe "#run" do
31
37
  it "starts the RTM connection" do
32
38
  expect(rtm_connection).to receive(:run)
@@ -42,6 +48,74 @@ describe Lita::Adapters::Slack, lita: true do
42
48
  end
43
49
  end
44
50
 
51
+ describe "#roster" do
52
+ describe "via the Web API, retrieving the roster for a channel" do
53
+ let(:room_source) { Lita::Source.new(room: 'C024BE91L') }
54
+ let(:response) do
55
+ {
56
+ ok: true,
57
+ channel: {
58
+ members: ['C024BE91L']
59
+ }
60
+ }
61
+ end
62
+ let(:api) { instance_double('Lita::Adapters::Slack::API') }
63
+
64
+ before do
65
+ allow(Lita::Adapters::Slack::API).to receive(:new).with(subject.config).and_return(api)
66
+ end
67
+
68
+ it "returns UID(s)" do
69
+ expect(subject).to receive(:channel_roster).with(room_source.room_object.id, api)
70
+
71
+ subject.roster(room_source.room_object)
72
+ end
73
+ end
74
+
75
+ describe "via the Web API, retrieving the roster for a group/mpim channel" do
76
+ let(:room_source) { Lita::Source.new(room: 'G024BE91L') }
77
+ let(:response) do
78
+ {
79
+ ok: true,
80
+ groups: [{ id: 'G024BE91L' }]
81
+ }
82
+ end
83
+ let(:api) { instance_double('Lita::Adapters::Slack::API') }
84
+
85
+ before do
86
+ allow(Lita::Adapters::Slack::API).to receive(:new).with(subject.config).and_return(api)
87
+ end
88
+
89
+ it "returns UID(s)" do
90
+ expect(subject).to receive(:group_roster).with(room_source.room_object.id, api).and_return(%q{})
91
+ expect(subject).to receive(:mpim_roster).with(room_source.room_object.id, api).and_return(%q{G024BE91L})
92
+
93
+ subject.roster(room_source.room_object)
94
+ end
95
+ end
96
+
97
+ describe "via the Web API, retrieving the roster for an im channel" do
98
+ let(:room_source) { Lita::Source.new(room: 'D024BFF1M') }
99
+ let(:response) do
100
+ {
101
+ ok: true,
102
+ ims: [{ id: 'D024BFF1M' }]
103
+ }
104
+ end
105
+ let(:api) { instance_double('Lita::Adapters::Slack::API') }
106
+
107
+ before do
108
+ allow(Lita::Adapters::Slack::API).to receive(:new).with(subject.config).and_return(api)
109
+ end
110
+
111
+ it "returns UID" do
112
+ expect(subject).to receive(:im_roster).with(room_source.room_object.id, api)
113
+
114
+ subject.roster(room_source.room_object)
115
+ end
116
+ end
117
+ end
118
+
45
119
  describe "#send_messages" do
46
120
  let(:room_source) { Lita::Source.new(room: 'C024BE91L') }
47
121
  let(:user) { Lita::User.new('U023BECGF') }
@@ -50,32 +124,19 @@ describe Lita::Adapters::Slack, lita: true do
50
124
  Lita::Source.new(room: 'C024BE91L', user: user, private_message: true)
51
125
  end
52
126
 
53
- it "sends messages to rooms" do
54
- expect(rtm_connection).to receive(:send_messages).with(room_source.room, ['foo'])
55
-
56
- subject.run
57
-
58
- subject.send_messages(room_source, ['foo'])
59
- end
60
-
61
- it "sends messages to users" do
62
- allow(rtm_connection).to receive(:im_for).with(user.id).and_return('D024BFF1M')
63
-
64
- expect(rtm_connection).to receive(:send_messages).with('D024BFF1M', ['foo'])
127
+ describe "via the Web API" do
128
+ let(:api) { instance_double('Lita::Adapters::Slack::API') }
65
129
 
66
- subject.run
130
+ before do
131
+ allow(Lita::Adapters::Slack::API).to receive(:new).with(subject.config).and_return(api)
132
+ end
67
133
 
68
- subject.send_messages(user_source, ['foo'])
69
- end
70
-
71
- it "sends messages to users when the source is marked as a private message" do
72
- allow(rtm_connection).to receive(:im_for).with(user.id).and_return('D024BFF1M')
73
-
74
- expect(rtm_connection).to receive(:send_messages).with('D024BFF1M', ['foo'])
75
-
76
- subject.run
134
+ it "does not send via the RTM api" do
135
+ expect(rtm_connection).to_not receive(:send_messages)
136
+ expect(api).to receive(:send_messages).with(room_source.room, ['foo'])
77
137
 
78
- subject.send_messages(private_message_source, ['foo'])
138
+ subject.send_messages(room_source, ['foo'])
139
+ end
79
140
  end
80
141
  end
81
142
 
@@ -1,6 +1,6 @@
1
1
  require "simplecov"
2
2
  require "coveralls"
3
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
3
+ SimpleCov.formatters = [
4
4
  SimpleCov::Formatter::HTMLFormatter,
5
5
  Coveralls::SimpleCov::Formatter
6
6
  ]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lita-slack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken J.
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-17 00:00:00.000000000 Z
12
+ date: 2016-09-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - ">="
61
61
  - !ruby/object:Gem::Version
62
- version: 4.6.0
62
+ version: 4.7.1
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - ">="
68
68
  - !ruby/object:Gem::Version
69
- version: 4.6.0
69
+ version: 4.7.1
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: multi_json
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -157,14 +157,14 @@ dependencies:
157
157
  requirements:
158
158
  - - ">="
159
159
  - !ruby/object:Gem::Version
160
- version: '0'
160
+ version: 0.9.2
161
161
  type: :development
162
162
  prerelease: false
163
163
  version_requirements: !ruby/object:Gem::Requirement
164
164
  requirements:
165
165
  - - ">="
166
166
  - !ruby/object:Gem::Version
167
- version: '0'
167
+ version: 0.9.2
168
168
  - !ruby/object:Gem::Dependency
169
169
  name: coveralls
170
170
  requirement: !ruby/object:Gem::Requirement
@@ -244,7 +244,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
244
244
  version: '0'
245
245
  requirements: []
246
246
  rubyforge_project:
247
- rubygems_version: 2.4.5.1
247
+ rubygems_version: 2.5.1
248
248
  signing_key:
249
249
  specification_version: 4
250
250
  summary: Lita adapter for Slack.
@@ -262,4 +262,3 @@ test_files:
262
262
  - spec/lita/adapters/slack/user_creator_spec.rb
263
263
  - spec/lita/adapters/slack_spec.rb
264
264
  - spec/spec_helper.rb
265
- has_rdoc: