slacks 0.5.0.pre → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []