slacks 0.5.0.pre → 0.6.3

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
- SHA1:
3
- metadata.gz: 3fa3ec9c05fdbe56d801c27cb5acbe5d879c07f7
4
- data.tar.gz: 348dc602d884498580f7d72f9fba829333ab087e
2
+ SHA256:
3
+ metadata.gz: e3f9538fbc719ad833d0027f5d2edb32e5c7ee3d991cb3596bb5217e58f02ba6
4
+ data.tar.gz: 418e0f974c5728e735c560e020429041f0c0af437201721d9d98a423b5db790a
5
5
  SHA512:
6
- metadata.gz: aa37ba98e00e52d176b5481cca8793d710cc2f068242e448e1b20052d3da3654503008fd6efecf2a9ec21b230f8a4d4c13fb3fd404a79cb981262e35c5b581fd
7
- data.tar.gz: 978735f54f957ee598631502c914f544aa3666f5a87ebe5f85da1c8bdea80e00b7c1331e49e2415f1749d03afab0e023c715b7c7177db5c43a442286ce5b66d1
6
+ metadata.gz: 6d47f55bf02c06928f9a3b7da4c1dd8968455550b9096db030945fb804cdaf9912f8b491e2782df45838b3a9ac6b82938a1edf7509e379ae8d7a7742150217f8
7
+ data.tar.gz: 46bc171a39e21778e9aeb1a2af25e081cc607178935cf4a8c130a5e19b777645e38199920b1b866d550f7d4ade5a316f6424dc0003a272eb2369e24c2cb50e87
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ ### v0.6.2
2
+ - Fixed a regression where groups, DMs, and private channels were not fetched
3
+
4
+ ### v0.6.1
5
+ - Fixed a typo with creating a DM associated with a user
6
+
7
+ ### v0.6.0
8
+ - Switched to using the Slack Conversations API
@@ -12,17 +12,12 @@ module Slacks
12
12
  end
13
13
 
14
14
  def reply(*messages)
15
- messages.flatten!
16
15
  return unless messages.any?
17
16
 
18
- first_message = messages.shift
19
- message_options = {}
20
- message_options = messages.shift if messages.length == 1 && messages[0].is_a?(Hash)
21
- slack.send_message(first_message, message_options.merge(channel: id))
22
-
23
- messages.each do |message|
24
- sleep message.length / slack.typing_speed
25
- slack.send_message(message, channel: id)
17
+ if messages.first.is_a?(Array)
18
+ reply_many(messages[0])
19
+ else
20
+ reply_one(*messages)
26
21
  end
27
22
  end
28
23
  alias :say :reply
@@ -61,6 +56,19 @@ module Slacks
61
56
  "##{name}"
62
57
  end
63
58
 
59
+ protected
60
+
61
+ def reply_one(message, options={})
62
+ slack.send_message(message, options.merge(channel: id))
63
+ end
64
+
65
+ def reply_many(messages)
66
+ messages.each_with_index.map do |message, i|
67
+ sleep message.length / slack.typing_speed if i > 0
68
+ slack.send_message(message, channel: id)
69
+ end
70
+ end
71
+
64
72
  private
65
73
  attr_reader :slack
66
74
  end
@@ -24,10 +24,8 @@ module Slacks
24
24
  @user_ids_dm_ids = {}
25
25
  @users_by_id = {}
26
26
  @user_id_by_name = {}
27
- @groups_by_id = {}
28
- @group_id_by_name = {}
29
- @channels_by_id = {}
30
- @channel_id_by_name = {}
27
+ @conversations_by_id = {}
28
+ @conversation_ids_by_name = {}
31
29
  end
32
30
 
33
31
 
@@ -42,7 +40,7 @@ module Slacks
42
40
  link_names: 1} # find and link channel names and user names
43
41
  params.merge!(attachments: MultiJson.dump(attachments)) if attachments.any?
44
42
  params.merge!(options.select { |key, _| SEND_MESSAGE_PARAMS.member?(key) })
45
- api("chat.postMessage", params)
43
+ api("chat.postMessage", **params)
46
44
  end
47
45
  alias :say :send_message
48
46
 
@@ -50,7 +48,7 @@ module Slacks
50
48
  params = {
51
49
  channel: to_channel_id(channel),
52
50
  timestamp: ts }
53
- api("reactions.get", params)
51
+ api("reactions.get", **params)
54
52
  end
55
53
 
56
54
  def update_message(ts, message, options={})
@@ -65,26 +63,34 @@ module Slacks
65
63
  params.merge!(attachments: MultiJson.dump(attachments)) if attachments.any?
66
64
  params.merge!(options.select { |key, _| [:username, :as_user, :parse, :link_names,
67
65
  :unfurl_links, :unfurl_media, :icon_url, :icon_emoji].member?(key) })
68
- api("chat.update", params)
66
+ api("chat.update", **params)
69
67
  end
70
68
 
71
69
  def add_reaction(emojis, message)
72
70
  Array(emojis).each do |emoji|
73
- api("reactions.add", {
71
+ api("reactions.add",
74
72
  name: emoji.gsub(/^:|:$/, ""),
75
73
  channel: message.channel.id,
76
- timestamp: message.timestamp })
74
+ timestamp: message.timestamp)
77
75
  end
78
76
  end
79
77
 
78
+
79
+
80
80
  def typing_on(channel)
81
+ raise NotListeningError unless listening?
81
82
  websocket.write MultiJson.dump(type: "typing", channel: to_channel_id(channel))
82
83
  end
83
84
 
84
85
  def ping
86
+ raise NotListeningError unless listening?
85
87
  websocket.ping
86
88
  end
87
89
 
90
+ def listening?
91
+ !websocket.nil?
92
+ end
93
+
88
94
 
89
95
 
90
96
  def listen!
@@ -108,21 +114,16 @@ module Slacks
108
114
  # one, we'll skill it.
109
115
  next
110
116
 
111
- when EVENT_GROUP_JOINED
112
- group = data["channel"]
113
- @groups_by_id[group["id"]] = group
114
- @group_id_by_name[group["name"]] = group["id"]
117
+ when EVENT_GROUP_JOINED, EVENT_CHANNEL_CREATED
118
+ conversation = data["channel"]
119
+ @conversations_by_id[conversation["id"]] = conversation
120
+ @conversation_ids_by_name[conversation["name"]] = conversation["id"]
115
121
 
116
122
  when EVENT_USER_JOINED
117
123
  user = data["user"]
118
124
  @users_by_id[user["id"]] = user
119
125
  @user_id_by_name[user["name"]] = user["id"]
120
126
 
121
- when EVENT_CHANNEL_CREATED
122
- channel = data["channel"]
123
- @channels_by_id[channel["id"]] = channel
124
- @channel_id_by_name[channel["name"]] = channel["id"]
125
-
126
127
  when EVENT_MESSAGE
127
128
  # Don't respond to things that this bot said
128
129
  next if data["user"] == bot.id
@@ -149,17 +150,17 @@ module Slacks
149
150
 
150
151
 
151
152
  def channels
152
- channels = user_id_by_name.keys + group_id_by_name.keys + channel_id_by_name.keys
153
+ channels = user_id_by_name.keys + conversation_ids_by_name.keys
153
154
  if channels.empty?
154
- fetch_channels!
155
- fetch_groups!
155
+ fetch_conversations!
156
156
  fetch_users!
157
157
  end
158
158
  channels
159
159
  end
160
160
 
161
161
  def can_see?(channel)
162
- to_channel_id(channel).present?
162
+ channel_id = to_channel_id(channel)
163
+ channel_id && !channel_id.empty?
163
164
  rescue ArgumentError
164
165
  false
165
166
  end
@@ -175,21 +176,28 @@ module Slacks
175
176
  "id" => id,
176
177
  "is_im" => true,
177
178
  "name" => user.username }
178
- when /^G/
179
- Slacks::Channel.new(self, groups_by_id.fetch(id) do
180
- raise ArgumentError, "Unable to find a group with the ID #{id.inspect}"
181
- end)
182
179
  else
183
- Slacks::Channel.new(self, channels_by_id.fetch(id) do
184
- raise ArgumentError, "Unable to find a channel with the ID #{id.inspect}"
185
- end)
180
+ Slacks::Channel.new(self, find_conversation(id))
181
+ end
182
+ end
183
+
184
+ def find_conversation(id)
185
+ conversations_by_id.fetch(id) do
186
+ fetch_conversations!
187
+ conversations_by_id.fetch(id) do
188
+ raise ArgumentError, "Unable to find a conversation with the ID #{id.inspect}"
189
+ end
186
190
  end
187
191
  end
188
192
 
189
193
  def find_user(id)
190
- Slacks::User.new(self, users_by_id.fetch(id) do
191
- raise ArgumentError, "Unable to find a user with the ID #{id.inspect}"
192
- end)
194
+ user = users_by_id.fetch(id) do
195
+ fetch_users!
196
+ users_by_id.fetch(id) do
197
+ raise ArgumentError, "Unable to find a user with the ID #{id.inspect}"
198
+ end
199
+ end
200
+ Slacks::User.new(self, user)
193
201
  end
194
202
 
195
203
  def find_user_by_nickname(nickname)
@@ -200,7 +208,8 @@ module Slacks
200
208
 
201
209
  def user_exists?(username)
202
210
  return false if username.nil?
203
- to_user_id(username).present?
211
+ user_id = to_user_id(username)
212
+ user_id && !user_id.empty?
204
213
  rescue ArgumentError
205
214
  false
206
215
  end
@@ -216,10 +225,8 @@ module Slacks
216
225
  attr_reader :user_ids_dm_ids,
217
226
  :users_by_id,
218
227
  :user_id_by_name,
219
- :groups_by_id,
220
- :group_id_by_name,
221
- :channels_by_id,
222
- :channel_id_by_name,
228
+ :conversations_by_id,
229
+ :conversation_ids_by_name,
223
230
  :websocket_url,
224
231
  :websocket
225
232
 
@@ -230,14 +237,8 @@ module Slacks
230
237
  @bot = BotUser.new(response.fetch("self"))
231
238
  @team = Team.new(response.fetch("team"))
232
239
 
233
- @channels_by_id = Hash[response.fetch("channels").map { |attrs| [attrs.fetch("id"), attrs] }]
234
- @channel_id_by_name = Hash[response.fetch("channels").map { |attrs| ["##{attrs.fetch("name")}", attrs.fetch("id")] }]
235
-
236
- @users_by_id = Hash[response.fetch("users").map { |attrs| [attrs.fetch("id"), attrs] }]
237
- @user_id_by_name = Hash[response.fetch("users").map { |attrs| ["@#{attrs.fetch("name")}", attrs.fetch("id")] }]
238
-
239
- @groups_by_id = Hash[response.fetch("groups").map { |attrs| [attrs.fetch("id"), attrs] }]
240
- @group_id_by_name = Hash[response.fetch("groups").map { |attrs| [attrs.fetch("name"), attrs.fetch("id")] }]
240
+ @conversations_by_id = Hash[response.fetch("channels").map { |attrs| [ attrs.fetch("id"), attrs ] }]
241
+ @conversation_ids_by_name = Hash[response.fetch("channels").map { |attrs| [ attrs["name"], attrs["id"] ] }]
241
242
  end
242
243
 
243
244
 
@@ -246,13 +247,9 @@ module Slacks
246
247
  return name.id if name.is_a?(Slacks::Channel)
247
248
  return name if name =~ /^[DGC]/ # this already looks like a channel id
248
249
  return get_dm_for_username(name) if name.start_with?("@")
249
- return to_group_id(name) unless name.start_with?("#")
250
250
 
251
- channel_id_by_name[name] || fetch_channels![name] || missing_channel!(name)
252
- end
253
-
254
- def to_group_id(name)
255
- group_id_by_name[name] || fetch_groups![name] || missing_group!(name)
251
+ name = name.gsub(/^#/, "") # Leading hashes are no longer a thing in the conversations API
252
+ conversation_ids_by_name[name] || fetch_conversations![name] || missing_conversation!(name)
256
253
  end
257
254
 
258
255
  def to_user_id(name)
@@ -265,39 +262,29 @@ module Slacks
265
262
 
266
263
  def get_dm_for_user_id(user_id)
267
264
  user_ids_dm_ids[user_id] ||= begin
268
- response = api("im.open", user: user_id)
265
+ response = api("conversations.open", users: user_id)
269
266
  response["channel"]["id"]
270
267
  end
271
268
  end
272
269
 
273
270
 
274
-
275
- def fetch_channels!
276
- response = api("channels.list")
277
- @channels_by_id = response["channels"].index_by { |attrs| attrs["id"] }
278
- @channel_id_by_name = Hash[response["channels"].map { |attrs| ["##{attrs["name"]}", attrs["id"]] }]
279
- end
280
-
281
- def fetch_groups!
282
- response = api("groups.list")
283
- @groups_by_id = response["groups"].index_by { |attrs| attrs["id"] }
284
- @group_id_by_name = Hash[response["groups"].map { |attrs| [attrs["name"], attrs["id"]] }]
271
+ def fetch_conversations!
272
+ conversations, ims = api("conversations.list", types: "public_channel,private_channel,mpim,im")["channels"].partition { |attrs| attrs["is_channel"] || attrs["is_group"] }
273
+ user_ids_dm_ids.merge! Hash[ims.map { |attrs| attrs.values_at("user", "id") }]
274
+ @conversations_by_id = Hash[conversations.map { |attrs| [ attrs.fetch("id"), attrs ] }]
275
+ @conversation_ids_by_name = Hash[conversations.map { |attrs| [ attrs["name"], attrs["id"] ] }]
285
276
  end
286
277
 
287
278
  def fetch_users!
288
279
  response = api("users.list")
289
- @users_by_id = response["members"].index_by { |attrs| attrs["id"] }
280
+ @users_by_id = response["members"].each_with_object({}) { |attrs, hash| hash[attrs["id"]] = attrs }
290
281
  @user_id_by_name = Hash[response["members"].map { |attrs| ["@#{attrs["name"]}", attrs["id"]] }]
291
282
  end
292
283
 
293
284
 
294
285
 
295
- def missing_channel!(name)
296
- raise ArgumentError, "Couldn't find a channel named #{name}"
297
- end
298
-
299
- def missing_group!(name)
300
- raise ArgumentError, "Couldn't find a private group named #{name}"
286
+ def missing_conversation!(name)
287
+ raise ArgumentError, "Couldn't find a conversation named #{name}"
301
288
  end
302
289
 
303
290
  def missing_user!(name)
@@ -309,8 +296,7 @@ module Slacks
309
296
  def get_user_id_for_dm(dm)
310
297
  user_id = user_ids_dm_ids.key(dm)
311
298
  unless user_id
312
- response = api("im.list")
313
- user_ids_dm_ids.merge! Hash[response["ims"].map { |attrs| attrs.values_at("user", "id") }]
299
+ fetch_conversations!
314
300
  user_id = user_ids_dm_ids.key(dm)
315
301
  end
316
302
  raise ArgumentError, "Unable to find a user for the direct message ID #{dm.inspect}" unless user_id
@@ -319,12 +305,33 @@ module Slacks
319
305
 
320
306
 
321
307
 
322
- def api(command, options={})
323
- response = http.post(command, options.merge(token: token))
308
+ def api(command, page_limit: MAX_PAGES, **params)
309
+ params_with_token = params.merge(token: token)
310
+ response = api_post command, params_with_token
311
+ fetched_pages = 1
312
+ cursor = response.dig("response_metadata", "next_cursor")
313
+ while cursor && !cursor.empty? && fetched_pages < page_limit do
314
+ api_post(command, params_with_token.merge(cursor: cursor)).each do |key, value|
315
+ if value.is_a?(Array)
316
+ response[key].concat value
317
+ elsif value.is_a?(Hash)
318
+ response[key].merge! value
319
+ else
320
+ response[key] = value
321
+ end
322
+ end
323
+ fetched_pages += 1
324
+ cursor = response.dig("response_metadata", "next_cursor")
325
+ end
326
+ response
327
+ end
328
+
329
+ def api_post(command, params)
330
+ response = http.post(command, params)
324
331
  response = MultiJson.load(response.body)
325
332
  unless response["ok"]
326
333
  response["error"].split(/,\s*/).each do |error_code|
327
- raise ::Slacks::Response.fetch(error_code).new(response)
334
+ raise ::Slacks::Response.fetch(error_code).new(command, params, response)
328
335
  end
329
336
  end
330
337
  response
@@ -356,5 +363,7 @@ module Slacks
356
363
  reply_broadcast
357
364
  }.freeze
358
365
 
366
+ MAX_PAGES = 9001
367
+
359
368
  end
360
369
  end
data/lib/slacks/errors.rb CHANGED
@@ -3,19 +3,29 @@ require "slacks/core_ext/exception"
3
3
  module Slacks
4
4
  module Response
5
5
  class Error < RuntimeError
6
- attr_reader :response
6
+ attr_reader :command, :params, :response
7
7
 
8
- def initialize(response, message)
8
+ def initialize(command, params, response, message)
9
9
  super message
10
+ @command = command
11
+ @params = params
10
12
  @response = response
13
+ additional_information[:command] = command
14
+ additional_information[:params] = params
11
15
  additional_information[:response] = response
12
16
  end
13
17
  end
14
18
 
19
+ class UnspecifiedError < ::Slacks::Response::Error
20
+ def initialize(command, params, response)
21
+ super command, params, response, "Request failed with #{response["error"].inspect}"
22
+ end
23
+ end
24
+
15
25
  @_errors = {}
16
26
 
17
27
  def self.fetch(error_code)
18
- @_errors.fetch(error_code, ::Slacks::Response::Error)
28
+ @_errors.fetch(error_code, ::Slacks::Response::UnspecifiedError)
19
29
  end
20
30
 
21
31
  {
@@ -25,6 +35,7 @@ module Slacks
25
35
  "cant_update_message" => "Authenticated user does not have permission to update this message.",
26
36
  "channel_not_found" => "Value passed for channel was invalid.",
27
37
  "edit_window_closed" => "The message cannot be edited due to the team message edit settings",
38
+ "fatal_error" => "",
28
39
  "file_comment_not_found" => "File comment specified by file_comment does not exist.",
29
40
  "file_not_found" => "File specified by file does not exist.",
30
41
  "invalid_arg_name" => "The method was passed an argument whose name falls outside the bounds of common decency. This includes very long names and names with non-alphanumeric characters other than _. If you get this error, it is typically an indication that you have made a very malformed API call.",
@@ -49,15 +60,15 @@ module Slacks
49
60
  "too_many_emoji" => "The limit for distinct reactions (i.e emoji) on the item has been reached.",
50
61
  "too_many_reactions" => "The limit for reactions a person may add to the item has been reached."
51
62
  }.each do |error_code, message|
52
- class_name = error_code.classify
63
+ class_name = error_code.gsub(/(?:^|_)([a-z]+)/) { $1.capitalize }
53
64
  class_name = {
54
65
  "MsgTooLong" => "MessageTooLong"
55
66
  }.fetch(class_name, class_name)
56
67
 
57
68
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
58
69
  class #{class_name} < ::Slacks::Response::Error
59
- def initialize(response)
60
- super response, "#{message}"
70
+ def initialize(command, params, response)
71
+ super command, params, response, "#{message}"
61
72
  end
62
73
  end
63
74
 
@@ -84,4 +95,10 @@ module Slacks
84
95
  super "Unable to connect to Slack; a token has no"
85
96
  end
86
97
  end
98
+
99
+ class NotListeningError < ArgumentError
100
+ def initialize
101
+ super "Not connected to the RTM API; call `listen!` first"
102
+ end
103
+ end
87
104
  end
@@ -1,3 +1,3 @@
1
1
  module Slacks
2
- VERSION = "0.5.0.pre"
2
+ VERSION = "0.6.3"
3
3
  end
data/slacks.gemspec CHANGED
@@ -25,8 +25,8 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency "faraday-raise-errors"
26
26
  spec.add_dependency "concurrent-ruby"
27
27
 
28
- spec.add_development_dependency "bundler", "~> 1.10"
29
- spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "bundler"
29
+ spec.add_development_dependency "rake"
30
30
  spec.add_development_dependency "minitest", "~> 5.0"
31
31
  spec.add_development_dependency "pry"
32
32
  spec.add_development_dependency "minitest-reporters"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slacks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0.pre
4
+ version: 0.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bob Lail
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-28 00:00:00.000000000 Z
11
+ date: 2021-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket-driver
@@ -84,30 +84,30 @@ dependencies:
84
84
  name: bundler
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '1.10'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '1.10'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rake
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '10.0'
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '10.0'
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: minitest
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -164,7 +164,7 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
- description:
167
+ description:
168
168
  email:
169
169
  - bob.lailfamily@gmail.com
170
170
  executables: []
@@ -173,6 +173,7 @@ extra_rdoc_files: []
173
173
  files:
174
174
  - ".gitignore"
175
175
  - ".travis.yml"
176
+ - CHANGELOG.md
176
177
  - Gemfile
177
178
  - LICENSE.txt
178
179
  - README.md
@@ -196,7 +197,7 @@ homepage: https://github.com/houston/slacks
196
197
  licenses:
197
198
  - MIT
198
199
  metadata: {}
199
- post_install_message:
200
+ post_install_message:
200
201
  rdoc_options: []
201
202
  require_paths:
202
203
  - lib
@@ -207,13 +208,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
207
208
  version: '0'
208
209
  required_rubygems_version: !ruby/object:Gem::Requirement
209
210
  requirements:
210
- - - ">"
211
+ - - ">="
211
212
  - !ruby/object:Gem::Version
212
- version: 1.3.1
213
+ version: '0'
213
214
  requirements: []
214
- rubyforge_project:
215
- rubygems_version: 2.2.2
216
- signing_key:
215
+ rubygems_version: 3.1.2
216
+ signing_key:
217
217
  specification_version: 4
218
218
  summary: A library for communicating via Slack
219
219
  test_files: []