sparks 0.3 → 0.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.
- data/CHANGELOG.md +9 -0
- data/Gemfile +3 -0
- data/lib/sparks.rb +145 -99
- data/lib/sparks/version.rb +2 -2
- data/sparks.gemspec +19 -18
- metadata +82 -66
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Sparks changelog
|
2
2
|
|
3
|
+
## Version 0.4 (20 January 2013)
|
4
|
+
|
5
|
+
- broke everything. sorry.
|
6
|
+
- streaming now works for more than a few minutes!
|
7
|
+
- handles both very small and very large JSON objects
|
8
|
+
- uses Yajl for streaming JSON parsing
|
9
|
+
- uses net-http-persistent to make repeated requests much faster
|
10
|
+
- removed Room and Campfire classes, now super...minimal. or something.
|
11
|
+
|
3
12
|
## Version 0.3 (7 October 2011)
|
4
13
|
|
5
14
|
- added support for play and tweet message types (@imajes)
|
data/Gemfile
ADDED
data/lib/sparks.rb
CHANGED
@@ -1,124 +1,170 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
3
|
-
require '
|
1
|
+
require 'logger'
|
2
|
+
require 'net/http/persistent'
|
3
|
+
require 'yajl'
|
4
4
|
|
5
5
|
# sparks, a tiny Campfire library
|
6
6
|
|
7
7
|
# Usage:
|
8
|
-
# c = Sparks
|
9
|
-
# r = c.
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
8
|
+
# c = Sparks.new('subdomain', 'abc123')
|
9
|
+
# r = c.room "Room Name"
|
10
|
+
# c.say r["id"], "hi there"
|
11
|
+
# c.paste r["id"], "class Foo\nend"
|
12
|
+
class Sparks
|
13
|
+
attr_reader :logger
|
14
|
+
|
15
|
+
def initialize subdomain, token, opts = {}
|
16
|
+
@base = URI("https://#{subdomain}.campfirenow.com")
|
17
|
+
@token = token
|
18
|
+
@logger = opts[:logger] || Logger.new(STDOUT)
|
19
|
+
@http = Net::HTTP::Persistent.new("sparks")
|
20
|
+
@http.ca_file = opts[:ca_file] if opts[:ca_file]
|
21
|
+
@rooms ||= {}
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
args.unshift(@id)
|
25
|
-
@api.send method, *args, &block
|
26
|
-
end
|
27
|
-
end
|
24
|
+
def me
|
25
|
+
user("me")
|
28
26
|
end
|
29
27
|
|
30
|
-
|
31
|
-
|
28
|
+
def user(id)
|
29
|
+
req("/users/#{id}")[:user]
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@pass = 'x'
|
32
|
+
def room_named(name)
|
33
|
+
req("/rooms")[:rooms].find{|r| r[:name] == name }
|
34
|
+
end
|
37
35
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
campfire_ca_certs = File.expand_path("../rapidssl.crt", __FILE__)
|
42
|
-
@http.ca_file = opts[:ca_file] || campfire_ca_certs
|
36
|
+
def room(id)
|
37
|
+
@rooms[id] ||= begin
|
38
|
+
req("/room/#{id.to_s}")[:room]
|
43
39
|
end
|
40
|
+
end
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
42
|
+
def join(id)
|
43
|
+
req("/room/#{id}/join", :post)
|
44
|
+
end
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
req['Content-Type'] = 'application/json'
|
54
|
-
req.basic_auth token, pass
|
55
|
-
begin
|
56
|
-
JSON.parse(http.request(req).body)["rooms"]
|
57
|
-
rescue JSON::ParserError
|
58
|
-
{}
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
46
|
+
def leave(id)
|
47
|
+
req("/room/#{id}/leave", :post)
|
48
|
+
end
|
62
49
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
50
|
+
def speak(id, message, type = 'TextMessage')
|
51
|
+
data = {'body' => message, 'type' => type}
|
52
|
+
json = Yajl::Encoder.encode('message' => data)
|
53
|
+
req("/room/#{id}/speak", json)
|
54
|
+
end
|
55
|
+
alias_method :say, :speak
|
67
56
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
req.basic_auth token, pass
|
72
|
-
http.request(req, json)
|
73
|
-
end
|
74
|
-
end
|
57
|
+
def paste(id, message)
|
58
|
+
speak id, message, 'PasteMessage'
|
59
|
+
end
|
75
60
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
alias_method :say, :speak
|
61
|
+
def play(id, message)
|
62
|
+
speak id, message, 'SoundMessage'
|
63
|
+
end
|
80
64
|
|
81
|
-
|
82
|
-
|
83
|
-
|
65
|
+
def tweet(id, message)
|
66
|
+
speak id, message, 'TweetMessage'
|
67
|
+
end
|
84
68
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
69
|
+
def watch(id)
|
70
|
+
# campfire won't let you stream until you've joined the room
|
71
|
+
join(id)
|
72
|
+
|
73
|
+
# don't allow retries if we've never connected before.
|
74
|
+
retries ||= nil
|
75
|
+
|
76
|
+
uri = URI("https://streaming.campfirenow.com") + "/room/#{id}/live.json"
|
77
|
+
logger.debug "Ready to stream from #{uri}"
|
78
|
+
|
79
|
+
request = Net::HTTP::Get.new(uri.path)
|
80
|
+
request.basic_auth @token, "x"
|
81
|
+
|
82
|
+
@http.request(uri, request) do |response|
|
83
|
+
logger.debug "Connected and streaming from room #{id}"
|
84
|
+
# connected! allow retries.
|
85
|
+
retries = 0
|
92
86
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
87
|
+
# Set up a Yajl stream parser
|
88
|
+
parser = Yajl::Parser.new(:symbolize_keys => true)
|
89
|
+
parser.on_parse_complete = -> hash { yield hash }
|
90
|
+
|
91
|
+
# Feed chunks into the stream parser
|
92
|
+
response.read_body do |chunk|
|
93
|
+
# Campfire keepalive pings
|
94
|
+
next if chunk == " "
|
95
|
+
parser << chunk
|
98
96
|
end
|
99
97
|
end
|
98
|
+
rescue Yajl::ParseError, # Bad JSON in the response
|
99
|
+
SystemCallError, # All Errno errors
|
100
|
+
SocketError, # Errors from socket operations
|
101
|
+
Net::HTTP::Persistent::Error, # Timeout, SSL, or connection error
|
102
|
+
Net::HTTPBadResponse, # response wasn't 2xx
|
103
|
+
Net::HTTPHeaderSyntaxError, # response header issue
|
104
|
+
Net::ProtocolError => e # not http
|
105
|
+
# pass through errors if we haven't ever connected
|
106
|
+
raise e unless retries
|
107
|
+
# if we connected at least once, try, try, again
|
108
|
+
retries += 1
|
109
|
+
logger.error "#{e.class}: #{e.message}"
|
110
|
+
logger.error "Trying to stream again in #{retries * 2}s"
|
111
|
+
sleep retries * 2
|
112
|
+
retry
|
113
|
+
end
|
100
114
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
req.basic_auth token, pass
|
111
|
-
http.request(req) do |res|
|
112
|
-
res.read_body do |chunk|
|
113
|
-
unless chunk.strip.empty?
|
114
|
-
chunk.split("\r").each do |message|
|
115
|
-
yield JSON.parse(message)
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
115
|
+
def req(uri, body = nil)
|
116
|
+
uri = @base + (uri + ".json") unless uri.is_a?(URI)
|
117
|
+
logger.debug "#{body ? 'POST' : 'GET'} #{uri}"
|
118
|
+
|
119
|
+
if body
|
120
|
+
request = Net::HTTP::Post.new(uri.path)
|
121
|
+
request.body = body unless body == :post
|
122
|
+
else
|
123
|
+
request = Net::HTTP::Get.new(uri.path)
|
121
124
|
end
|
125
|
+
request.content_type = "application/json"
|
126
|
+
request.basic_auth @token, "x"
|
127
|
+
|
128
|
+
retries ||= 0
|
129
|
+
response = @http.request(uri, request)
|
130
|
+
response.value # raises if response is not 2xx
|
131
|
+
parse_response(response)
|
132
|
+
|
133
|
+
rescue Net::HTTPRetriableError => e # response was 3xx
|
134
|
+
location = URI(response['location'])
|
135
|
+
logger.info "Request redirected to #{location}"
|
136
|
+
sleep 2
|
137
|
+
req(location, body)
|
138
|
+
|
139
|
+
rescue Net::HTTPServerException => e # response was 4xx
|
140
|
+
msg = "Authorization failed: HTTP #{response.code}"
|
141
|
+
msg << ": " << request.body if request.body && !request.body.empty?
|
142
|
+
raise msg
|
143
|
+
|
144
|
+
rescue SystemCallError, # All Errno errors
|
145
|
+
Net::HTTP::Persistent::Error, # Timeout, SSL, or connection error
|
146
|
+
Net::HTTPBadResponse, # response wasn't 2xx
|
147
|
+
Net::HTTPHeaderSyntaxError, # response header issue
|
148
|
+
Net::ProtocolError => e # not http
|
149
|
+
# Retry if something goes wrong
|
150
|
+
retries += 1
|
151
|
+
logger.info "Request failed: #{e.class}: #{e.message}"
|
152
|
+
logger.info "Going to retry request in #{retries * 2}s"
|
153
|
+
sleep retries * 2
|
154
|
+
retry
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
122
158
|
|
159
|
+
def parse_response(response)
|
160
|
+
if response.body.strip.empty?
|
161
|
+
true
|
162
|
+
else
|
163
|
+
Yajl::Parser.parse(response.body, :symbolize_keys => true)
|
164
|
+
end
|
165
|
+
rescue Yajl::ParseError
|
166
|
+
logger.debug "Couldn't parse #{res.inspect}: #{res.body.inspect}"
|
167
|
+
{}
|
123
168
|
end
|
169
|
+
|
124
170
|
end
|
data/lib/sparks/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "0.
|
1
|
+
class Sparks
|
2
|
+
VERSION = "0.4"
|
3
3
|
end
|
data/sparks.gemspec
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
3
|
require "sparks/version"
|
4
4
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
s.description = %q{A tiny Campfire client API that only uses the standard library}
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "sparks"
|
7
|
+
gem.version = Sparks::VERSION
|
8
|
+
gem.summary = %q{A tiny Campfire client API}
|
9
|
+
gem.description = %q{Yet another Campfire client. Because oh my god so many dependencies.}
|
10
|
+
gem.authors = ["André Arko"]
|
11
|
+
gem.email = ["andre@arko.net"]
|
12
|
+
gem.homepage = "http://github.com/indirect/sparks"
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
gem.add_dependency "yajl-ruby", "~> 1.1"
|
20
|
+
gem.add_dependency "net-http-persistent", "~> 2.8"
|
21
|
+
|
22
|
+
gem.add_development_dependency "rake", "~> 10.0"
|
23
|
+
gem.add_development_dependency "bundler", "~> 1.2"
|
23
24
|
end
|
metadata
CHANGED
@@ -1,102 +1,118 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: sparks
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 3
|
9
|
-
version: "0.3"
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.4'
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
12
|
-
-
|
7
|
+
authors:
|
8
|
+
- André Arko
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
date: 2013-04-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: yajl-ruby
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.1'
|
22
|
+
type: :runtime
|
22
23
|
prerelease: false
|
23
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.1'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: net-http-persistent
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
24
33
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
segments:
|
30
|
-
- 0
|
31
|
-
version: "0"
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.8'
|
32
38
|
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: bundler
|
36
39
|
prerelease: false
|
37
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '2.8'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '10.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bundler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
38
65
|
none: false
|
39
|
-
requirements:
|
66
|
+
requirements:
|
40
67
|
- - ~>
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
|
43
|
-
segments:
|
44
|
-
- 1
|
45
|
-
- 0
|
46
|
-
version: "1.0"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.2'
|
47
70
|
type: :development
|
48
|
-
|
49
|
-
|
50
|
-
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.2'
|
78
|
+
description: Yet another Campfire client. Because oh my god so many dependencies.
|
79
|
+
email:
|
51
80
|
- andre@arko.net
|
52
81
|
executables: []
|
53
|
-
|
54
82
|
extensions: []
|
55
|
-
|
56
83
|
extra_rdoc_files: []
|
57
|
-
|
58
|
-
files:
|
84
|
+
files:
|
59
85
|
- .gitignore
|
60
86
|
- CHANGELOG.md
|
87
|
+
- Gemfile
|
61
88
|
- README.md
|
62
89
|
- Rakefile
|
63
90
|
- lib/rapidssl.crt
|
64
91
|
- lib/sparks.rb
|
65
92
|
- lib/sparks/version.rb
|
66
93
|
- sparks.gemspec
|
67
|
-
has_rdoc: true
|
68
94
|
homepage: http://github.com/indirect/sparks
|
69
95
|
licenses: []
|
70
|
-
|
71
96
|
post_install_message:
|
72
97
|
rdoc_options: []
|
73
|
-
|
74
|
-
require_paths:
|
98
|
+
require_paths:
|
75
99
|
- lib
|
76
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
101
|
none: false
|
78
|
-
requirements:
|
79
|
-
- -
|
80
|
-
- !ruby/object:Gem::Version
|
81
|
-
|
82
|
-
|
83
|
-
- 0
|
84
|
-
version: "0"
|
85
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
107
|
none: false
|
87
|
-
requirements:
|
88
|
-
- -
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
|
91
|
-
segments:
|
92
|
-
- 0
|
93
|
-
version: "0"
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
94
112
|
requirements: []
|
95
|
-
|
96
|
-
|
97
|
-
rubygems_version: 1.3.7
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.8.23
|
98
115
|
signing_key:
|
99
116
|
specification_version: 3
|
100
117
|
summary: A tiny Campfire client API
|
101
118
|
test_files: []
|
102
|
-
|