slack-ruby-client 0.14.1 → 0.14.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop_todo.yml +15 -4
  4. data/CHANGELOG.md +7 -0
  5. data/README.md +8 -1
  6. data/Rakefile +1 -1
  7. data/bin/commands.rb +1 -0
  8. data/bin/commands/api.rb +2 -2
  9. data/bin/commands/apps.rb +2 -2
  10. data/bin/commands/apps_permissions.rb +4 -4
  11. data/bin/commands/apps_permissions_resources.rb +2 -2
  12. data/bin/commands/apps_permissions_scopes.rb +2 -2
  13. data/bin/commands/apps_permissions_users.rb +4 -4
  14. data/bin/commands/auth.rb +4 -4
  15. data/bin/commands/bots.rb +2 -2
  16. data/bin/commands/channels.rb +32 -31
  17. data/bin/commands/chat.rb +48 -17
  18. data/bin/commands/chat_scheduledMessages.rb +17 -0
  19. data/bin/commands/conversations.rb +35 -35
  20. data/bin/commands/dialog.rb +2 -2
  21. data/bin/commands/dnd.rb +9 -9
  22. data/bin/commands/emoji.rb +2 -2
  23. data/bin/commands/files.rb +14 -25
  24. data/bin/commands/files_comments.rb +2 -23
  25. data/bin/commands/groups.rb +33 -33
  26. data/bin/commands/im.rb +13 -13
  27. data/bin/commands/migration.rb +2 -2
  28. data/bin/commands/mpim.rb +11 -11
  29. data/bin/commands/oauth.rb +4 -4
  30. data/bin/commands/pins.rb +6 -6
  31. data/bin/commands/reactions.rb +8 -8
  32. data/bin/commands/reminders.rb +10 -10
  33. data/bin/commands/rtm.rb +4 -4
  34. data/bin/commands/search.rb +7 -7
  35. data/bin/commands/stars.rb +6 -6
  36. data/bin/commands/team.rb +8 -8
  37. data/bin/commands/team_profile.rb +2 -2
  38. data/bin/commands/usergroups.rb +11 -11
  39. data/bin/commands/usergroups_users.rb +4 -4
  40. data/bin/commands/users.rb +21 -22
  41. data/bin/commands/users_profile.rb +4 -4
  42. data/lib/slack/events/request.rb +9 -5
  43. data/lib/slack/real_time/client.rb +23 -6
  44. data/lib/slack/real_time/concurrency/async.rb +55 -23
  45. data/lib/slack/real_time/concurrency/celluloid.rb +0 -1
  46. data/lib/slack/real_time/concurrency/eventmachine.rb +0 -5
  47. data/lib/slack/real_time/socket.rb +16 -9
  48. data/lib/slack/version.rb +1 -1
  49. data/lib/slack/web/api/endpoints.rb +2 -0
  50. data/lib/slack/web/api/endpoints/api.rb +1 -1
  51. data/lib/slack/web/api/endpoints/apps.rb +1 -1
  52. data/lib/slack/web/api/endpoints/apps_permissions.rb +2 -2
  53. data/lib/slack/web/api/endpoints/apps_permissions_resources.rb +1 -1
  54. data/lib/slack/web/api/endpoints/apps_permissions_scopes.rb +1 -1
  55. data/lib/slack/web/api/endpoints/apps_permissions_users.rb +2 -2
  56. data/lib/slack/web/api/endpoints/auth.rb +2 -2
  57. data/lib/slack/web/api/endpoints/bots.rb +1 -1
  58. data/lib/slack/web/api/endpoints/channels.rb +18 -15
  59. data/lib/slack/web/api/endpoints/chat.rb +63 -9
  60. data/lib/slack/web/api/endpoints/chat_scheduledMessages.rb +37 -0
  61. data/lib/slack/web/api/endpoints/conversations.rb +17 -17
  62. data/lib/slack/web/api/endpoints/dialog.rb +1 -1
  63. data/lib/slack/web/api/endpoints/dnd.rb +4 -4
  64. data/lib/slack/web/api/endpoints/emoji.rb +1 -1
  65. data/lib/slack/web/api/endpoints/files.rb +7 -18
  66. data/lib/slack/web/api/endpoints/files_comments.rb +1 -34
  67. data/lib/slack/web/api/endpoints/groups.rb +18 -16
  68. data/lib/slack/web/api/endpoints/im.rb +8 -6
  69. data/lib/slack/web/api/endpoints/migration.rb +1 -1
  70. data/lib/slack/web/api/endpoints/mpim.rb +7 -5
  71. data/lib/slack/web/api/endpoints/oauth.rb +2 -2
  72. data/lib/slack/web/api/endpoints/pins.rb +5 -3
  73. data/lib/slack/web/api/endpoints/reactions.rb +6 -4
  74. data/lib/slack/web/api/endpoints/reminders.rb +5 -5
  75. data/lib/slack/web/api/endpoints/rtm.rb +2 -2
  76. data/lib/slack/web/api/endpoints/search.rb +3 -3
  77. data/lib/slack/web/api/endpoints/stars.rb +5 -3
  78. data/lib/slack/web/api/endpoints/team.rb +5 -4
  79. data/lib/slack/web/api/endpoints/team_profile.rb +1 -1
  80. data/lib/slack/web/api/endpoints/usergroups.rb +5 -5
  81. data/lib/slack/web/api/endpoints/usergroups_users.rb +2 -2
  82. data/lib/slack/web/api/endpoints/users.rb +12 -12
  83. data/lib/slack/web/api/endpoints/users_profile.rb +2 -2
  84. data/spec/integration/integration_spec.rb +43 -36
  85. data/spec/slack/events/request_spec.rb +29 -1
  86. data/spec/slack/real_time/concurrency/celluloid_spec.rb +5 -0
  87. data/spec/slack/real_time/concurrency/eventmachine_spec.rb +1 -0
  88. data/spec/slack/web/api/endpoints/chat_scheduledMessages_spec.rb +7 -0
  89. data/spec/slack/web/api/endpoints/files_comments_spec.rb +0 -19
  90. data/spec/spec_helper.rb +5 -0
  91. metadata +6 -2
@@ -2,8 +2,8 @@
2
2
 
3
3
  desc 'UsergroupsUsers methods.'
4
4
  command 'usergroups_users' do |g|
5
- g.desc 'List all users in a User Group'
6
- g.long_desc %( List all users in a User Group )
5
+ g.desc 'This method returns a list of all users within a User Group.'
6
+ g.long_desc %( This method returns a list of all users within a User Group. )
7
7
  g.command 'list' do |c|
8
8
  c.flag 'usergroup', desc: 'The encoded ID of the User Group to update.'
9
9
  c.flag 'include_disabled', desc: 'Allow results that involve disabled User Groups.'
@@ -12,8 +12,8 @@ command 'usergroups_users' do |g|
12
12
  end
13
13
  end
14
14
 
15
- g.desc 'Update the list of users for a User Group'
16
- g.long_desc %( Update the list of users for a User Group )
15
+ g.desc 'This method updates the list of users that belong to a User Group. This method replaces all users in a User Group with the list of users provided in the users parameter.'
16
+ g.long_desc %( This method updates the list of users that belong to a User Group. This method replaces all users in a User Group with the list of users provided in the users parameter. )
17
17
  g.command 'update' do |c|
18
18
  c.flag 'usergroup', desc: 'The encoded ID of the User Group to update.'
19
19
  c.flag 'users', desc: 'A comma separated string of encoded user IDs that represent the entire list of users for the User Group.'
@@ -1,9 +1,9 @@
1
1
  # This file was auto-generated by lib/tasks/web.rake
2
2
 
3
- desc 'Get info on members of your Slack team.'
3
+ desc 'Users methods.'
4
4
  command 'users' do |g|
5
- g.desc 'List conversations the calling user may access.'
6
- g.long_desc %( List conversations the calling user may access. )
5
+ g.desc "As part of the Conversations API, this method's required scopes depend on the type of channel-like object you're working with. For classic Slack apps, a corresponding channels: scope is required when working with public channels, groups: for private channels, also the same rules are applied for im: and mpim:. For workspace apps, a conversations: scope is all that's needed."
6
+ g.long_desc %( As part of the Conversations API, this method's required scopes depend on the type of channel-like object you're working with. For classic Slack apps, a corresponding channels: scope is required when working with public channels, groups: for private channels, also the same rules are applied for im: and mpim:. For workspace apps, a conversations: scope is all that's needed. )
7
7
  g.command 'conversations' do |c|
8
8
  c.flag 'cursor', desc: "Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata. Default value fetches the first 'page' of the collection. See pagination for more detail."
9
9
  c.flag 'exclude_archived', desc: 'Set to true to exclude archived channels from the list.'
@@ -15,16 +15,16 @@ command 'users' do |g|
15
15
  end
16
16
  end
17
17
 
18
- g.desc 'Delete the user profile photo'
19
- g.long_desc %( Delete the user profile photo )
18
+ g.desc 'This method allows the user to delete their profile image. It will clear whatever image is currently set.'
19
+ g.long_desc %( This method allows the user to delete their profile image. It will clear whatever image is currently set. )
20
20
  g.command 'deletePhoto' do |c|
21
21
  c.action do |_global_options, options, _args|
22
22
  puts JSON.dump($client.users_deletePhoto(options))
23
23
  end
24
24
  end
25
25
 
26
- g.desc 'Gets user presence information.'
27
- g.long_desc %( Gets user presence information. )
26
+ g.desc "This method lets you find out information about a user's presence."
27
+ g.long_desc %( This method lets you find out information about a user's presence. Consult the presence documentation for more details. )
28
28
  g.command 'getPresence' do |c|
29
29
  c.flag 'user', desc: 'User to get presence info on. Defaults to the authed user.'
30
30
  c.action do |_global_options, options, _args|
@@ -41,16 +41,16 @@ command 'users' do |g|
41
41
  end
42
42
  end
43
43
 
44
- g.desc "Get a user's identity."
45
- g.long_desc %( Get a user's identity. )
44
+ g.desc "After your Slack app is awarded an identity token through Sign in with Slack, use this method to retrieve a user's identity."
45
+ g.long_desc %( After your Slack app is awarded an identity token through Sign in with Slack, use this method to retrieve a user's identity. )
46
46
  g.command 'identity' do |c|
47
47
  c.action do |_global_options, options, _args|
48
48
  puts JSON.dump($client.users_identity(options))
49
49
  end
50
50
  end
51
51
 
52
- g.desc 'Gets information about a user.'
53
- g.long_desc %( Gets information about a user. )
52
+ g.desc 'This method returns information about a member of a workspace.'
53
+ g.long_desc %( This method returns information about a member of a workspace. )
54
54
  g.command 'info' do |c|
55
55
  c.flag 'user', desc: 'User to get info on.'
56
56
  c.flag 'include_locale', desc: 'Set this to true to receive the locale for this user. Defaults to false.'
@@ -59,20 +59,19 @@ command 'users' do |g|
59
59
  end
60
60
  end
61
61
 
62
- g.desc 'Lists all users in a Slack team.'
63
- g.long_desc %( Lists all users in a Slack team. )
62
+ g.desc 'This method returns a list of all users in the workspace. This includes deleted/deactivated users.'
63
+ g.long_desc %( This method returns a list of all users in the workspace. This includes deleted/deactivated users. )
64
64
  g.command 'list' do |c|
65
65
  c.flag 'cursor', desc: "Paginate through collections of data by setting the cursor parameter to a next_cursor attribute returned by a previous request's response_metadata. Default value fetches the first 'page' of the collection. See pagination for more detail."
66
66
  c.flag 'include_locale', desc: 'Set this to true to receive the locale for users. Defaults to false.'
67
67
  c.flag 'limit', desc: "The maximum number of items to return. Fewer than the requested number of items may be returned, even if the end of the users list hasn't been reached."
68
- c.flag 'presence', desc: 'Deprecated. Whether to include presence data in the output. Defaults to false. Setting this to true reduces performance, especially with large teams.'
69
68
  c.action do |_global_options, options, _args|
70
69
  puts JSON.dump($client.users_list(options))
71
70
  end
72
71
  end
73
72
 
74
- g.desc 'Find a user with an email address.'
75
- g.long_desc %( Find a user with an email address. )
73
+ g.desc 'Retrieve a single user by looking them up by their registered email address. Requires users:read.email.'
74
+ g.long_desc %( Retrieve a single user by looking them up by their registered email address. Requires users:read.email. )
76
75
  g.command 'lookupByEmail' do |c|
77
76
  c.flag 'email', desc: 'An email address belonging to a user in the workspace.'
78
77
  c.action do |_global_options, options, _args|
@@ -89,16 +88,16 @@ command 'users' do |g|
89
88
  end
90
89
  end
91
90
 
92
- g.desc 'Marked a user as active. Deprecated and non-functional.'
93
- g.long_desc %( Marked a user as active. Deprecated and non-functional. )
91
+ g.desc 'This method is no longer functional and the behavior it controlled is no longer offered. The method will no longer exist beginning May 8, 2018.'
92
+ g.long_desc %( This method is no longer functional and the behavior it controlled is no longer offered. The method will no longer exist beginning May 8, 2018. )
94
93
  g.command 'setActive' do |c|
95
94
  c.action do |_global_options, options, _args|
96
95
  puts JSON.dump($client.users_setActive(options))
97
96
  end
98
97
  end
99
98
 
100
- g.desc 'Set the user profile photo'
101
- g.long_desc %( Set the user profile photo )
99
+ g.desc 'This method allows the user to set their profile image. The caller can pass image data via image.'
100
+ g.long_desc %( This method allows the user to set their profile image. The caller can pass image data via image. )
102
101
  g.command 'setPhoto' do |c|
103
102
  c.flag 'image', desc: 'File contents via multipart/form-data.'
104
103
  c.flag 'crop_w', desc: 'Width/height of crop box (always square).'
@@ -109,8 +108,8 @@ command 'users' do |g|
109
108
  end
110
109
  end
111
110
 
112
- g.desc 'Manually sets user presence.'
113
- g.long_desc %( Manually sets user presence. )
111
+ g.desc "This method lets you set the calling user's manual presence."
112
+ g.long_desc %( This method lets you set the calling user's manual presence. Consult the presence documentation for more details. )
114
113
  g.command 'setPresence' do |c|
115
114
  c.flag 'presence', desc: 'Either auto or away.'
116
115
  c.action do |_global_options, options, _args|
@@ -2,8 +2,8 @@
2
2
 
3
3
  desc 'UsersProfile methods.'
4
4
  command 'users_profile' do |g|
5
- g.desc "Retrieves a user's profile information."
6
- g.long_desc %( Retrieves a user's profile information. )
5
+ g.desc "Use this method to retrieve a user's profile information."
6
+ g.long_desc %( Use this method to retrieve a user's profile information. )
7
7
  g.command 'get' do |c|
8
8
  c.flag 'include_labels', desc: 'Include labels for each ID in custom profile fields.'
9
9
  c.flag 'user', desc: 'User to retrieve profile info for.'
@@ -12,8 +12,8 @@ command 'users_profile' do |g|
12
12
  end
13
13
  end
14
14
 
15
- g.desc 'Set the profile information for a user.'
16
- g.long_desc %( Set the profile information for a user. )
15
+ g.desc "Use this method to set a user's profile information, including name, email, current status, and other attributes."
16
+ g.long_desc %( Use this method to set a user's profile information, including name, email, current status, and other attributes. )
17
17
  g.command 'set' do |c|
18
18
  c.flag 'name', desc: 'Name of a single key to set. Usable only if profile is not passed.'
19
19
  c.flag 'profile', desc: 'Collection of key:value pairs presented as a URL-encoded JSON hash. At most 50 fields may be set. Each field name is limited to 255 characters.'
@@ -5,10 +5,14 @@ module Slack
5
5
  class TimestampExpired < StandardError; end
6
6
  class InvalidSignature < StandardError; end
7
7
 
8
- attr_reader :http_request
8
+ attr_reader :http_request,
9
+ :signing_secret,
10
+ :signature_expires_in
9
11
 
10
- def initialize(http_request)
12
+ def initialize(http_request, options = {})
11
13
  @http_request = http_request
14
+ @signing_secret = options[:signing_secret] || Slack::Events.config.signing_secret
15
+ @signature_expires_in = options[:signature_expires_in] || Slack::Events.config.signature_expires_in
12
16
  end
13
17
 
14
18
  # Request timestamp.
@@ -34,16 +38,16 @@ module Slack
34
38
 
35
39
  # Returns true if the signature coming from Slack has expired.
36
40
  def expired?
37
- timestamp.nil? || (Time.now.to_i - timestamp.to_i).abs > Slack::Events.config.signature_expires_in
41
+ timestamp.nil? || (Time.now.to_i - timestamp.to_i).abs > signature_expires_in
38
42
  end
39
43
 
40
44
  # Returns true if the signature coming from Slack is valid.
41
45
  def valid?
42
- raise MissingSigningSecret unless Slack::Events.config.signing_secret
46
+ raise MissingSigningSecret unless signing_secret
43
47
 
44
48
  digest = OpenSSL::Digest::SHA256.new
45
49
  signature_basestring = [version, timestamp, body].join(':')
46
- hex_hash = OpenSSL::HMAC.hexdigest(digest, Slack::Events.config.signing_secret, signature_basestring)
50
+ hex_hash = OpenSSL::HMAC.hexdigest(digest, signing_secret, signature_basestring)
47
51
  computed_signature = [version, hex_hash].join('=')
48
52
  computed_signature == signature
49
53
  end
@@ -81,8 +81,6 @@ module Slack
81
81
 
82
82
  def run_loop
83
83
  @socket.connect! do |driver|
84
- @callback.call(driver) if @callback
85
-
86
84
  driver.on :open do |event|
87
85
  logger.debug("#{self.class}##{__method__}") { event.class.name }
88
86
  open(event)
@@ -100,16 +98,35 @@ module Slack
100
98
  close(event)
101
99
  callback(event, :closed)
102
100
  end
101
+
102
+ # This must be called last to ensure any events are registered before invoking user code.
103
+ @callback.call(driver) if @callback
103
104
  end
104
105
  end
105
106
 
106
- def run_ping!
107
+ # Ensure the server is running, and ping the remote server if no other messages were sent.
108
+ def keep_alive?
109
+ # We can't ping the remote server if we aren't connected.
110
+ return false if @socket.nil? || !@socket.connected?
111
+
107
112
  time_since_last_message = @socket.time_since_last_message
108
- return if time_since_last_message < websocket_ping
109
- raise Slack::RealTime::Client::ClientNotStartedError if !@socket.connected? || time_since_last_message > (websocket_ping * 2)
110
113
 
114
+ # If the server responded within the specified time, we are okay:
115
+ return true if time_since_last_message < websocket_ping
116
+
117
+ # If the server has not responded for a while:
118
+ return false if time_since_last_message > (websocket_ping * 2)
119
+
120
+ # Kick off the next ping message:
111
121
  ping
112
- rescue Slack::RealTime::Client::ClientNotStartedError
122
+
123
+ true
124
+ end
125
+
126
+ # Check if the remote server is responsive, and if not, restart the connection.
127
+ def run_ping!
128
+ return if keep_alive?
129
+
113
130
  restart_async
114
131
  end
115
132
 
@@ -1,14 +1,11 @@
1
1
  require 'async/websocket'
2
+ require 'async/notification'
2
3
  require 'async/clock'
3
4
 
4
5
  module Slack
5
6
  module RealTime
6
7
  module Concurrency
7
8
  module Async
8
- class Reactor < ::Async::Reactor
9
- def_delegators :@timers, :cancel
10
- end
11
-
12
9
  class Client < ::Async::WebSocket::Client
13
10
  extend ::Forwardable
14
11
  def_delegators :@driver, :on, :text, :binary, :emit
@@ -17,35 +14,57 @@ module Slack
17
14
  class Socket < Slack::RealTime::Socket
18
15
  attr_reader :client
19
16
 
17
+ def start_sync(client)
18
+ start_reactor(client).wait
19
+ end
20
+
20
21
  def start_async(client)
21
- @reactor = Reactor.new
22
22
  Thread.new do
23
+ start_reactor(client)
24
+ end
25
+ end
26
+
27
+ def start_reactor(client)
28
+ Async do |task|
29
+ @restart = ::Async::Notification.new
30
+
23
31
  if client.run_ping?
24
- @reactor.every(client.websocket_ping) do
25
- client.run_ping!
32
+ @ping_task = task.async do |subtask|
33
+ subtask.annotate 'client keep-alive'
34
+
35
+ # The timer task will naturally exit after the driver is set to nil.
36
+ while @restart
37
+ subtask.sleep client.websocket_ping
38
+ client.run_ping! if @restart
39
+ end
26
40
  end
27
41
  end
28
- @reactor.run do |task|
29
- task.async do
30
- client.run_loop
42
+
43
+ while @restart
44
+ @client_task.stop if @client_task
45
+
46
+ @client_task = task.async do |subtask|
47
+ begin
48
+ subtask.annotate 'client run-loop'
49
+ client.run_loop
50
+ rescue ::Async::Wrapper::Cancelled => e
51
+ # Will get restarted by ping worker.
52
+ client.logger.warn(subtask.to_s) { e.message }
53
+ end
31
54
  end
55
+
56
+ @restart.wait
32
57
  end
58
+
59
+ @ping_task.stop if @ping_task
33
60
  end
34
61
  end
35
62
 
36
- def restart_async(client, new_url)
63
+ def restart_async(_client, new_url)
37
64
  @url = new_url
38
65
  @last_message_at = current_time
39
- return unless @reactor
40
66
 
41
- @reactor.async do
42
- client.run_loop
43
- end
44
- end
45
-
46
- def disconnect!
47
- super
48
- @reactor.cancel
67
+ @restart.signal if @restart
49
68
  end
50
69
 
51
70
  def current_time
@@ -57,14 +76,27 @@ module Slack
57
76
  run_loop
58
77
  end
59
78
 
79
+ # Kill the restart/ping loop.
80
+ def disconnect!
81
+ super
82
+ ensure
83
+ if restart = @restart
84
+ @restart = nil
85
+ restart.signal
86
+ end
87
+ end
88
+
89
+ # Close the socket.
60
90
  def close
61
- @closing = true
62
- @driver.close if @driver
63
91
  super
92
+ ensure
93
+ if @socket
94
+ @socket.close
95
+ @socket = nil
96
+ end
64
97
  end
65
98
 
66
99
  def run_loop
67
- @closing = false
68
100
  while @driver && @driver.next_event
69
101
  # $stderr.puts event.inspect
70
102
  end
@@ -46,7 +46,6 @@ module Slack
46
46
 
47
47
  def close
48
48
  @closing = true
49
- driver.close if driver
50
49
  super
51
50
  end
52
51
 
@@ -56,11 +56,6 @@ module Slack
56
56
  @thread = nil
57
57
  end
58
58
 
59
- def close
60
- driver.close if driver
61
- super
62
- end
63
-
64
59
  def send_data(message)
65
60
  logger.debug("#{self.class}##{__method__}") { message }
66
61
  driver.send(message)
@@ -18,9 +18,9 @@ module Slack
18
18
  def send_data(message)
19
19
  logger.debug("#{self.class}##{__method__}") { message }
20
20
  case message
21
- when Numeric then driver.text(message.to_s)
22
- when String then driver.text(message)
23
- when Array then driver.binary(message)
21
+ when Numeric then @driver.text(message.to_s)
22
+ when String then @driver.text(message)
23
+ when Array then @driver.binary(message)
24
24
  else false
25
25
  end
26
26
  end
@@ -29,21 +29,24 @@ module Slack
29
29
  return if connected?
30
30
 
31
31
  connect
32
- logger.debug("#{self.class}##{__method__}") { driver.class }
32
+ logger.debug("#{self.class}##{__method__}") { @driver.class }
33
33
 
34
- driver.on :message do
34
+ @driver.on :message do
35
35
  @last_message_at = current_time
36
36
  end
37
37
 
38
- yield driver if block_given?
38
+ yield @driver if block_given?
39
39
  end
40
40
 
41
+ # Gracefully shut down the connection.
41
42
  def disconnect!
42
- driver.close
43
+ @driver.close
44
+ ensure
45
+ close
43
46
  end
44
47
 
45
48
  def connected?
46
- !driver.nil?
49
+ !@driver.nil?
47
50
  end
48
51
 
49
52
  def start_sync(client)
@@ -73,7 +76,11 @@ module Slack
73
76
  end
74
77
 
75
78
  def close
76
- @driver = nil
79
+ # When you call `driver.emit(:close)`, it will typically end up calling `client.close` which will call `@socket.close` and end up back here. In order to break this infinite recursion, we check and set `@driver = nil` before invoking `client.close`.
80
+ if driver = @driver
81
+ @driver = nil
82
+ driver.emit(:close)
83
+ end
77
84
  end
78
85
 
79
86
  protected
@@ -1,3 +1,3 @@
1
1
  module Slack
2
- VERSION = '0.14.1'.freeze
2
+ VERSION = '0.14.2'.freeze
3
3
  end
@@ -10,6 +10,7 @@ require_relative 'endpoints/auth'
10
10
  require_relative 'endpoints/bots'
11
11
  require_relative 'endpoints/channels'
12
12
  require_relative 'endpoints/chat'
13
+ require_relative 'endpoints/chat_scheduledMessages'
13
14
  require_relative 'endpoints/conversations'
14
15
  require_relative 'endpoints/dialog'
15
16
  require_relative 'endpoints/dnd'
@@ -54,6 +55,7 @@ module Slack
54
55
  include Bots
55
56
  include Channels
56
57
  include Chat
58
+ include ChatScheduledmessages
57
59
  include Conversations
58
60
  include Dialog
59
61
  include Dnd