tweetstream 1.0.5 → 1.1.0.rc1
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.
Potentially problematic release.
This version of tweetstream might be problematic. Click here for more details.
- data/.document +5 -0
- data/.gemtest +0 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.yardopts +4 -0
- data/Gemfile +2 -0
- data/Guardfile +10 -0
- data/{LICENSE → LICENSE.md} +1 -1
- data/README.md +223 -0
- data/{RELEASE_NOTES.rdoc → RELEASE_NOTES.md} +2 -1
- data/Rakefile +13 -54
- data/examples/oauth.rb +17 -0
- data/lib/tweetstream.rb +21 -0
- data/lib/tweetstream/client.rb +137 -79
- data/lib/tweetstream/configuration.rb +82 -0
- data/lib/tweetstream/daemon.rb +6 -2
- data/lib/tweetstream/version.rb +3 -0
- data/spec/spec_helper.rb +8 -7
- data/spec/tweetstream/client_spec.rb +124 -64
- data/spec/tweetstream/parser_spec.rb +25 -22
- data/spec/tweetstream_spec.rb +106 -0
- data/tweetstream.gemspec +35 -0
- metadata +131 -36
- data/Gemfile.lock +0 -56
- data/README.rdoc +0 -162
- data/VERSION +0 -1
- data/lib/tweetstream/parsers/active_support.rb +0 -11
- data/lib/tweetstream/parsers/json_gem.rb +0 -11
- data/lib/tweetstream/parsers/json_pure.rb +0 -11
- data/lib/tweetstream/parsers/yajl.rb +0 -11
- data/spec/spec.opts +0 -2
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'tweetstream/version'
|
3
|
+
|
4
|
+
module TweetStream
|
5
|
+
# Defines constants and methods related to configuration
|
6
|
+
module Configuration
|
7
|
+
# An array of valid keys in the options hash when configuring TweetStream.
|
8
|
+
VALID_OPTIONS_KEYS = [
|
9
|
+
:parser,
|
10
|
+
:username,
|
11
|
+
:password,
|
12
|
+
:user_agent,
|
13
|
+
:auth_method,
|
14
|
+
:consumer_key,
|
15
|
+
:consumer_secret,
|
16
|
+
:oauth_token,
|
17
|
+
:oauth_token_secret].freeze
|
18
|
+
|
19
|
+
# The parser that will be used to connect if none is set
|
20
|
+
DEFAULT_PARSER = MultiJson.default_engine
|
21
|
+
|
22
|
+
# By default, don't set a username
|
23
|
+
DEFAULT_USERNAME = nil
|
24
|
+
|
25
|
+
# By default, don't set a password
|
26
|
+
DEFAULT_PASSWORD = nil
|
27
|
+
|
28
|
+
# The user agent that will be sent to the API endpoint if none is set
|
29
|
+
DEFAULT_USER_AGENT = "TweetStream Ruby Gem #{TweetStream::VERSION}".freeze
|
30
|
+
|
31
|
+
# The default authentication method
|
32
|
+
DEFAULT_AUTH_METHOD = :oauth
|
33
|
+
|
34
|
+
VALID_FORMATS = [
|
35
|
+
:basic,
|
36
|
+
:oauth].freeze
|
37
|
+
|
38
|
+
# By default, don't set an application key
|
39
|
+
DEFAULT_CONSUMER_KEY = nil
|
40
|
+
|
41
|
+
# By default, don't set an application secret
|
42
|
+
DEFAULT_CONSUMER_SECRET = nil
|
43
|
+
|
44
|
+
# By default, don't set a user oauth token
|
45
|
+
DEFAULT_OAUTH_TOKEN = nil
|
46
|
+
|
47
|
+
# By default, don't set a user oauth secret
|
48
|
+
DEFAULT_OAUTH_TOKEN_SECRET = nil
|
49
|
+
|
50
|
+
# @private
|
51
|
+
attr_accessor *VALID_OPTIONS_KEYS
|
52
|
+
|
53
|
+
# When this module is extended, set all configuration options to their default values
|
54
|
+
def self.extended(base)
|
55
|
+
base.reset
|
56
|
+
end
|
57
|
+
|
58
|
+
# Convenience method to allow configuration options to be set in a block
|
59
|
+
def configure
|
60
|
+
yield self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Create a hash of options and their values
|
64
|
+
def options
|
65
|
+
Hash[VALID_OPTIONS_KEYS.map {|key| [key, send(key)] }]
|
66
|
+
end
|
67
|
+
|
68
|
+
# Reset all configuration options to defaults
|
69
|
+
def reset
|
70
|
+
self.parser = DEFAULT_PARSER
|
71
|
+
self.username = DEFAULT_USERNAME
|
72
|
+
self.password = DEFAULT_PASSWORD
|
73
|
+
self.user_agent = DEFAULT_USER_AGENT
|
74
|
+
self.auth_method = DEFAULT_AUTH_METHOD
|
75
|
+
self.consumer_key = DEFAULT_CONSUMER_KEY
|
76
|
+
self.consumer_secret = DEFAULT_CONSUMER_SECRET
|
77
|
+
self.oauth_token = DEFAULT_OAUTH_TOKEN
|
78
|
+
self.oauth_token_secret = DEFAULT_OAUTH_TOKEN_SECRET
|
79
|
+
self
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/tweetstream/daemon.rb
CHANGED
@@ -30,10 +30,14 @@ class TweetStream::Daemon < TweetStream::Client
|
|
30
30
|
@app_name = app_name
|
31
31
|
super(user, pass, parser)
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def start(path, query_parameters = {}, &block) #:nodoc:
|
35
|
+
# Because of a change in Ruvy 1.8.7 patchlevel 249, you cannot call anymore
|
36
|
+
# super inside a block. So I assign to a variable the base class method before
|
37
|
+
# the Daemons block begins.
|
38
|
+
startmethod = super.start
|
35
39
|
Daemons.run_proc(@app_name || 'tweetstream', :multiple => true) do
|
36
|
-
|
40
|
+
startmethod(path, query_parameters, &block)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start do
|
3
|
+
add_group 'Tweetstream', 'lib/tweetstream'
|
4
|
+
add_group 'Specs', 'spec'
|
5
|
+
end
|
6
|
+
|
1
7
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
8
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
9
|
|
4
|
-
require 'rubygems'
|
5
10
|
require 'tweetstream'
|
6
|
-
require '
|
7
|
-
require '
|
11
|
+
require 'rspec'
|
12
|
+
require 'rspec/autorun'
|
8
13
|
require 'yajl'
|
9
14
|
require 'json'
|
10
15
|
|
@@ -19,7 +24,3 @@ def sample_tweets
|
|
19
24
|
@tweets
|
20
25
|
end
|
21
26
|
end
|
22
|
-
|
23
|
-
Spec::Runner.configure do |config|
|
24
|
-
|
25
|
-
end
|
@@ -1,17 +1,16 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/../spec_helper'
|
2
2
|
|
3
3
|
describe TweetStream::Client do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
before(:each) do
|
5
|
+
TweetStream.configure do |config|
|
6
|
+
config.username = 'abc'
|
7
|
+
config.password = 'def'
|
8
|
+
config.auth_method = :basic
|
9
|
+
end
|
10
|
+
@client = TweetStream::Client.new
|
8
11
|
end
|
9
12
|
|
10
13
|
describe '#build_uri' do
|
11
|
-
before do
|
12
|
-
@client = TweetStream::Client.new('abc','def')
|
13
|
-
end
|
14
|
-
|
15
14
|
it 'should return a URI' do
|
16
15
|
@client.send(:build_uri, '').is_a?(URI).should be_true
|
17
16
|
end
|
@@ -26,10 +25,6 @@ describe TweetStream::Client do
|
|
26
25
|
end
|
27
26
|
|
28
27
|
describe '#build_post_body' do
|
29
|
-
before do
|
30
|
-
@client = TweetStream::Client.new('abc','def')
|
31
|
-
end
|
32
|
-
|
33
28
|
it 'should return a blank string if passed a nil value' do
|
34
29
|
@client.send(:build_post_body, nil).should == ''
|
35
30
|
end
|
@@ -53,71 +48,73 @@ describe TweetStream::Client do
|
|
53
48
|
|
54
49
|
describe '#start' do
|
55
50
|
before do
|
56
|
-
@stream = stub("Twitter::JSONStream",
|
51
|
+
@stream = stub("Twitter::JSONStream",
|
57
52
|
:connect => true,
|
58
|
-
:unbind => true,
|
59
|
-
:each_item => true,
|
53
|
+
:unbind => true,
|
54
|
+
:each_item => true,
|
60
55
|
:on_error => true,
|
61
56
|
:on_max_reconnects => true,
|
62
57
|
:connection_completed => true
|
63
58
|
)
|
64
59
|
EM.stub!(:run).and_yield
|
65
60
|
Twitter::JSONStream.stub!(:connect).and_return(@stream)
|
66
|
-
@client = TweetStream::Client.new('abc','def')
|
67
61
|
end
|
68
|
-
|
69
|
-
it 'should try to connect via a JSON stream' do
|
62
|
+
|
63
|
+
it 'should try to connect via a JSON stream with basic auth' do
|
70
64
|
Twitter::JSONStream.should_receive(:connect).with(
|
71
|
-
:auth => 'abc:def',
|
72
|
-
:content => 'track=monday',
|
73
65
|
:path => URI.parse('/1/statuses/filter.json'),
|
74
66
|
:method => 'POST',
|
75
|
-
:user_agent =>
|
67
|
+
:user_agent => TweetStream::Configuration::DEFAULT_USER_AGENT,
|
68
|
+
:on_inited => nil,
|
69
|
+
:filters => 'monday',
|
70
|
+
:params => {},
|
71
|
+
:ssl => true,
|
72
|
+
:auth => 'abc:def'
|
76
73
|
).and_return(@stream)
|
77
|
-
|
74
|
+
|
78
75
|
@client.track('monday')
|
79
76
|
end
|
80
|
-
|
77
|
+
|
81
78
|
describe '#each_item' do
|
82
79
|
it 'should call the appropriate parser' do
|
83
|
-
@client = TweetStream::Client.new
|
84
|
-
|
80
|
+
@client = TweetStream::Client.new
|
81
|
+
MultiJson.should_receive(:decode).and_return({})
|
85
82
|
@stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
|
86
83
|
@client.track('abc','def')
|
87
84
|
end
|
88
|
-
|
85
|
+
|
89
86
|
it 'should yield a TweetStream::Status' do
|
90
87
|
@stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
|
91
88
|
@client.track('abc'){|s| s.should be_kind_of(TweetStream::Status)}
|
92
89
|
end
|
93
|
-
|
90
|
+
|
94
91
|
it 'should also yield the client if a block with arity 2 is given' do
|
95
92
|
@stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
|
96
93
|
@client.track('abc'){|s,c| c.should == @client}
|
97
94
|
end
|
98
|
-
|
95
|
+
|
99
96
|
it 'should include the proper values' do
|
100
97
|
tweet = sample_tweets[0]
|
101
98
|
tweet[:id] = 123
|
102
99
|
tweet[:user][:screen_name] = 'monkey'
|
103
100
|
tweet[:text] = "Oo oo aa aa"
|
104
101
|
@stream.should_receive(:each_item).and_yield(tweet.to_json)
|
105
|
-
@client.track('abc') do |s|
|
102
|
+
@client.track('abc') do |s|
|
106
103
|
s[:id].should == 123
|
107
104
|
s.user.screen_name.should == 'monkey'
|
108
105
|
s.text.should == 'Oo oo aa aa'
|
109
106
|
end
|
110
107
|
end
|
111
|
-
|
108
|
+
|
112
109
|
it 'should call the on_delete if specified' do
|
113
110
|
delete = '{ "delete": { "status": { "id": 1234, "user_id": 3 } } }'
|
114
111
|
@stream.should_receive(:each_item).and_yield(delete)
|
115
|
-
@client.on_delete do |id, user_id|
|
112
|
+
@client.on_delete do |id, user_id|
|
116
113
|
id.should == 1234
|
117
114
|
user_id.should == 3
|
118
115
|
end.track('abc')
|
119
116
|
end
|
120
|
-
|
117
|
+
|
121
118
|
it 'should call the on_limit if specified' do
|
122
119
|
limit = '{ "limit": { "track": 1234 } }'
|
123
120
|
@stream.should_receive(:each_item).and_yield(limit)
|
@@ -125,15 +122,22 @@ describe TweetStream::Client do
|
|
125
122
|
track.should == 1234
|
126
123
|
end.track('abc')
|
127
124
|
end
|
128
|
-
|
125
|
+
|
129
126
|
it 'should call on_error if a non-hash response is received' do
|
130
127
|
@stream.should_receive(:each_item).and_yield('["favorited"]')
|
131
128
|
@client.on_error do |message|
|
132
129
|
message.should == 'Unexpected JSON object in stream: ["favorited"]'
|
133
130
|
end.track('abc')
|
134
131
|
end
|
132
|
+
|
133
|
+
it 'should call on_error if a json parse error occurs' do
|
134
|
+
@stream.should_receive(:each_item).and_yield("{'a_key':}")
|
135
|
+
@client.on_error do |message|
|
136
|
+
message.should == "MultiJson::DecodeError occured in stream: {'a_key':}"
|
137
|
+
end.track('abc')
|
138
|
+
end
|
135
139
|
end
|
136
|
-
|
140
|
+
|
137
141
|
describe '#on_error' do
|
138
142
|
it 'should pass the message on to the error block' do
|
139
143
|
@stream.should_receive(:on_error).and_yield('Uh oh')
|
@@ -142,7 +146,7 @@ describe TweetStream::Client do
|
|
142
146
|
end.track('abc')
|
143
147
|
end
|
144
148
|
end
|
145
|
-
|
149
|
+
|
146
150
|
describe '#on_max_reconnects' do
|
147
151
|
it 'should raise a ReconnectError' do
|
148
152
|
@stream.should_receive(:on_max_reconnects).and_yield(30, 20)
|
@@ -153,51 +157,52 @@ describe TweetStream::Client do
|
|
153
157
|
end
|
154
158
|
end
|
155
159
|
end
|
156
|
-
|
160
|
+
|
157
161
|
describe ' API methods' do
|
158
|
-
before do
|
159
|
-
@client = TweetStream::Client.new('abc','def')
|
160
|
-
end
|
161
|
-
|
162
162
|
%w(firehose retweet sample).each do |method|
|
163
163
|
it "##{method} should make a call to start with \"statuses/#{method}\"" do
|
164
164
|
@client.should_receive(:start).once.with('statuses/' + method, {})
|
165
165
|
@client.send(method)
|
166
166
|
end
|
167
167
|
end
|
168
|
-
|
168
|
+
|
169
169
|
it '#track should make a call to start with "statuses/filter" and a track query parameter' do
|
170
|
-
@client.should_receive(:start).once.with('statuses/filter', :track => 'test', :method => :post)
|
170
|
+
@client.should_receive(:start).once.with('statuses/filter', :track => ['test'], :method => :post)
|
171
171
|
@client.track('test')
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
it '#track should comma-join multiple arguments' do
|
175
|
-
@client.should_receive(:start).once.with('statuses/filter', :track => 'foo,bar,baz', :method => :post)
|
175
|
+
@client.should_receive(:start).once.with('statuses/filter', :track => ['foo', 'bar', 'baz'], :method => :post)
|
176
176
|
@client.track('foo', 'bar', 'baz')
|
177
177
|
end
|
178
|
-
|
178
|
+
|
179
|
+
it '#track should comma-join an array of arguments' do
|
180
|
+
@client.should_receive(:start).once.with('statuses/filter', :track => [['foo','bar','baz']], :method => :post)
|
181
|
+
@client.track(['foo','bar','baz'])
|
182
|
+
end
|
183
|
+
|
179
184
|
it '#follow should make a call to start with "statuses/filter" and a follow query parameter' do
|
180
|
-
@client.should_receive(:start).once.with('statuses/filter', :follow =>
|
185
|
+
@client.should_receive(:start).once.with('statuses/filter', :follow => [123], :method => :post)
|
181
186
|
@client.follow(123)
|
182
187
|
end
|
183
|
-
|
188
|
+
|
184
189
|
it '#follow should comma-join multiple arguments' do
|
185
|
-
@client.should_receive(:start).once.with('statuses/filter', :follow =>
|
190
|
+
@client.should_receive(:start).once.with('statuses/filter', :follow => [123,456], :method => :post)
|
186
191
|
@client.follow(123, 456)
|
187
192
|
end
|
188
|
-
|
193
|
+
|
189
194
|
it '#filter should make a call to "statuses/filter" with the query params provided' do
|
190
|
-
@client.should_receive(:start).once.with('statuses/filter', :follow =>
|
195
|
+
@client.should_receive(:start).once.with('statuses/filter', :follow => 123, :method => :post)
|
191
196
|
@client.filter(:follow => 123)
|
192
197
|
end
|
198
|
+
it '#filter should make a call to "statuses/filter" with the query params provided longitude/latitude pairs, separated by commas ' do
|
199
|
+
@client.should_receive(:start).once.with('statuses/filter', :locations => '-122.75,36.8,-121.75,37.8,-74,40,-73,41', :method => :post)
|
200
|
+
@client.filter(:locations => '-122.75,36.8,-121.75,37.8,-74,40,-73,41')
|
201
|
+
end
|
193
202
|
end
|
194
|
-
|
195
|
-
%w(on_delete on_limit).each do |proc_setter|
|
203
|
+
|
204
|
+
%w(on_delete on_limit on_inited).each do |proc_setter|
|
196
205
|
describe "##{proc_setter}" do
|
197
|
-
before do
|
198
|
-
@client = TweetStream::Client.new('abc','def')
|
199
|
-
end
|
200
|
-
|
201
206
|
it 'should set when a block is given' do
|
202
207
|
proc = Proc.new{|a,b| puts a }
|
203
208
|
@client.send(proc_setter, &proc)
|
@@ -207,27 +212,82 @@ describe TweetStream::Client do
|
|
207
212
|
end
|
208
213
|
|
209
214
|
describe '#track' do
|
210
|
-
before do
|
211
|
-
@client = TweetStream::Client.new('abc','def')
|
212
|
-
end
|
213
|
-
|
214
215
|
it 'should call #start with "statuses/filter" and the provided queries' do
|
215
|
-
@client.should_receive(:start).once.with('statuses/filter', :track => 'rock', :method => :post)
|
216
|
+
@client.should_receive(:start).once.with('statuses/filter', :track => ['rock'], :method => :post)
|
216
217
|
@client.track('rock')
|
217
218
|
end
|
218
219
|
end
|
219
220
|
|
221
|
+
describe '#locations' do
|
222
|
+
it 'should call #start with "statuses/filter" with the query params provided longitude/latitude pairs' do
|
223
|
+
@client.should_receive(:start).once.with('statuses/filter', :locations => ['-122.75,36.8,-121.75,37.8,-74,40,-73,41'], :method => :post)
|
224
|
+
@client.locations('-122.75,36.8,-121.75,37.8,-74,40,-73,41')
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should call #start with "statuses/filter" with the query params provided longitude/latitude pairs and additional filter' do
|
228
|
+
@client.should_receive(:start).once.with('statuses/filter', :locations => ['-122.75,36.8,-121.75,37.8,-74,40,-73,41'], :track => 'rock', :method => :post)
|
229
|
+
@client.locations('-122.75,36.8,-121.75,37.8,-74,40,-73,41', :track => 'rock')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
220
233
|
describe 'instance .stop' do
|
221
234
|
it 'should call EventMachine::stop_event_loop' do
|
222
235
|
EventMachine.should_receive :stop_event_loop
|
223
|
-
TweetStream::Client.new
|
236
|
+
TweetStream::Client.new.stop.should be_nil
|
224
237
|
end
|
225
|
-
|
238
|
+
|
226
239
|
it 'should return the last status yielded' do
|
227
240
|
EventMachine.should_receive :stop_event_loop
|
228
|
-
client = TweetStream::Client.new
|
241
|
+
client = TweetStream::Client.new
|
229
242
|
client.send(:instance_variable_set, :@last_status, {})
|
230
243
|
client.stop.should == {}
|
231
244
|
end
|
232
245
|
end
|
246
|
+
|
247
|
+
describe "oauth" do
|
248
|
+
describe '#start' do
|
249
|
+
before do
|
250
|
+
TweetStream.configure do |config|
|
251
|
+
config.consumer_key = '123456789'
|
252
|
+
config.consumer_secret = 'abcdefghijklmnopqrstuvwxyz'
|
253
|
+
config.oauth_token = '123456789'
|
254
|
+
config.oauth_token_secret = 'abcdefghijklmnopqrstuvwxyz'
|
255
|
+
config.auth_method = :oauth
|
256
|
+
end
|
257
|
+
@client = TweetStream::Client.new
|
258
|
+
|
259
|
+
@stream = stub("Twitter::JSONStream",
|
260
|
+
:connect => true,
|
261
|
+
:unbind => true,
|
262
|
+
:each_item => true,
|
263
|
+
:on_error => true,
|
264
|
+
:on_max_reconnects => true,
|
265
|
+
:connection_completed => true
|
266
|
+
)
|
267
|
+
EM.stub!(:run).and_yield
|
268
|
+
Twitter::JSONStream.stub!(:connect).and_return(@stream)
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'should try to connect via a JSON stream with oauth' do
|
272
|
+
Twitter::JSONStream.should_receive(:connect).with(
|
273
|
+
:path => URI.parse('/1/statuses/filter.json'),
|
274
|
+
:method => 'POST',
|
275
|
+
:user_agent => TweetStream::Configuration::DEFAULT_USER_AGENT,
|
276
|
+
:on_inited => nil,
|
277
|
+
:filters => 'monday',
|
278
|
+
:params => {},
|
279
|
+
:ssl => true,
|
280
|
+
:oauth => {
|
281
|
+
:consumer_key => '123456789',
|
282
|
+
:consumer_secret => 'abcdefghijklmnopqrstuvwxyz',
|
283
|
+
:access_key => '123456789',
|
284
|
+
:access_secret => 'abcdefghijklmnopqrstuvwxyz'
|
285
|
+
}
|
286
|
+
).and_return(@stream)
|
287
|
+
|
288
|
+
@client.track('monday')
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
233
293
|
end
|