slack-ruby-client-bhe 0.5.4

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.
Files changed (147) hide show
  1. checksums.yaml +17 -0
  2. data/.gitignore +4 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +6 -0
  6. data/.rubocop_todo.yml +78 -0
  7. data/.travis.yml +26 -0
  8. data/CHANGELOG.md +79 -0
  9. data/CONTRIBUTING.md +157 -0
  10. data/Gemfile +5 -0
  11. data/LICENSE.md +22 -0
  12. data/README.md +385 -0
  13. data/RELEASING.md +69 -0
  14. data/Rakefile +19 -0
  15. data/UPGRADING.md +26 -0
  16. data/bin/commands.rb +21 -0
  17. data/bin/commands/api.rb +14 -0
  18. data/bin/commands/auth.rb +12 -0
  19. data/bin/commands/channels.rb +149 -0
  20. data/bin/commands/chat.rb +47 -0
  21. data/bin/commands/dnd.rb +47 -0
  22. data/bin/commands/emoji.rb +12 -0
  23. data/bin/commands/files.rb +72 -0
  24. data/bin/commands/groups.rb +167 -0
  25. data/bin/commands/im.rb +53 -0
  26. data/bin/commands/mpim.rb +53 -0
  27. data/bin/commands/oauth.rb +16 -0
  28. data/bin/commands/pins.rb +37 -0
  29. data/bin/commands/reactions.rb +53 -0
  30. data/bin/commands/rtm.rb +15 -0
  31. data/bin/commands/search.rb +40 -0
  32. data/bin/commands/stars.rb +37 -0
  33. data/bin/commands/team.rb +32 -0
  34. data/bin/commands/usergroups.rb +73 -0
  35. data/bin/commands/users.rb +57 -0
  36. data/bin/slack +50 -0
  37. data/examples/hi_real_time/Gemfile +5 -0
  38. data/examples/hi_real_time/hi.gif +0 -0
  39. data/examples/hi_real_time/hi.rb +32 -0
  40. data/examples/hi_real_time_and_web/Gemfile +5 -0
  41. data/examples/hi_real_time_and_web/hi.gif +0 -0
  42. data/examples/hi_real_time_and_web/hi.rb +24 -0
  43. data/examples/hi_real_time_async/Gemfile +5 -0
  44. data/examples/hi_real_time_async/hi.rb +29 -0
  45. data/examples/hi_web/Gemfile +3 -0
  46. data/examples/hi_web/hi.gif +0 -0
  47. data/examples/hi_web/hi.rb +12 -0
  48. data/examples/new_ticket/Gemfile +3 -0
  49. data/examples/new_ticket/new_ticket.rb +25 -0
  50. data/lib/slack-ruby-client.rb +29 -0
  51. data/lib/slack.rb +1 -0
  52. data/lib/slack/config.rb +21 -0
  53. data/lib/slack/messages/formatting.rb +30 -0
  54. data/lib/slack/real_time/api/message.rb +20 -0
  55. data/lib/slack/real_time/api/message_id.rb +14 -0
  56. data/lib/slack/real_time/api/ping.rb +16 -0
  57. data/lib/slack/real_time/api/typing.rb +17 -0
  58. data/lib/slack/real_time/client.rb +152 -0
  59. data/lib/slack/real_time/concurrency.rb +8 -0
  60. data/lib/slack/real_time/concurrency/celluloid.rb +93 -0
  61. data/lib/slack/real_time/concurrency/eventmachine.rb +39 -0
  62. data/lib/slack/real_time/config.rb +55 -0
  63. data/lib/slack/real_time/socket.rb +81 -0
  64. data/lib/slack/version.rb +3 -0
  65. data/lib/slack/web/api/endpoints.rb +53 -0
  66. data/lib/slack/web/api/endpoints/api.rb +24 -0
  67. data/lib/slack/web/api/endpoints/auth.rb +20 -0
  68. data/lib/slack/web/api/endpoints/channels.rb +220 -0
  69. data/lib/slack/web/api/endpoints/chat.rb +91 -0
  70. data/lib/slack/web/api/endpoints/dnd.rb +64 -0
  71. data/lib/slack/web/api/endpoints/emoji.rb +20 -0
  72. data/lib/slack/web/api/endpoints/files.rb +108 -0
  73. data/lib/slack/web/api/endpoints/groups.rb +247 -0
  74. data/lib/slack/web/api/endpoints/im.rb +85 -0
  75. data/lib/slack/web/api/endpoints/mpim.rb +84 -0
  76. data/lib/slack/web/api/endpoints/oauth.rb +32 -0
  77. data/lib/slack/web/api/endpoints/pins.rb +64 -0
  78. data/lib/slack/web/api/endpoints/presence.rb +23 -0
  79. data/lib/slack/web/api/endpoints/reactions.rb +89 -0
  80. data/lib/slack/web/api/endpoints/rtm.rb +27 -0
  81. data/lib/slack/web/api/endpoints/search.rb +65 -0
  82. data/lib/slack/web/api/endpoints/stars.rb +61 -0
  83. data/lib/slack/web/api/endpoints/team.rb +47 -0
  84. data/lib/slack/web/api/endpoints/usergroups.rb +113 -0
  85. data/lib/slack/web/api/endpoints/users.rb +73 -0
  86. data/lib/slack/web/api/error.rb +14 -0
  87. data/lib/slack/web/api/mixins.rb +3 -0
  88. data/lib/slack/web/api/mixins/channels.id.json +20 -0
  89. data/lib/slack/web/api/mixins/channels.id.rb +26 -0
  90. data/lib/slack/web/api/mixins/groups.id.json +20 -0
  91. data/lib/slack/web/api/mixins/groups.id.rb +26 -0
  92. data/lib/slack/web/api/mixins/users.id.json +20 -0
  93. data/lib/slack/web/api/mixins/users.id.rb +26 -0
  94. data/lib/slack/web/api/patches/chat.1.text-attachments-required.patch +13 -0
  95. data/lib/slack/web/api/patches/chat.2.attachments-json.patch +17 -0
  96. data/lib/slack/web/api/schema/group.json +14 -0
  97. data/lib/slack/web/api/schema/method.json +45 -0
  98. data/lib/slack/web/api/tasks/generate.rake +61 -0
  99. data/lib/slack/web/api/templates/command.erb +34 -0
  100. data/lib/slack/web/api/templates/commands.erb +5 -0
  101. data/lib/slack/web/api/templates/endpoints.erb +21 -0
  102. data/lib/slack/web/api/templates/method.erb +49 -0
  103. data/lib/slack/web/client.rb +28 -0
  104. data/lib/slack/web/config.rb +41 -0
  105. data/lib/slack/web/faraday/connection.rb +29 -0
  106. data/lib/slack/web/faraday/request.rb +39 -0
  107. data/lib/slack/web/faraday/response/raise_error.rb +15 -0
  108. data/lib/slack_ruby_client.rb +1 -0
  109. data/screenshots/register-bot.png +0 -0
  110. data/slack-ruby-client.gemspec +31 -0
  111. data/slack.png +0 -0
  112. data/spec/fixtures/slack/web/429_error.yml +83 -0
  113. data/spec/fixtures/slack/web/auth_test_error.yml +48 -0
  114. data/spec/fixtures/slack/web/auth_test_success.yml +57 -0
  115. data/spec/fixtures/slack/web/channels_info.yml +46 -0
  116. data/spec/fixtures/slack/web/groups_info.yml +43 -0
  117. data/spec/fixtures/slack/web/rtm_start.yml +73 -0
  118. data/spec/fixtures/slack/web/users_info.yml +130 -0
  119. data/spec/fixtures/slack/web/users_list.yml +72 -0
  120. data/spec/integration/integration_spec.rb +107 -0
  121. data/spec/slack/config_spec.rb +14 -0
  122. data/spec/slack/messages/formatting_spec.rb +43 -0
  123. data/spec/slack/real_time/api/message_spec.rb +15 -0
  124. data/spec/slack/real_time/api/ping_spec.rb +15 -0
  125. data/spec/slack/real_time/api/typing_spec.rb +15 -0
  126. data/spec/slack/real_time/client_spec.rb +198 -0
  127. data/spec/slack/real_time/concurrency/celluloid_spec.rb +58 -0
  128. data/spec/slack/real_time/concurrency/eventmachine_spec.rb +49 -0
  129. data/spec/slack/slack_spec.rb +58 -0
  130. data/spec/slack/version_spec.rb +7 -0
  131. data/spec/slack/web/api/endpoints/auth_spec.rb +20 -0
  132. data/spec/slack/web/api/endpoints/channels_spec.rb +11 -0
  133. data/spec/slack/web/api/endpoints/chat_spec.rb +33 -0
  134. data/spec/slack/web/api/endpoints/groups_spec.rb +11 -0
  135. data/spec/slack/web/api/endpoints/users_spec.rb +17 -0
  136. data/spec/slack/web/api/error_spec.rb +14 -0
  137. data/spec/slack/web/api/mixins/channels_spec.rb +31 -0
  138. data/spec/slack/web/api/mixins/groups_spec.rb +31 -0
  139. data/spec/slack/web/api/mixins/users_spec.rb +31 -0
  140. data/spec/slack/web/client_spec.rb +134 -0
  141. data/spec/spec_helper.rb +14 -0
  142. data/spec/support/queue_with_timeout.rb +34 -0
  143. data/spec/support/real_time/concurrency/mock.rb +31 -0
  144. data/spec/support/real_time/connected_client.rb +16 -0
  145. data/spec/support/token.rb +10 -0
  146. data/spec/support/vcr.rb +8 -0
  147. metadata +392 -0
@@ -0,0 +1,20 @@
1
+ module Slack
2
+ module RealTime
3
+ module Api
4
+ module Message
5
+ #
6
+ # Sends a message to a channel.
7
+ #
8
+ # @option options [channel] :channel
9
+ # Channel to send message to. Can be a public channel, private group or IM channel. Can be an encoded ID, or a name.
10
+ # @option options [Object] :text
11
+ # Text of the message to send. See below for an explanation of formatting.
12
+ def message(options = {})
13
+ throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
14
+ throw ArgumentError.new('Required arguments :text missing') if options[:text].nil?
15
+ send_json({ type: 'message', id: next_id }.merge(options))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module Slack
2
+ module RealTime
3
+ module Api
4
+ module MessageId
5
+ private
6
+
7
+ def next_id
8
+ @next_id ||= 0
9
+ @next_id += 1
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ module Slack
2
+ module RealTime
3
+ module Api
4
+ module Ping
5
+ #
6
+ # Clients should try to quickly detect disconnections, even in idle periods, so that users can easily tell the
7
+ # difference between being disconnected and everyone being quiet. Not all web browsers support the WebSocket
8
+ # ping spec, so the RTM protocol also supports ping/pong messages.
9
+ #
10
+ def ping(options = {})
11
+ send_json({ type: 'ping', id: next_id }.merge(options))
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Slack
2
+ module RealTime
3
+ module Api
4
+ module Typing
5
+ #
6
+ # Send a typing indicator to indicate that the user is currently writing a message.
7
+ #
8
+ # @option options [channel] :channel
9
+ # Channel to send message to. Can be a public channel, private group or IM channel. Can be an encoded ID, or a name.
10
+ def typing(options = {})
11
+ throw ArgumentError.new('Required arguments :channel missing') if options[:channel].nil?
12
+ send_json({ type: 'typing', id: next_id }.merge(options))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,152 @@
1
+ module Slack
2
+ module RealTime
3
+ class Client
4
+ class ClientNotStartedError < StandardError; end
5
+ class ClientAlreadyStartedError < StandardError; end
6
+
7
+ include Api::MessageId
8
+ include Api::Ping
9
+ include Api::Message
10
+ include Api::Typing
11
+
12
+ attr_accessor :web_client
13
+ attr_accessor(*Config::ATTRIBUTES)
14
+
15
+ def initialize(options = {})
16
+ @callbacks = Hash.new { |h, k| h[k] = [] }
17
+ Slack::RealTime::Config::ATTRIBUTES.each do |key|
18
+ send("#{key}=", options[key] || Slack::RealTime.config.send(key))
19
+ end
20
+ @token ||= Slack.config.token
21
+ @web_client = Slack::Web::Client.new(token: token)
22
+ end
23
+
24
+ [:url, :team, :self, :users, :channels, :groups, :ims, :bots].each do |attr|
25
+ define_method attr do
26
+ @options[attr.to_s] if @options
27
+ end
28
+ end
29
+
30
+ def on(type, &block)
31
+ type = type.to_s
32
+ callbacks[type] << block
33
+ end
34
+
35
+ # Start RealTime client and block until it disconnects.
36
+ # @yieldparam [Websocket::Driver] driver
37
+ def start!(&block)
38
+ socket = build_socket
39
+ socket.start_sync { run_loop(socket, &block) }
40
+ end
41
+
42
+ # Start RealTime client and return immediately.
43
+ # The RealTime::Client will run in the background.
44
+ # @yieldparam [Websocket::Driver] driver
45
+ def start_async(&block)
46
+ socket = build_socket
47
+ socket.start_async { run_loop(socket, &block) }
48
+ end
49
+
50
+ def stop!
51
+ fail ClientNotStartedError unless started?
52
+ @socket.disconnect! if @socket
53
+ end
54
+
55
+ def started?
56
+ @socket && @socket.connected?
57
+ end
58
+
59
+ class << self
60
+ def configure
61
+ block_given? ? yield(config) : config
62
+ end
63
+
64
+ def config
65
+ Config
66
+ end
67
+ end
68
+
69
+ protected
70
+
71
+ # @return [Slack::RealTime::Socket]
72
+ def build_socket
73
+ fail ClientAlreadyStartedError if started?
74
+ @options = web_client.rtm_start
75
+
76
+ socket_class.new(@options.fetch('url'), socket_options)
77
+ end
78
+
79
+ def socket_options
80
+ socket_options = {}
81
+ socket_options[:ping] = websocket_ping if websocket_ping
82
+ socket_options[:proxy] = websocket_proxy if websocket_proxy
83
+ socket_options
84
+ end
85
+
86
+ def run_loop(socket)
87
+ @socket = socket
88
+
89
+ @socket.connect! do |driver|
90
+ yield driver if block_given?
91
+
92
+ driver.on :open do |event|
93
+ open(event)
94
+ callback(event, :open)
95
+ end
96
+
97
+ driver.on :message do |event|
98
+ dispatch(event)
99
+ end
100
+
101
+ driver.on :close do |event|
102
+ callback(event, :close)
103
+ close(event)
104
+ end
105
+ end
106
+ end
107
+
108
+ attr_reader :callbacks
109
+ def socket_class
110
+ concurrency::Socket
111
+ end
112
+
113
+ def send_json(data)
114
+ fail ClientNotStartedError unless started?
115
+ @socket.send_data(data.to_json)
116
+ end
117
+
118
+ def open(_event)
119
+ end
120
+
121
+ def close(_event)
122
+ socket = @socket
123
+ @socket = nil
124
+
125
+ [socket, socket_class].each do |s|
126
+ s.close if s.respond_to?(:close)
127
+ end
128
+ end
129
+
130
+ def callback(event, type)
131
+ callbacks = self.callbacks[type.to_s]
132
+ return false unless callbacks
133
+ callbacks.each do |c|
134
+ c.call(event)
135
+ end
136
+ end
137
+
138
+ def dispatch(event)
139
+ return false unless event.data
140
+ data = JSON.parse(event.data)
141
+ type = data['type']
142
+ return false unless type
143
+ callbacks = self.callbacks[type.to_s]
144
+ return false unless callbacks
145
+ callbacks.each do |c|
146
+ c.call(data)
147
+ end
148
+ true
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,8 @@
1
+ module Slack
2
+ module RealTime
3
+ module Concurrency
4
+ autoload :Eventmachine, 'slack/real_time/concurrency/eventmachine'
5
+ autoload :Celluloid, 'slack/real_time/concurrency/celluloid'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,93 @@
1
+ require 'websocket/driver'
2
+ require 'socket'
3
+ require 'forwardable'
4
+ require 'celluloid/current'
5
+ require 'celluloid/io'
6
+
7
+ module Slack
8
+ module RealTime
9
+ module Concurrency
10
+ module Celluloid
11
+ class Socket < Slack::RealTime::Socket
12
+ include ::Celluloid::IO
13
+ include ::Celluloid::Internals::Logger
14
+
15
+ BLOCK_SIZE = 4096
16
+
17
+ extend ::Forwardable
18
+ def_delegator :socket, :write
19
+ def_delegators :driver, :text, :binary, :close
20
+
21
+ attr_reader :socket
22
+
23
+ def initialize(*args)
24
+ super
25
+ @driver = build_driver
26
+ end
27
+
28
+ # @yieldparam [WebSocket::Driver] driver
29
+ def connect!
30
+ super
31
+
32
+ driver.start
33
+ future.run_loop
34
+ end
35
+
36
+ def run_loop
37
+ loop { read } if socket
38
+ rescue EOFError
39
+ # connection closed
40
+ end
41
+
42
+ def read
43
+ buffer = socket.readpartial(BLOCK_SIZE)
44
+ driver.parse buffer
45
+ end
46
+
47
+ def start_async
48
+ future = yield self if block_given?
49
+
50
+ Actor.new(future)
51
+ end
52
+
53
+ def connected?
54
+ !@connected.nil?
55
+ end
56
+
57
+ protected
58
+
59
+ class Actor
60
+ attr_reader :future
61
+
62
+ def initialize(future)
63
+ @future = future
64
+ end
65
+
66
+ def join
67
+ @future.value
68
+ end
69
+ end
70
+
71
+ def build_socket
72
+ socket = TCPSocket.new(addr, port)
73
+ socket = SSLSocket.new(socket, build_ssl_context) if secure?
74
+ socket
75
+ end
76
+
77
+ def build_ssl_context
78
+ OpenSSL::SSL::SSLContext.new(:TLSv1_2_client)
79
+ end
80
+
81
+ def build_driver
82
+ ::WebSocket::Driver.client(self)
83
+ end
84
+
85
+ def connect
86
+ @socket = build_socket
87
+ @connected = @socket.connect
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,39 @@
1
+ require 'faye/websocket'
2
+ require 'eventmachine'
3
+
4
+ module Slack
5
+ module RealTime
6
+ module Concurrency
7
+ module Eventmachine
8
+ class Socket < Slack::RealTime::Socket
9
+ def start_async
10
+ thread = ensure_reactor_running
11
+
12
+ yield self if block_given?
13
+
14
+ thread
15
+ end
16
+
17
+ def send_data(message)
18
+ driver.send(message)
19
+ end
20
+
21
+ protected
22
+
23
+ # @return [Thread]
24
+ def ensure_reactor_running
25
+ return if EventMachine.reactor_running?
26
+
27
+ reactor = Thread.new { EventMachine.run }
28
+ Thread.pass until EventMachine.reactor_running?
29
+ reactor
30
+ end
31
+
32
+ def connect
33
+ @driver = ::Faye::WebSocket::Client.new(url, nil, options)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,55 @@
1
+ module Slack
2
+ module RealTime
3
+ module Config
4
+ class NoConcurrencyError < StandardError; end
5
+
6
+ extend self
7
+
8
+ ATTRIBUTES = [
9
+ :token,
10
+ :websocket_ping,
11
+ :websocket_proxy,
12
+ :concurrency
13
+ ]
14
+
15
+ attr_accessor(*Config::ATTRIBUTES)
16
+
17
+ def reset
18
+ self.websocket_ping = 30
19
+ self.websocket_proxy = nil
20
+ self.token = nil
21
+ self.concurrency = method(:detect_concurrency)
22
+ end
23
+
24
+ def concurrency
25
+ (val = @concurrency).respond_to?(:call) ? val.call : val
26
+ end
27
+
28
+ private
29
+
30
+ def detect_concurrency
31
+ [:Eventmachine, :Celluloid].each do |concurrency|
32
+ begin
33
+ return Slack::RealTime::Concurrency.const_get(concurrency)
34
+ rescue LoadError, NameError
35
+ false # could not be loaded, missing dependencies
36
+ end
37
+ end
38
+
39
+ fail NoConcurrencyError, 'Missing concurrency. Add faye-websocket or celluloid-io to your Gemfile.'
40
+ end
41
+ end
42
+
43
+ class << self
44
+ def configure
45
+ block_given? ? yield(Config) : Config
46
+ end
47
+
48
+ def config
49
+ Config
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ Slack::RealTime::Config.reset
@@ -0,0 +1,81 @@
1
+ module Slack
2
+ module RealTime
3
+ class Socket
4
+ attr_accessor :url
5
+ attr_accessor :options
6
+ attr_reader :driver
7
+
8
+ def initialize(url, options = {})
9
+ @url = url
10
+ @options = options
11
+ @driver = nil
12
+ end
13
+
14
+ def send_data(message)
15
+ case message
16
+ when Numeric then driver.text(message.to_s)
17
+ when String then driver.text(message)
18
+ when Array then driver.binary(message)
19
+ else false
20
+ end
21
+ end
22
+
23
+ def connect!
24
+ return if connected?
25
+
26
+ connect
27
+
28
+ yield driver if block_given?
29
+ end
30
+
31
+ def disconnect!
32
+ driver.close
33
+ end
34
+
35
+ def connected?
36
+ !driver.nil?
37
+ end
38
+
39
+ def start_sync(&block)
40
+ thread = start_async(&block)
41
+ thread.join if thread
42
+ rescue Interrupt
43
+ thread.exit if thread
44
+ end
45
+
46
+ # @return [#join]
47
+ def start_async
48
+ fail NotImplementedError, "Expected #{self.class} to implement #{__method__}."
49
+ end
50
+
51
+ def close(_event)
52
+ @driver = nil
53
+ end
54
+
55
+ protected
56
+
57
+ def addr
58
+ URI(url).host
59
+ end
60
+
61
+ def secure?
62
+ port == URI::HTTPS::DEFAULT_PORT
63
+ end
64
+
65
+ def port
66
+ case (uri = URI(url)).scheme
67
+ when 'wss'.freeze, 'https'.freeze
68
+ URI::HTTPS::DEFAULT_PORT
69
+ when 'ws', 'http'.freeze
70
+ URI::HTTP::DEFAULT_PORT
71
+ else
72
+ uri.port
73
+ end
74
+ end
75
+
76
+ def connect
77
+ fail NotImplementedError, "Expected #{self.class} to implement #{__method__}."
78
+ end
79
+ end
80
+ end
81
+ end