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 +7 -0
- data/LICENSE +13 -0
- data/Readme.md +45 -0
- data/VERSION +1 -0
- data/lib/ferris.rb +3 -0
- data/lib/ferris/api.rb +30 -0
- data/lib/ferris/metadata.rb +48 -0
- data/lib/ferris/rtm.rb +177 -0
- metadata +107 -0
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
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:
|