slack-rtm-receiver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1821e8e1626538c67c4d4decf64cd3535011a2af
4
+ data.tar.gz: ab3b8e9761278e7988ecc4455d798f1522fa22c9
5
+ SHA512:
6
+ metadata.gz: 49680d9a6a7fd5221a7f5d5f904c0c401c65b719c018de616742cbff52dc6899e2a573f7b31bedbff5653e85d12d685aa68b5d6dc8ef59ca2a2b2c96ae0c27eb
7
+ data.tar.gz: c4c5d3750d586452ef925e7cd0abaf5edb75934d8c8c2aaa57a441db00caaad2a1d2b2712e688ced049167f4e2a4746fa4aae901752a27b563c636e99441d7f3
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ken J.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,60 @@
1
+ # slack-rtm-receiver
2
+
3
+ A Ruby gem. It connects to Slack Real Time Messaging API to receive events. Runs on EventMachine.
4
+
5
+ ## Requirements
6
+
7
+ - Ruby 2.0.0 <=
8
+ - eventmachine 1.0
9
+ - em-http-request 1.1
10
+ - faye-websocket 0.8
11
+
12
+ ## Getting Started
13
+
14
+ ### Install
15
+
16
+ ```
17
+ $ gem install slack-rtm-receiver
18
+ ```
19
+
20
+ ### Use
21
+
22
+ ```
23
+ require 'slack-rtm-receiver'
24
+ ```
25
+
26
+ Create an object to respond to received events. You can subclass **EventHandler**.
27
+ ```
28
+ class MyHandler < SlackRTMReceiver::EventHandler
29
+ def process_event(event, session)
30
+ if event[:text] == 'hi'
31
+ res_event = {
32
+ type: 'message',
33
+ channel: event[:channel],
34
+ text: 'Hi!'
35
+ }
36
+ session.send_event(res_event)
37
+ end
38
+ end
39
+ end
40
+ SlackRTMReceiver.add_event_handler(MyHandler.new)
41
+ ```
42
+ Or, you can pass a block. The following works the same as above.
43
+ ```
44
+ SlackRTMReceiver::EventHandler.add_type('message') do |event, session|
45
+ if event[:text] == 'hi'
46
+ res_event = {
47
+ type: 'message',
48
+ channel: event[:channel],
49
+ text: 'Hi!'
50
+ }
51
+ session.send_event(res_event)
52
+ end
53
+ end
54
+ ```
55
+
56
+ Start the reactor to connect to Slack.
57
+ ```
58
+ opts = {token: 'xoxb-1234abcd5678efgh'}
59
+ SlackRTMReceiver::Reactor.run(opts)
60
+ ```
@@ -0,0 +1,11 @@
1
+ require 'slack-rtm-receiver/event-handler'
2
+ require 'slack-rtm-receiver/logger'
3
+ require 'slack-rtm-receiver/reactor'
4
+ require 'slack-rtm-receiver/session'
5
+ require 'slack-rtm-receiver/starter'
6
+ require 'slack-rtm-receiver/version'
7
+
8
+
9
+ module SlackRTMReceiver
10
+
11
+ end
@@ -0,0 +1,48 @@
1
+ module SlackRTMReceiver
2
+
3
+ def self.event_handlers=(handlers)
4
+ @event_handlers = handlers
5
+ end
6
+
7
+ def self.event_handlers
8
+ @event_handlers ||= []
9
+ end
10
+
11
+ def self.add_event_handler(handler)
12
+ @event_handlers ||= []
13
+ @event_handlers << handler
14
+ end
15
+
16
+ # Create objects to handle events:
17
+ # - subclass EventHandler, or
18
+ # - just create an instance and pass a block
19
+ class EventHandler
20
+
21
+ # Create and register a handler object by passing a block
22
+ # @param type [String] event type
23
+ def self.add_type(type, &block)
24
+ handler = new(type, &block)
25
+ SlackRTMReceiver.add_event_handler(handler)
26
+ end
27
+
28
+ attr_reader :type
29
+
30
+ # Create a new handler instance
31
+ # @param type [String] event type
32
+ def initialize(type = 'message', &block)
33
+ @type = type
34
+ @block = block if block_given?
35
+ end
36
+
37
+ # Callback method; called by SlackRTMReceiver::Session when event is received
38
+ # @param [Hash] event data
39
+ # @param [Object] caller object
40
+ def process_event(event, session)
41
+ logger = session.logger
42
+ logger.debug "#{self.class.name} running..."
43
+ @block.call(event, session) if @block
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,24 @@
1
+ require 'logger'
2
+
3
+
4
+ module SlackRTMReceiver
5
+
6
+ def self.logger=(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def self.logger
11
+ @logger ||= NullLogger.new()
12
+ end
13
+
14
+ class NullLogger < Logger
15
+
16
+ def initialize(*args)
17
+ end
18
+
19
+ def add(*args, &block)
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,32 @@
1
+ require 'eventmachine'
2
+
3
+
4
+ module SlackRTMReceiver
5
+
6
+ class Reactor
7
+
8
+ # Start reactor
9
+ # @param opts [Hash] options for Slack web API rtm.start
10
+ def self.run(opts)
11
+ logger = SlackRTMReceiver.logger
12
+ logger.info "SlackRTMReceiver ver. #{Version} loaded, Reactor starting..."
13
+ EM.run do
14
+ session = Session.new
15
+ starter = Starter.start(session, opts)
16
+
17
+ # life check
18
+ EM.add_periodic_timer(15) do
19
+ session.alive? ? session.ping_if_idle : starter.start(session)
20
+ end
21
+
22
+ # statistics check
23
+ EM.add_periodic_timer(3600) do
24
+ session.stats({log: true}) if session.alive?
25
+ end
26
+ end
27
+ logger.warn 'Reactor stopped'
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,199 @@
1
+ require 'faye/websocket'
2
+
3
+
4
+ module SlackRTMReceiver
5
+
6
+ # Websocket client for the RTM session
7
+ class Session
8
+
9
+ attr_reader :logger
10
+ attr_reader :websocket
11
+ alias_method :ws, :websocket
12
+ attr_reader :last_timestamp
13
+ alias_method :ts, :last_timestamp
14
+
15
+ def initialize
16
+ @logger = SlackRTMReceiver.logger
17
+ logger.debug 'Initializing Session...'
18
+ @websocket = nil
19
+ @last_timestamp = nil
20
+ @ping = nil
21
+ @stats = {}
22
+ end
23
+
24
+ # Start RTM websocket session
25
+ # @param url [String] websocket URL
26
+ # @return [Boolean] false if a session is already up
27
+ def start(url)
28
+ return false if ws
29
+ logger.debug "Connecting to websocket...\n URL: #{url}"
30
+ opts = {ping: 60}
31
+ @websocket = Faye::WebSocket::Client.new(url, nil, opts)
32
+
33
+ ws.on :open do
34
+ logger.debug 'Websocket opened'
35
+ touch_ts
36
+ end
37
+
38
+ ws.on :message do |ws_event|
39
+ touch_ts
40
+ event = JSON.parse(ws_event.data, {symbolize_names: true})
41
+ case event[:type]
42
+ when 'hello'
43
+ hello_handler(event)
44
+ when 'pong'
45
+ pong_handler(event)
46
+ else
47
+ run_event_handlers(event)
48
+ end
49
+ @stats[:events_received] ||= 0
50
+ @stats[:events_received] += 1
51
+ end
52
+
53
+ ws.on :close do |ws_event|
54
+ logger.warn 'RTM session closed'
55
+ cleanup
56
+ end
57
+
58
+ return true
59
+ end
60
+
61
+ # Send RTM event
62
+ # @param event [Hash] RTM event; 'id' will be added automatically
63
+ # @return [Hash] the event that was sent
64
+ def send_event(event)
65
+ return nil unless ws
66
+ event[:id] = new_event_id
67
+ ws.send(JSON.fast_generate(event))
68
+ logger.info "Event sent with id: #{event[:id]} type: #{event[:type]}"
69
+ logger.debug "Sent event: #{event}"
70
+ return event
71
+ end
72
+
73
+ # True if RTM session is alive
74
+ def alive?
75
+ # return true if ws && ts && Time.now - ts < 30
76
+ return true if ws && ts
77
+ return false
78
+ end
79
+
80
+ # Idle for how long?
81
+ # @return [Float] seconds
82
+ def idle_time
83
+ return 0 unless alive?
84
+ return Time.now - ts
85
+ end
86
+
87
+ # Send RTM ping
88
+ # @param timeout [Fixnum] timeout in seconds
89
+ # @return [Boolean] true if pinged
90
+ def ping(timeout = 5)
91
+ if @ping
92
+ logger.debug 'RTM ping requested, but another was recently sent. Ignoring...'
93
+ return false
94
+ end
95
+ event = send_event({type: 'ping', time: Time.now.to_f})
96
+ return false unless event
97
+ timer = EM::Timer.new(timeout) do
98
+ logger.warn "RTM ping timed out: threshold #{timeout} sec"
99
+ close
100
+ end
101
+ event[:em_timer] = timer
102
+ @ping = event
103
+ @stats[:pings_sent] ||= 0
104
+ @stats[:pings_sent] += 1
105
+ return true
106
+ end
107
+
108
+ # Ping if idle for more than set seconds
109
+ # @param sec [Fixnum] idle time in seconds
110
+ # @return [Boolean] true if pinged
111
+ def ping_if_idle(sec = 10)
112
+ return false if idle_time < sec
113
+ ping
114
+ end
115
+
116
+ # Return statistics
117
+ # @param opts [Hash]
118
+ # @return [Hash]
119
+ def stats(opts = {})
120
+ return nil if @stats.empty?
121
+ secs = Time.now - @stats[:hello_time]
122
+ secs = secs.to_i
123
+ days = secs / 86400
124
+ secs = secs % 86400
125
+ hours = secs / 3600
126
+ secs = secs % 3600
127
+ mins = secs / 60
128
+ secs = secs % 60
129
+ if opts[:log]
130
+ msg = "Statistics since #{@stats[:hello_time]} (#{days} days, #{hours} hrs, #{mins} mins, #{secs} secs)\n"
131
+ msg << "#{@stats}"
132
+ logger.info msg
133
+ end
134
+ return @stats
135
+ end
136
+
137
+ # Close RTM session
138
+ def close
139
+ ws.close if ws
140
+ end
141
+
142
+ private
143
+
144
+ # Handle RTM hello
145
+ def hello_handler(event)
146
+ @stats[:hello_time] = Time.now
147
+ logger.info 'Hello received, RTM session established'
148
+ end
149
+
150
+ # Handle RTM pong
151
+ def pong_handler(event)
152
+ @stats[:pongs_received] ||= 0
153
+ @stats[:pongs_received] += 1
154
+ if @ping.nil? || @ping[:id] != event[:reply_to]
155
+ logger.warn "Unexpected RTM pong received\n Pong: #{event}"
156
+ return
157
+ end
158
+ latency = Time.now.to_f - @ping[:time]
159
+ logger.info "RTM pong received, ping latency: #{'%.5f' % latency} sec"
160
+ @ping[:em_timer].cancel
161
+ @ping = nil
162
+ @stats[:last_ping_latency] = latency
163
+ end
164
+
165
+ # Run matching handlers
166
+ def run_event_handlers(event)
167
+ logger.debug "Received event: #{event}"
168
+ return unless event[:type]
169
+ handlers = SlackRTMReceiver.event_handlers
170
+ handlers.each do |handler|
171
+ handler.process_event(event, self) if handler.type == event[:type]
172
+ end
173
+ end
174
+
175
+ # Get new RTM event ID
176
+ def new_event_id
177
+ @stats[:events_sent] ||= 0
178
+ return @stats[:events_sent] += 1
179
+ end
180
+
181
+ # Update last timestamp
182
+ def touch_ts
183
+ @last_timestamp = Time.now
184
+ end
185
+
186
+ # Reset instance variables
187
+ def cleanup
188
+ @hellotime = 0
189
+ @websocket = nil
190
+ @last_timestamp = nil
191
+ @ping = nil
192
+ stats({log: true})
193
+ @stats = {}
194
+ logger.warn 'RTM session closed'
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -0,0 +1,76 @@
1
+ require 'em-http'
2
+ require 'json'
3
+
4
+
5
+ module SlackRTMReceiver
6
+
7
+ # HTTP client to call rtm.start
8
+ class Starter
9
+
10
+ # Create and start a Starter
11
+ # @param session [SlackRTMReceiver::Session]
12
+ # @return [SlackRTMReceiver::Starter]
13
+ def self.start(session, opts = {})
14
+ starter = self.new(opts)
15
+ starter.start(session)
16
+ return starter
17
+ end
18
+
19
+ attr_reader :logger
20
+ attr_accessor :options
21
+
22
+ # @return [Boolean]
23
+ def started?
24
+ return true if @is_starting
25
+ return true if @session && @session.alive?
26
+ return false
27
+ end
28
+
29
+ # @param opts [Hash] options for Slack web API rtm.start
30
+ def initialize(opts)
31
+ @logger = SlackRTMReceiver.logger
32
+ logger.debug 'Initializing Starter...'
33
+ @options = opts
34
+ @is_starting = false
35
+ @session = nil
36
+ end
37
+
38
+ # @param session [SlackRTMReceiver::Session]
39
+ def start(session)
40
+ if started?
41
+ logger.debug 'Start requested but already running. Ignoring...'
42
+ return nil
43
+ end
44
+ @is_starting = true
45
+ @session = session
46
+ logger.debug 'Starter is calling rtm.start...'
47
+ baseurl = 'https://slack.com/api/rtm.start'
48
+ http = EM::HttpRequest.new(baseurl).get(query: @options, redirects: 5)
49
+ http.callback do
50
+ callback(http, session)
51
+ @is_starting = false
52
+ end
53
+ http.errback do
54
+ errback(http)
55
+ @is_starting = false
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ # EM::HttpRequest callback handler
62
+ def callback(http, session)
63
+ status = http.response_header.status
64
+ raise "Web API rtm.start failed: received HTTP status code #{status}" unless status == 200
65
+ logger.info 'Recived rtm.start response'
66
+ session.start(JSON.parse(http.response)['url'])
67
+ end
68
+
69
+ # EM::HttpRequest error callback handler
70
+ def errback(http)
71
+ raise "Web API rtm.start failed: #{http.error}"
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,5 @@
1
+ module SlackRTMReceiver
2
+
3
+ Version = '0.0.1'
4
+
5
+ end
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
2
+ require 'slack-rtm-receiver/version'
3
+
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'slack-rtm-receiver'
7
+ s.version = SlackRTMReceiver::Version
8
+ s.authors = ['Ken J.']
9
+ s.email = ['kenjij@gmail.com']
10
+ s.description = %q{Slack RTM receiver}
11
+ s.summary = %q{Connects to Slack Real Time Messaging API to receive events.}
12
+ s.homepage = 'https://github.com/kenjij/slack-rtm-receiver'
13
+ s.license = 'MIT'
14
+
15
+ s.files = `git ls-files`.split($/)
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_runtime_dependency "eventmachine", "~> 1.0"
19
+ s.add_runtime_dependency "faye-websocket", "~> 0.8"
20
+ s.add_runtime_dependency "em-http-request", "~> 1.1"
21
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slack-rtm-receiver
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ken J.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faye-websocket
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: em-http-request
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ description: Slack RTM receiver
56
+ email:
57
+ - kenjij@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - README.md
64
+ - lib/slack-rtm-receiver.rb
65
+ - lib/slack-rtm-receiver/event-handler.rb
66
+ - lib/slack-rtm-receiver/logger.rb
67
+ - lib/slack-rtm-receiver/reactor.rb
68
+ - lib/slack-rtm-receiver/session.rb
69
+ - lib/slack-rtm-receiver/starter.rb
70
+ - lib/slack-rtm-receiver/version.rb
71
+ - slack-rtm-receiver.gemspec
72
+ homepage: https://github.com/kenjij/slack-rtm-receiver
73
+ licenses:
74
+ - MIT
75
+ metadata: {}
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 2.4.3
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: Connects to Slack Real Time Messaging API to receive events.
96
+ test_files: []