ferris 0.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 44f7b3f20a88bfe85335fca5585c9defa5d0f49f
4
+ data.tar.gz: ef9e9b221ab4401946e385b6e9ec6325fcad3c87
5
+ SHA512:
6
+ metadata.gz: e66d90192fe63c8c05216225a5dc15f9597895a1d4506a45cd2d4dceb8701bc7a7ae3c4559bc2fff95b593b02ed7b27a443e99e0ffb605c7a205791b40e74f03
7
+ data.tar.gz: 0fca2026e06abb0f329b565b57d84e658a979f347dd1a1624954b31764d16d57e87f0e4a4702d62a85d93becbf7a651138ae1432b556baed4d3186258601790f
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2015 Sean Clemmer and Blue Jeans Network
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any
4
+ purpose with or without fee is hereby granted, provided that the above
5
+ copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13
+ PERFORMANCE OF THIS SOFTWARE.
data/Readme.md ADDED
@@ -0,0 +1,45 @@
1
+ # Ferris
2
+
3
+ A simple Slack client for Ruby
4
+
5
+ ```ruby
6
+ #!/usr/bin/env ruby
7
+ require 'ferris'
8
+
9
+ TOKEN, CHANNEL = '...', '...'
10
+
11
+ # Interact with the Real-Time Messaging API
12
+ rtm = Ferris::RTM.new token: TOKEN
13
+
14
+ # Interact with the regular API methods
15
+ api = Ferris::API.new token: TOKEN
16
+
17
+ # Callback for messages from the RTM API
18
+ rtm.on :message do |m|
19
+ puts m
20
+ end
21
+
22
+ # Add as many callbacks as you like, they'll
23
+ # each get a copy of incoming messages
24
+ rtm.on :message do |m|
25
+ $stderr.puts message: m
26
+ end
27
+
28
+ rtm.go # Start the RTM API worker in the background
29
+
30
+ # The RTM API allows you to send basic messages
31
+ # See https://api.slack.com/rtm for docs
32
+ rtm.send 'message', \
33
+ channel: CHANNEL, text: 'Hello from the RTM API'
34
+
35
+ rtm.send 'ping' # Send "ping" and get back "pong"
36
+
37
+ # Use the regular API for advanced scenarios (e.g. attachments)
38
+ # See https://api.slack.com/methods for docs
39
+ api.send 'chat.postMessage', \
40
+ channel: CHANNEL, text: 'Hello from the regular API'
41
+
42
+ rtm.join # Bring the RTM API worker into the foreground
43
+
44
+ rtm.kill # We'll never reach here...
45
+ ```
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/lib/ferris.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'ferris/metadata'
2
+ require_relative 'ferris/api'
3
+ require_relative 'ferris/rtm'
data/lib/ferris/api.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'json'
2
+ require 'net/http'
3
+
4
+ require 'slog'
5
+
6
+ Thread.abort_on_exception = true
7
+
8
+
9
+ module Ferris
10
+ class API
11
+ def initialize options={}
12
+ @token = options.fetch :token
13
+ @logger = options.fetch :logger, Slog.new
14
+ @api_url = options.fetch :api_url, 'https://slack.com/api'
15
+ log.trace event: 'API client initialized'
16
+ end
17
+
18
+ def send method, options={}
19
+ uri = URI File.join(@api_url, method)
20
+ options = { token: @token }.merge(options)
21
+ log.trace event: 'sending api request', method: method, options: options
22
+ res = Net::HTTP.post_form uri, options
23
+ log.debug event: 'sent api request', method: method, options: options, response: res
24
+ JSON.parse res.body
25
+ end
26
+
27
+ private
28
+ def log ; @logger end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ module Ferris
2
+
3
+ # We use a VERSION file to tie into our build pipeline
4
+ VERSION = File.read(File.join(File.dirname(__FILE__), '..', '..', 'VERSION')).strip
5
+
6
+ # We don't really do all that much, be humble
7
+ SUMMARY = 'A simple Slack client for Ruby'
8
+
9
+ # Like the MIT license, but even simpler
10
+ LICENSE = 'ISC'
11
+
12
+ # Where you should look first
13
+ HOMEPAGE = 'https://github.com/sczizzo/ferris'
14
+
15
+ # Your benevolent dictator for life
16
+ AUTHOR = 'Sean Clemmer'
17
+
18
+ # Turn here to strangle your dictator
19
+ EMAIL = 'sclemmer@bluejeans.com'
20
+
21
+ # Bundled extensions
22
+ TRAVELING_RUBY_BUCKET = 'http://d6r77u77i8pq3.cloudfront.net'
23
+ TRAVELING_RUBY_VERSION = '20150715-2.2.2'
24
+ EM_VERSION = '1.0.4'
25
+
26
+ # Every project deserves its own ASCII art
27
+ ART = <<-'EOART' % VERSION
28
+
29
+
30
+ ***
31
+ ** *** *
32
+ ** *** ***
33
+ ** *
34
+ ** *** **** *** **** ****
35
+ ****** *** **** **** * **** **** * *** * **** *
36
+ ***** * *** ** **** ** **** *** ** ****
37
+ ** * *** ** ** ** ****
38
+ ** ** *** ** ** ** ***
39
+ ** ******** ** ** ** ***
40
+ ** ******* ** ** ** ***
41
+ ** ** ** ** ** **** **
42
+ ** **** * *** *** ** * **** *
43
+ ** ******* *** *** *** * ****
44
+ ** ***** ***
45
+ v%s
46
+
47
+ EOART
48
+ end
data/lib/ferris/rtm.rb ADDED
@@ -0,0 +1,177 @@
1
+ require 'json'
2
+ require 'socket'
3
+ require 'thread'
4
+ require 'net/http'
5
+
6
+ require 'slog'
7
+ require 'websocket/driver'
8
+
9
+ Thread.abort_on_exception = true
10
+
11
+
12
+ module Ferris
13
+ class RTM
14
+ HTTPS_PORT = 443
15
+ SOCKET_READ_SIZE = 1024
16
+
17
+ def initialize options={}
18
+ @id = 0
19
+ @jobs = []
20
+ @stopped = false
21
+ @callbacks = Hash.new { |h,k| h[k] = [] }
22
+ @q = Queue.new
23
+ @qlock = Mutex.new
24
+ @token = options.fetch :token
25
+ @api_url = options.fetch :api_url, 'https://slack.com/api'
26
+ @logger = options.fetch :logger, Slog.new
27
+ @wss_uri = construct_wss_uri @token, @api_url
28
+ log.trace event: 'RTM client initialized', wss_uri: @wss_uri.to_s
29
+ end
30
+
31
+
32
+ def on event, &block
33
+ unless [ :open, :close, :error, :message ].include? event
34
+ log.fatal event: 'invalid event', on_event: event
35
+ raise ArgumentError
36
+ end
37
+
38
+ log.debug event: 'adding callback', on_event: event
39
+ @callbacks[event] << block
40
+ end
41
+
42
+
43
+ def send type, message={}
44
+ log.debug event: 'sending message', message: message
45
+ message[:id] ||= next_id
46
+ message[:type] = type
47
+ enqueue message
48
+ nil
49
+ end
50
+
51
+
52
+ def go
53
+ log.trace event: 'connecting'
54
+ socket, driver = connect
55
+ log.debug event: 'connected'
56
+ @jobs << go_read_input(socket, driver)
57
+ @jobs << go_write_output(socket, driver)
58
+ @jobs
59
+ end
60
+
61
+
62
+ def kill ; @jobs.map(&:kill) end
63
+
64
+ def join ; @jobs.map(&:join) end
65
+
66
+
67
+
68
+ private
69
+
70
+ def log ; @logger end
71
+
72
+
73
+ def construct_wss_uri token, api_url
74
+ uri = URI File.join(api_url, 'rtm.start')
75
+ req = Net::HTTP.post_form uri, token: token
76
+ bod = JSON.parse req.body, symbolize_names: true
77
+ URI bod[:url]
78
+
79
+ rescue
80
+ x = { event: 'could not construct WSS URI' }
81
+ x[:error] = bod[:error] if bod && bod[:error]
82
+ log.fatal x
83
+ raise RuntimeError
84
+ end
85
+
86
+
87
+ def next_id
88
+ @qlock.synchronize { @id += 1 }
89
+ end
90
+
91
+
92
+ def enqueue message
93
+ @qlock.synchronize { @q << message }
94
+ end
95
+
96
+
97
+ def full_dequeue q=[]
98
+ nq = @qlock.synchronize { q << @q.shift until @q.empty? ; q }
99
+ nq
100
+ end
101
+
102
+
103
+ def wrapper socket
104
+ s = Struct.new :url, :socket do
105
+ def write *args
106
+ self.socket.write *args
107
+ end
108
+ end
109
+ s.new @wss_uri.to_s, socket
110
+ end
111
+
112
+
113
+ def connect
114
+ tcp_socket = TCPSocket.new @wss_uri.host, HTTPS_PORT
115
+ ssl_socket = OpenSSL::SSL::SSLSocket.new tcp_socket
116
+ ssl_socket.connect
117
+
118
+ driver = WebSocket::Driver.client wrapper(ssl_socket)
119
+
120
+ driver.on :open do |_|
121
+ log.trace event: 'driver open'
122
+ @callbacks[:open].map(&:call)
123
+ end
124
+
125
+ driver.on :close do |_|
126
+ log.trace event: 'driver close'
127
+ @callbacks[:close].map(&:call)
128
+ end
129
+
130
+ driver.on :error do |_|
131
+ log.trace event: 'driver error'
132
+ @callbacks[:error].map(&:call)
133
+ end
134
+
135
+ driver.on :message do |event|
136
+ log.trace event: 'driver message', message: event
137
+ data = JSON.parse event.data
138
+ @callbacks[:message].map { |c| c.call data }
139
+ end
140
+
141
+ driver.start
142
+ [ ssl_socket, driver ]
143
+ end
144
+
145
+
146
+ def go_read_input socket, driver
147
+ Thread.new do
148
+ loop do
149
+ log.trace event: 'reading socket'
150
+ data = socket.readpartial SOCKET_READ_SIZE
151
+ log.debug event: 'read socket', data_size: data.size
152
+ next if data.nil? || data.empty?
153
+ driver.parse data
154
+ end
155
+ end
156
+ end
157
+
158
+
159
+ def go_write_output socket, driver, delay=0.1
160
+ Thread.new do
161
+ loop do
162
+ q = full_dequeue
163
+
164
+ q.each do |m|
165
+ sent = driver.text JSON.generate(m)
166
+ unless sent
167
+ log.fatal event: 'could not send', message: m
168
+ raise RuntimeError
169
+ end
170
+ end
171
+
172
+ sleep delay if q.empty?
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ferris
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Sean Clemmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slog
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ - !ruby/object:Gem::Dependency
42
+ name: websocket-driver
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: eventmachine
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.0.4
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.4
69
+ description: A simple Slack client for Ruby.
70
+ email: sclemmer@bluejeans.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - LICENSE
76
+ - Readme.md
77
+ - VERSION
78
+ - lib/ferris.rb
79
+ - lib/ferris/api.rb
80
+ - lib/ferris/metadata.rb
81
+ - lib/ferris/rtm.rb
82
+ homepage: https://github.com/sczizzo/ferris
83
+ licenses:
84
+ - ISC
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.4.5.1
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: A simple Slack client for Ruby
106
+ test_files: []
107
+ has_rdoc: