tweetstream 1.0.5 → 1.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
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
data/.document
ADDED
data/.gemtest
ADDED
File without changes
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec/" }
|
8
|
+
watch('spec/data/.+') { "spec/" }
|
9
|
+
end
|
10
|
+
|
data/{LICENSE → LICENSE.md}
RENAMED
data/README.md
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
TweetStream
|
2
|
+
===========
|
3
|
+
|
4
|
+
TweetStream provides simple Ruby access to [Twitter's Streaming API](http://developer.twitter.com/pages/streaming_api).
|
5
|
+
|
6
|
+
Installation
|
7
|
+
------------
|
8
|
+
|
9
|
+
To install:
|
10
|
+
|
11
|
+
gem install tweetstream
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
|
16
|
+
Using TweetStream is quite simple:
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'tweetstream'
|
20
|
+
|
21
|
+
TweetStream.configure do |config|
|
22
|
+
config.consumer_key = 'abcdefghijklmnopqrstuvwxyz'
|
23
|
+
config.consumer_secret = '0123456789'
|
24
|
+
config.oauth_token = 'abcdefghijklmnopqrstuvwxyz'
|
25
|
+
config.oauth_token_secret = '0123456789'
|
26
|
+
config.auth_method = :oauth
|
27
|
+
config.parser = :yajl
|
28
|
+
end
|
29
|
+
|
30
|
+
# This will pull a sample of all tweets based on
|
31
|
+
# your Twitter account's Streaming API role.
|
32
|
+
TweetStream::Client.new.sample do |status|
|
33
|
+
# The status object is a special Hash with
|
34
|
+
# method access to its keys.
|
35
|
+
puts "#{status.text}"
|
36
|
+
end
|
37
|
+
|
38
|
+
You can also use it to track keywords or follow a given set of
|
39
|
+
user ids:
|
40
|
+
|
41
|
+
# Use 'track' to track a list of single-word keywords
|
42
|
+
TweetStream::Client.new.track('term1', 'term2') do |status|
|
43
|
+
puts "#{status.text}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Use 'follow' to follow a group of user ids (integers, not screen names)
|
47
|
+
TweetStream::Client.new.follow(14252, 53235) do |status|
|
48
|
+
puts "#{status.text}"
|
49
|
+
end
|
50
|
+
|
51
|
+
The methods available to TweetStream::Client will be kept in parity
|
52
|
+
with the methods available on the Streaming API wiki page.
|
53
|
+
|
54
|
+
Configuration and Changes in 1.1.0
|
55
|
+
----------------------------------
|
56
|
+
|
57
|
+
As of version 1.1.0.rc1 TweetStream supports OAuth. Please note that in order
|
58
|
+
to support OAuth, the `TweetStream::Client` initializer no longer accepts a
|
59
|
+
username/password. `TweetStream::Client` now accepts a hash:
|
60
|
+
|
61
|
+
TweetStream::Client.new(:username => 'you', :password => 'pass')
|
62
|
+
|
63
|
+
Alternatively, you can configure TweetStream via the configure method:
|
64
|
+
|
65
|
+
TweetStream.configure do |config|
|
66
|
+
config.consumer_key = 'cVcIw5zoLFE2a4BdDsmmA'
|
67
|
+
config.consumer_secret = 'yYgVgvTT9uCFAi2IuscbYTCqwJZ1sdQxzISvLhNWUA'
|
68
|
+
config.oauth_token = '4618-H3gU7mjDQ7MtFkAwHhCqD91Cp4RqDTp1AKwGzpHGL3I'
|
69
|
+
config.oauth_token_secret = 'xmc9kFgOXpMdQ590Tho2gV7fE71v5OmBrX8qPGh7Y'
|
70
|
+
config.auth_method = :oauth
|
71
|
+
config.parser = :yajl
|
72
|
+
end
|
73
|
+
|
74
|
+
If you are using Basic Auth:
|
75
|
+
|
76
|
+
TweetStream.configure do |config|
|
77
|
+
config.username = 'username'
|
78
|
+
config.password = 'password'
|
79
|
+
config.auth_method = :basic
|
80
|
+
config.parser = :yajl
|
81
|
+
end
|
82
|
+
|
83
|
+
TweetStream assumes OAuth by default. If you are using Basic Auth, it is recommended
|
84
|
+
that you update your code to use OAuth as Twitter is likely to phase out Basic Auth
|
85
|
+
support.
|
86
|
+
|
87
|
+
Swappable JSON Parsing
|
88
|
+
----------------------
|
89
|
+
|
90
|
+
As of version 1.1, TweetStream supports swappable JSON backends via MultiJson. You can
|
91
|
+
specify a parser during configuration:
|
92
|
+
|
93
|
+
# Parse tweets using Yajl-Ruby
|
94
|
+
TweetStream.configure do |config|
|
95
|
+
..
|
96
|
+
config.parser = :yajl
|
97
|
+
end.
|
98
|
+
|
99
|
+
Available options are `:yajl`, `:json_gem` (default),
|
100
|
+
`:json_pure`, and `:active_support`.
|
101
|
+
|
102
|
+
Handling Deletes and Rate Limitations
|
103
|
+
-------------------------------------
|
104
|
+
|
105
|
+
Sometimes the Streaming API will send messages other than statuses.
|
106
|
+
Specifically, it does so when a status is deleted or rate limitations
|
107
|
+
have caused some tweets not to appear in the stream. To handle these,
|
108
|
+
you can use the on_delete and on_limit methods. Example:
|
109
|
+
|
110
|
+
@client = TweetStream::Client.new
|
111
|
+
|
112
|
+
@client.on_delete do |status_id, user_id|
|
113
|
+
Tweet.delete(status_id)
|
114
|
+
end
|
115
|
+
|
116
|
+
@client.on_limit do |skip_count|
|
117
|
+
# do something
|
118
|
+
end
|
119
|
+
|
120
|
+
@client.track('intridea')
|
121
|
+
|
122
|
+
The on_delete and on_limit methods can also be chained, like so:
|
123
|
+
|
124
|
+
TweetStream::Client.new.on_delete{ |status_id, user_id|
|
125
|
+
Tweet.delete(status_id)
|
126
|
+
}.on_limit { |skip_count|
|
127
|
+
# do something
|
128
|
+
}.track('intridea') do |status|
|
129
|
+
# do something with the status like normal
|
130
|
+
end
|
131
|
+
|
132
|
+
You can also provide `:delete` and/or `:limit`
|
133
|
+
options when you make your method call:
|
134
|
+
|
135
|
+
TweetStream::Client.new.track('intridea',
|
136
|
+
:delete => Proc.new{ |status_id, user_id| # do something },
|
137
|
+
:limit => Proc.new{ |skip_count| # do something }
|
138
|
+
) do |status|
|
139
|
+
# do something with the status like normal
|
140
|
+
end
|
141
|
+
|
142
|
+
Twitter recommends honoring deletions as quickly as possible, and
|
143
|
+
you would likely be wise to integrate this functionality into your
|
144
|
+
application.
|
145
|
+
|
146
|
+
Errors and Reconnecting
|
147
|
+
-----------------------
|
148
|
+
|
149
|
+
TweetStream uses EventMachine to connect to the Twitter Streaming
|
150
|
+
API, and attempts to honor Twitter's guidelines in terms of automatic
|
151
|
+
reconnection. When Twitter becomes unavailable, the block specified
|
152
|
+
by you in `on_error` will be called. Note that this does not
|
153
|
+
indicate something is actually wrong, just that Twitter is momentarily
|
154
|
+
down. It could be for routine maintenance, etc.
|
155
|
+
|
156
|
+
TweetStream::Client.new.on_error do |message|
|
157
|
+
# Log your error message somewhere
|
158
|
+
end.track('term') do |status|
|
159
|
+
# Do things when nothing's wrong
|
160
|
+
end
|
161
|
+
|
162
|
+
However, if the maximum number of reconnect attempts has been reached,
|
163
|
+
TweetStream will raise a `TweetStream::ReconnectError` with
|
164
|
+
information about the timeout and number of retries attempted.
|
165
|
+
|
166
|
+
Terminating a TweetStream
|
167
|
+
-------------------------
|
168
|
+
|
169
|
+
It is often the case that you will need to change the parameters of your
|
170
|
+
track or follow tweet streams. In the case that you need to terminate
|
171
|
+
a stream, you may add a second argument to your block that will yield
|
172
|
+
the client itself:
|
173
|
+
|
174
|
+
# Stop after collecting 10 statuses
|
175
|
+
@statuses = []
|
176
|
+
TweetStream::Client.new.sample do |status, client|
|
177
|
+
@statuses << status
|
178
|
+
client.stop if @statuses.size >= 10
|
179
|
+
end
|
180
|
+
|
181
|
+
When `stop` is called, TweetStream will return from the block
|
182
|
+
the last successfully yielded status, allowing you to make note of
|
183
|
+
it in your application as necessary.
|
184
|
+
|
185
|
+
Daemonizing
|
186
|
+
-----------
|
187
|
+
|
188
|
+
It is also possible to create a daemonized script quite easily
|
189
|
+
using the TweetStream library:
|
190
|
+
|
191
|
+
# The third argument is an optional process name
|
192
|
+
TweetStream::Daemon.new('username','password', 'tracker').track('term1', 'term2') do |status|
|
193
|
+
# do something in the background
|
194
|
+
end
|
195
|
+
|
196
|
+
If you put the above into a script and run the script with `ruby scriptname.rb`, you will see a list of daemonization commands such
|
197
|
+
as start, stop, and run.
|
198
|
+
|
199
|
+
TODO
|
200
|
+
----
|
201
|
+
|
202
|
+
* UserStream support
|
203
|
+
* SiteStream support
|
204
|
+
|
205
|
+
Note on Patches/Pull Requests
|
206
|
+
-----------------------------
|
207
|
+
|
208
|
+
* Fork the project.
|
209
|
+
* Make your feature addition or bug fix.
|
210
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
211
|
+
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
212
|
+
* Send me a pull request. Bonus points for topic branches.
|
213
|
+
|
214
|
+
Contributors
|
215
|
+
------------
|
216
|
+
|
217
|
+
* Michael Bleigh (initial gem)
|
218
|
+
* Steve Agalloco (current maintainer)
|
219
|
+
|
220
|
+
Copyright
|
221
|
+
---------
|
222
|
+
|
223
|
+
Copyright (c) 2011 Intridea, Inc. (http://www.intridea.com/). See [LICENSE](https://github.com/intridea/tweetstream/blob/master/LICENSE.md) for details.
|
data/Rakefile
CHANGED
@@ -1,60 +1,19 @@
|
|
1
|
-
require '
|
2
|
-
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name = "tweetstream"
|
8
|
-
gem.summary = %Q{TweetStream is a simple wrapper for consuming the Twitter Streaming API.}
|
9
|
-
gem.description = %Q{TweetStream allows you to easily consume the Twitter Streaming API utilizing the YAJL Ruby gem.}
|
10
|
-
gem.email = ["michael@intridea.com", "steve.agalloco@gmail.com"]
|
11
|
-
gem.homepage = "http://github.com/intridea/tweetstream"
|
12
|
-
gem.authors = ["Michael Bleigh", "Steve Agalloco"]
|
13
|
-
gem.files = FileList["[A-Z]*", "{lib,spec,examples}/**/*"] - FileList["**/*.log"]
|
14
|
-
gem.add_development_dependency "rspec"
|
15
|
-
gem.add_dependency 'twitter-stream'
|
16
|
-
gem.add_dependency 'daemons'
|
17
|
-
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
-
end
|
19
|
-
Jeweler::GemcutterTasks.new
|
20
|
-
rescue LoadError
|
21
|
-
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
22
|
-
end
|
23
|
-
|
24
|
-
namespace :release do
|
25
|
-
%w(patch minor major).each do |level|
|
26
|
-
desc "Tag a #{level} version and push it to Gemcutter."
|
27
|
-
task level.to_sym => %w(version:bump:patch release gemcutter:release)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
require 'spec/rake/spectask'
|
32
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
33
|
-
spec.libs << 'lib' << 'spec'
|
34
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
35
|
-
end
|
36
|
-
|
37
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
38
|
-
spec.libs << 'lib' << 'spec'
|
39
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
40
|
-
spec.rcov = true
|
41
|
-
spec.rcov_opts = %w{--exclude "spec\/*,gems\/*"}
|
42
|
-
end
|
43
|
-
|
44
|
-
task :spec => :check_dependencies
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
45
6
|
|
7
|
+
task :test => :spec
|
46
8
|
task :default => :spec
|
47
9
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
10
|
+
namespace :doc do
|
11
|
+
require 'yard'
|
12
|
+
YARD::Rake::YardocTask.new do |task|
|
13
|
+
task.files = ['RELEASE_NOTES.md', 'LICENSE.md', 'lib/**/*.rb']
|
14
|
+
task.options = [
|
15
|
+
'--output-dir', 'doc/yard',
|
16
|
+
'--markup', 'markdown',
|
17
|
+
]
|
54
18
|
end
|
55
|
-
|
56
|
-
rdoc.rdoc_dir = 'rdoc'
|
57
|
-
rdoc.title = "tweetstream #{version}"
|
58
|
-
rdoc.rdoc_files.include('README*')
|
59
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
60
19
|
end
|
data/examples/oauth.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'yajl'
|
2
|
+
require 'tweetstream'
|
3
|
+
|
4
|
+
TweetStream.configure do |config|
|
5
|
+
config.consumer_key = 'abcdefghijklmnopqrstuvwxyz'
|
6
|
+
config.consumer_secret = '0123456789'
|
7
|
+
config.oauth_token = 'abcdefghijklmnopqrstuvwxyz'
|
8
|
+
config.oauth_token_secret = '0123456789'
|
9
|
+
config.auth_method = :oauth
|
10
|
+
config.parser = :yajl
|
11
|
+
end
|
12
|
+
|
13
|
+
TweetStream::Client.new.on_error do |message|
|
14
|
+
puts message
|
15
|
+
end.track("yankees") do |status|
|
16
|
+
puts "#{status.text}"
|
17
|
+
end
|
data/lib/tweetstream.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'tweetstream/configuration'
|
1
2
|
require 'tweetstream/client'
|
2
3
|
require 'tweetstream/hash'
|
3
4
|
require 'tweetstream/status'
|
@@ -5,6 +6,8 @@ require 'tweetstream/user'
|
|
5
6
|
require 'tweetstream/daemon'
|
6
7
|
|
7
8
|
module TweetStream
|
9
|
+
extend Configuration
|
10
|
+
|
8
11
|
class Terminated < ::StandardError; end
|
9
12
|
class Error < ::StandardError; end
|
10
13
|
class ConnectionError < TweetStream::Error; end
|
@@ -18,4 +21,22 @@ module TweetStream
|
|
18
21
|
super("Failed to reconnect after #{retries} tries.")
|
19
22
|
end
|
20
23
|
end
|
24
|
+
|
25
|
+
# Alias for TweetStream::Client.new
|
26
|
+
#
|
27
|
+
# @return [TweetStream::Client]
|
28
|
+
def self.client(options={})
|
29
|
+
TweetStream::Client.new(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Delegate to TweetStream::Client
|
33
|
+
def self.method_missing(method, *args, &block)
|
34
|
+
return super unless client.respond_to?(method)
|
35
|
+
client.send(method, *args, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Delegate to TweetStream::Client
|
39
|
+
def self.respond_to?(method)
|
40
|
+
client.respond_to?(method) || super
|
41
|
+
end
|
21
42
|
end
|
data/lib/tweetstream/client.rb
CHANGED
@@ -2,7 +2,7 @@ require 'uri'
|
|
2
2
|
require 'cgi'
|
3
3
|
require 'eventmachine'
|
4
4
|
require 'twitter/json_stream'
|
5
|
-
require '
|
5
|
+
require 'multi_json'
|
6
6
|
|
7
7
|
module TweetStream
|
8
8
|
# Provides simple access to the Twitter Streaming API (http://apiwiki.twitter.com/Streaming-API-Documentation)
|
@@ -23,38 +23,41 @@ module TweetStream
|
|
23
23
|
attr_accessor :username, :password
|
24
24
|
attr_reader :parser
|
25
25
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def parser=(parser)
|
36
|
-
@parser = parser_from(parser)
|
26
|
+
# @private
|
27
|
+
attr_accessor *Configuration::VALID_OPTIONS_KEYS
|
28
|
+
|
29
|
+
# Creates a new API
|
30
|
+
def initialize(options={})
|
31
|
+
options = TweetStream.options.merge(options)
|
32
|
+
Configuration::VALID_OPTIONS_KEYS.each do |key|
|
33
|
+
send("#{key}=", options[key])
|
34
|
+
end
|
37
35
|
end
|
38
|
-
|
36
|
+
|
37
|
+
# Get the JSON parser class for this client.
|
38
|
+
def json_parser
|
39
|
+
parser_from(parser)
|
40
|
+
end
|
41
|
+
|
39
42
|
# Create a new client with the Twitter credentials
|
40
43
|
# of the account you want to be using its API quota.
|
41
44
|
# You may also set the JSON parsing library as specified
|
42
45
|
# in the #parser= setter.
|
43
|
-
def initialize(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
|
46
|
+
# def initialize(ouser, pass, parser = :json_gem)
|
47
|
+
# self.username = user
|
48
|
+
# self.password = pass
|
49
|
+
# self.parser = parser
|
50
|
+
# end
|
51
|
+
|
49
52
|
# Returns all public statuses. The Firehose is not a generally
|
50
|
-
# available resource. Few applications require this level of access.
|
51
|
-
# Creative use of a combination of other resources and various access
|
52
|
-
# levels can satisfy nearly every application use case.
|
53
|
+
# available resource. Few applications require this level of access.
|
54
|
+
# Creative use of a combination of other resources and various access
|
55
|
+
# levels can satisfy nearly every application use case.
|
53
56
|
def firehose(query_parameters = {}, &block)
|
54
57
|
start('statuses/firehose', query_parameters, &block)
|
55
58
|
end
|
56
|
-
|
57
|
-
# Returns all retweets. The retweet stream is not a generally available
|
59
|
+
|
60
|
+
# Returns all retweets. The retweet stream is not a generally available
|
58
61
|
# resource. Few applications require this level of access. Creative
|
59
62
|
# use of a combination of other resources and various access levels
|
60
63
|
# can satisfy nearly every application use case. As of 9/11/2009,
|
@@ -63,8 +66,8 @@ module TweetStream
|
|
63
66
|
def retweet(query_parameters = {}, &block)
|
64
67
|
start('statuses/retweet', query_parameters, &block)
|
65
68
|
end
|
66
|
-
|
67
|
-
# Returns a random sample of all public statuses. The default access level
|
69
|
+
|
70
|
+
# Returns a random sample of all public statuses. The default access level
|
68
71
|
# provides a small proportion of the Firehose. The "Gardenhose" access
|
69
72
|
# level provides a proportion more suitable for data mining and
|
70
73
|
# research applications that desire a larger proportion to be statistically
|
@@ -73,11 +76,11 @@ module TweetStream
|
|
73
76
|
start('statuses/sample', query_parameters, &block)
|
74
77
|
end
|
75
78
|
|
76
|
-
# Specify keywords to track. Queries are subject to Track Limitations,
|
77
|
-
# described in Track Limiting and subject to access roles, described in
|
78
|
-
# the statuses/filter method. Track keywords are case-insensitive logical
|
79
|
+
# Specify keywords to track. Queries are subject to Track Limitations,
|
80
|
+
# described in Track Limiting and subject to access roles, described in
|
81
|
+
# the statuses/filter method. Track keywords are case-insensitive logical
|
79
82
|
# ORs. Terms are exact-matched, and also exact-matched ignoring
|
80
|
-
# punctuation. Phrases, keywords with spaces, are not supported.
|
83
|
+
# punctuation. Phrases, keywords with spaces, are not supported.
|
81
84
|
# Keywords containing punctuation will only exact match tokens.
|
82
85
|
# Query parameters may be passed as the last argument.
|
83
86
|
def track(*keywords, &block)
|
@@ -86,8 +89,8 @@ module TweetStream
|
|
86
89
|
filter(query_params.merge(:track => keywords), &block)
|
87
90
|
end
|
88
91
|
|
89
|
-
# Returns public statuses from or in reply to a set of users. Mentions
|
90
|
-
# ("Hello @user!") and implicit replies ("@user Hello!" created without
|
92
|
+
# Returns public statuses from or in reply to a set of users. Mentions
|
93
|
+
# ("Hello @user!") and implicit replies ("@user Hello!" created without
|
91
94
|
# pressing the reply "swoosh") are not matched. Requires integer user
|
92
95
|
# IDs, not screen names. Query parameters may be passed as the last argument.
|
93
96
|
def follow(*user_ids, &block)
|
@@ -95,20 +98,27 @@ module TweetStream
|
|
95
98
|
query_params ||= {}
|
96
99
|
filter(query_params.merge(:follow => user_ids), &block)
|
97
100
|
end
|
98
|
-
|
101
|
+
|
102
|
+
# Specifies a set of bounding boxes to track. Only tweets that are both created
|
103
|
+
# using the Geotagging API and are placed from within a tracked bounding box will
|
104
|
+
# be included in the stream – the user’s location field is not used to filter tweets
|
105
|
+
# (e.g. if a user has their location set to “San Francisco”, but the tweet was not created
|
106
|
+
# using the Geotagging API and has no geo element, it will not be included in the stream).
|
107
|
+
# Bounding boxes are specified as a comma separate list of longitude/latitude pairs, with
|
108
|
+
# the first pair denoting the southwest corner of the box
|
109
|
+
# longitude/latitude pairs, separated by commas. The first pair specifies the southwest corner of the box.
|
110
|
+
def locations(*locations_map, &block)
|
111
|
+
query_params = locations_map.pop if locations_map.last.is_a?(::Hash)
|
112
|
+
query_params ||= {}
|
113
|
+
filter(query_params.merge(:locations => locations_map), &block)
|
114
|
+
end
|
115
|
+
|
99
116
|
# Make a call to the statuses/filter method of the Streaming API,
|
100
117
|
# you may provide <tt>:follow</tt>, <tt>:track</tt> or both as options
|
101
118
|
# to follow the tweets of specified users or track keywords. This
|
102
119
|
# method is provided separately for cases when it would conserve the
|
103
120
|
# number of HTTP connections to combine track and follow.
|
104
121
|
def filter(query_params = {}, &block)
|
105
|
-
[:follow, :track].each do |param|
|
106
|
-
if query_params[param].is_a?(Array)
|
107
|
-
query_params[param] = query_params[param].collect{|q| q.to_s}.join(',')
|
108
|
-
elsif query_params[param]
|
109
|
-
query_params[param] = query_params[param].to_s
|
110
|
-
end
|
111
|
-
end
|
112
122
|
start('statuses/filter', query_params.merge(:method => :post), &block)
|
113
123
|
end
|
114
124
|
|
@@ -121,7 +131,7 @@ module TweetStream
|
|
121
131
|
# end
|
122
132
|
#
|
123
133
|
# Block must take two arguments: the status id and the user id.
|
124
|
-
# If no block is given, it will return the currently set
|
134
|
+
# If no block is given, it will return the currently set
|
125
135
|
# deletion proc. When a block is given, the TweetStream::Client
|
126
136
|
# object is returned to allow for chaining.
|
127
137
|
def on_delete(&block)
|
@@ -132,7 +142,7 @@ module TweetStream
|
|
132
142
|
@on_delete
|
133
143
|
end
|
134
144
|
end
|
135
|
-
|
145
|
+
|
136
146
|
# Set a Proc to be run when a rate limit notice is received
|
137
147
|
# from the Twitter stream. For example:
|
138
148
|
#
|
@@ -142,7 +152,7 @@ module TweetStream
|
|
142
152
|
# end
|
143
153
|
#
|
144
154
|
# Block must take one argument: the number of discarded tweets.
|
145
|
-
# If no block is given, it will return the currently set
|
155
|
+
# If no block is given, it will return the currently set
|
146
156
|
# limit proc. When a block is given, the TweetStream::Client
|
147
157
|
# object is returned to allow for chaining.
|
148
158
|
def on_limit(&block)
|
@@ -153,7 +163,7 @@ module TweetStream
|
|
153
163
|
@on_limit
|
154
164
|
end
|
155
165
|
end
|
156
|
-
|
166
|
+
|
157
167
|
# Set a Proc to be run when an HTTP error is encountered in the
|
158
168
|
# processing of the stream. Note that TweetStream will automatically
|
159
169
|
# try to reconnect, this is for reference only. Don't panic!
|
@@ -164,7 +174,7 @@ module TweetStream
|
|
164
174
|
# end
|
165
175
|
#
|
166
176
|
# Block must take one argument: the error message.
|
167
|
-
# If no block is given, it will return the currently set
|
177
|
+
# If no block is given, it will return the currently set
|
168
178
|
# error proc. When a block is given, the TweetStream::Client
|
169
179
|
# object is returned to allow for chaining.
|
170
180
|
def on_error(&block)
|
@@ -175,42 +185,69 @@ module TweetStream
|
|
175
185
|
@on_error
|
176
186
|
end
|
177
187
|
end
|
178
|
-
|
188
|
+
|
189
|
+
# Set a Proc to be run when connection established.
|
190
|
+
# Called in EventMachine::Connection#post_init
|
191
|
+
#
|
192
|
+
# @client = TweetStream::Client.new('user','pass')
|
193
|
+
# @client.on_inited do
|
194
|
+
# puts 'Connected...'
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
def on_inited(&block)
|
198
|
+
if block_given?
|
199
|
+
@on_inited = block
|
200
|
+
self
|
201
|
+
else
|
202
|
+
@on_inited
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
179
206
|
def start(path, query_parameters = {}, &block) #:nodoc:
|
180
207
|
method = query_parameters.delete(:method) || :get
|
181
208
|
delete_proc = query_parameters.delete(:delete) || self.on_delete
|
182
209
|
limit_proc = query_parameters.delete(:limit) || self.on_limit
|
183
210
|
error_proc = query_parameters.delete(:error) || self.on_error
|
184
|
-
|
185
|
-
|
186
|
-
|
211
|
+
inited_proc = query_parameters.delete(:inited) || self.on_inited
|
212
|
+
|
213
|
+
params = normalize_filter_parameters(query_parameters)
|
214
|
+
|
215
|
+
uri = method == :get ? build_uri(path, params) : build_uri(path)
|
216
|
+
|
187
217
|
EventMachine::run {
|
188
|
-
|
189
|
-
:path
|
190
|
-
:
|
191
|
-
:
|
192
|
-
:
|
193
|
-
:
|
194
|
-
:
|
195
|
-
|
196
|
-
|
218
|
+
stream_params = {
|
219
|
+
:path => uri,
|
220
|
+
:method => method.to_s.upcase,
|
221
|
+
:user_agent => user_agent,
|
222
|
+
:on_inited => inited_proc,
|
223
|
+
:filters => params.delete(:track),
|
224
|
+
:params => params,
|
225
|
+
:ssl => true
|
226
|
+
}.merge(auth_params)
|
227
|
+
|
228
|
+
@stream = Twitter::JSONStream.connect(stream_params)
|
197
229
|
@stream.each_item do |item|
|
198
|
-
|
199
|
-
|
230
|
+
begin
|
231
|
+
raw_hash = json_parser.decode(item)
|
232
|
+
rescue MultiJson::DecodeError
|
233
|
+
error_proc.call("MultiJson::DecodeError occured in stream: #{item}") if error_proc.is_a?(Proc)
|
234
|
+
next
|
235
|
+
end
|
236
|
+
|
200
237
|
unless raw_hash.is_a?(::Hash)
|
201
|
-
error_proc.call("Unexpected JSON object in stream: #{item}")
|
238
|
+
error_proc.call("Unexpected JSON object in stream: #{item}") if error_proc.is_a?(Proc)
|
202
239
|
next
|
203
240
|
end
|
204
|
-
|
205
|
-
hash = TweetStream::Hash.new(raw_hash)
|
206
|
-
|
241
|
+
|
242
|
+
hash = TweetStream::Hash.new(raw_hash)
|
243
|
+
|
207
244
|
if hash[:delete] && hash[:delete][:status]
|
208
245
|
delete_proc.call(hash[:delete][:status][:id], hash[:delete][:status][:user_id]) if delete_proc.is_a?(Proc)
|
209
246
|
elsif hash[:limit] && hash[:limit][:track]
|
210
247
|
limit_proc.call(hash[:limit][:track]) if limit_proc.is_a?(Proc)
|
211
248
|
elsif hash[:text] && hash[:user]
|
212
249
|
@last_status = TweetStream::Status.new(hash)
|
213
|
-
|
250
|
+
|
214
251
|
# Give the block the option to receive either one
|
215
252
|
# or two arguments, depending on its arity.
|
216
253
|
case block.arity
|
@@ -221,35 +258,30 @@ module TweetStream
|
|
221
258
|
end
|
222
259
|
end
|
223
260
|
end
|
224
|
-
|
261
|
+
|
225
262
|
@stream.on_error do |message|
|
226
263
|
error_proc.call(message) if error_proc.is_a?(Proc)
|
227
264
|
end
|
228
|
-
|
265
|
+
|
229
266
|
@stream.on_max_reconnects do |timeout, retries|
|
230
267
|
raise TweetStream::ReconnectError.new(timeout, retries)
|
231
268
|
end
|
232
269
|
}
|
233
270
|
end
|
234
|
-
|
271
|
+
|
235
272
|
# Terminate the currently running TweetStream.
|
236
273
|
def stop
|
237
274
|
EventMachine.stop_event_loop
|
238
275
|
@last_status
|
239
276
|
end
|
240
|
-
|
277
|
+
|
241
278
|
protected
|
242
279
|
|
243
280
|
def parser_from(parser)
|
244
|
-
|
245
|
-
|
246
|
-
parser
|
247
|
-
when Symbol
|
248
|
-
require "tweetstream/parsers/#{parser.to_s}"
|
249
|
-
eval("TweetStream::Parsers::#{parser.to_s.split('_').map{|s| s.capitalize}.join('')}")
|
250
|
-
end
|
281
|
+
MultiJson.engine = parser
|
282
|
+
MultiJson
|
251
283
|
end
|
252
|
-
|
284
|
+
|
253
285
|
def build_uri(path, query_parameters = {}) #:nodoc:
|
254
286
|
URI.parse("/1/#{path}.json#{build_query_parameters(query_parameters)}")
|
255
287
|
end
|
@@ -257,16 +289,42 @@ module TweetStream
|
|
257
289
|
def build_query_parameters(query)
|
258
290
|
query.size > 0 ? "?#{build_post_body(query)}" : ''
|
259
291
|
end
|
260
|
-
|
292
|
+
|
261
293
|
def build_post_body(query) #:nodoc:
|
262
294
|
return '' unless query && query.is_a?(::Hash) && query.size > 0
|
263
295
|
pairs = []
|
264
|
-
|
296
|
+
|
265
297
|
query.each_pair do |k,v|
|
298
|
+
v = v.flatten.collect { |q| q.to_s }.join(',') if v.is_a?(Array)
|
266
299
|
pairs << "#{k.to_s}=#{CGI.escape(v.to_s)}"
|
267
300
|
end
|
268
301
|
|
269
302
|
pairs.join('&')
|
270
303
|
end
|
304
|
+
|
305
|
+
def normalize_filter_parameters(query_parameters = {})
|
306
|
+
[:follow, :track, :locations].each do |param|
|
307
|
+
if query_parameters[param].kind_of?(Array)
|
308
|
+
query_parameters[param] = query_parameters[param].flatten.collect{|q| q.to_s}.join(',')
|
309
|
+
elsif query_parameters[param]
|
310
|
+
query_parameters[param] = query_parameters[param].to_s
|
311
|
+
end
|
312
|
+
end
|
313
|
+
query_parameters
|
314
|
+
end
|
315
|
+
|
316
|
+
def auth_params
|
317
|
+
case auth_method
|
318
|
+
when :basic
|
319
|
+
return :auth => "#{username}:#{password}"
|
320
|
+
when :oauth
|
321
|
+
return :oauth => {
|
322
|
+
:consumer_key => consumer_key,
|
323
|
+
:consumer_secret => consumer_secret,
|
324
|
+
:access_key => oauth_token,
|
325
|
+
:access_secret => oauth_token_secret
|
326
|
+
}
|
327
|
+
end
|
328
|
+
end
|
271
329
|
end
|
272
330
|
end
|