xh5-tweetstream 1.1.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Intridea, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,166 @@
1
+ = TweetStream
2
+
3
+ TweetStream provides simple Ruby access to Twitter's Streaming API
4
+ (http://apiwiki.twitter.com/Streaming-API-Documentation).
5
+
6
+ This fork has been modified to support the site_streams API currently in Beta.
7
+
8
+ We have also added support for location-based queries to the streaming API.
9
+
10
+ == Installation
11
+
12
+ To install from Gemcutter:
13
+
14
+ gem install tweetstream
15
+
16
+ == Usage
17
+
18
+ Using TweetStream is quite simple:
19
+
20
+ require 'rubygems'
21
+ require 'tweetstream'
22
+
23
+ # This will pull a sample of all tweets based on
24
+ # your Twitter account's Streaming API role.
25
+ TweetStream::Client.new('username','password').sample do |status|
26
+ # The status object is a special Hash with
27
+ # method access to its keys.
28
+ puts "#{status.text}"
29
+ end
30
+
31
+ You can also use it to track keywords or follow a given set of
32
+ user ids:
33
+
34
+ # Use 'track' to track a list of single-word keywords
35
+ TweetStream::Client.new('username','password').track('term1', 'term2') do |status|
36
+ puts "#{status.text}"
37
+ end
38
+
39
+ # Use 'follow' to follow a group of user ids (integers, not screen names)
40
+ TweetStream::Client.new('username','password').follow(14252, 53235) do |status|
41
+ puts "#{status.text}"
42
+ end
43
+
44
+ The methods available to TweetStream::Client will be kept in parity
45
+ with the methods available on the Streaming API wiki page.
46
+
47
+ == Swappable JSON Parsing
48
+
49
+ As of version 1.0, TweetStream supports swappable JSON backends for
50
+ parsing the Tweets. These are specified when you initialize the
51
+ client or daemon by passing it in as the last argument:
52
+
53
+ # Parse tweets using Yajl-Ruby
54
+ TweetStream::Client.new('abc','def',:yajl) # ...
55
+
56
+ Available options are <tt>:yajl</tt>, <tt>:json_gem</tt> (default),
57
+ <tt>:json_pure</tt>, and <tt>:active_support</tt>.
58
+
59
+ == Handling Deletes and Rate Limitations
60
+
61
+ Sometimes the Streaming API will send messages other than statuses.
62
+ Specifically, it does so when a status is deleted or rate limitations
63
+ have caused some tweets not to appear in the stream. To handle these,
64
+ you can use the on_delete and on_limit methods. Example:
65
+
66
+ @client = TweetStream::Client.new('user','pass')
67
+
68
+ @client.on_delete do |status_id, user_id|
69
+ Tweet.delete(status_id)
70
+ end
71
+
72
+ @client.on_limit do |skip_count|
73
+ # do something
74
+ end
75
+
76
+ @client.track('intridea')
77
+
78
+ The on_delete and on_limit methods can also be chained, like so:
79
+
80
+ TweetStream::Client.new('user','pass').on_delete{ |status_id, user_id|
81
+ Tweet.delete(status_id)
82
+ }.on_limit { |skip_count|
83
+ # do something
84
+ }.track('intridea') do |status|
85
+ # do something with the status like normal
86
+ end
87
+
88
+ You can also provide <tt>:delete</tt> and/or <tt>:limit</tt>
89
+ options when you make your method call:
90
+
91
+ TweetStream::Client.new('user','pass').track('intridea',
92
+ :delete => Proc.new{ |status_id, user_id| # do something },
93
+ :limit => Proc.new{ |skip_count| # do something }
94
+ ) do |status|
95
+ # do something with the status like normal
96
+ end
97
+
98
+ Twitter recommends honoring deletions as quickly as possible, and
99
+ you would likely be wise to integrate this functionality into your
100
+ application.
101
+
102
+ == Errors and Reconnecting
103
+
104
+ TweetStream uses EventMachine to connect to the Twitter Streaming
105
+ API, and attempts to honor Twitter's guidelines in terms of automatic
106
+ reconnection. When Twitter becomes unavailable, the block specified
107
+ by you in <tt>on_error</tt> will be called. Note that this does not
108
+ indicate something is actually wrong, just that Twitter is momentarily
109
+ down. It could be for routine maintenance, etc.
110
+
111
+ TweetStream::Client.new('abc','def').on_error do |message|
112
+ # Log your error message somewhere
113
+ end.track('term') do |status|
114
+ # Do things when nothing's wrong
115
+ end
116
+
117
+ However, if the maximum number of reconnect attempts has been reached,
118
+ TweetStream will raise a <tt>TweetStream::ReconnectError</tt> with
119
+ information about the timeout and number of retries attempted.
120
+
121
+ == Terminating a TweetStream
122
+
123
+ It is often the case that you will need to change the parameters of your
124
+ track or follow tweet streams. In the case that you need to terminate
125
+ a stream, you may add a second argument to your block that will yield
126
+ the client itself:
127
+
128
+ # Stop after collecting 10 statuses
129
+ @statuses = []
130
+ TweetStream::Client.new('username','password').sample do |status, client|
131
+ @statuses << status
132
+ client.stop if @statuses.size >= 10
133
+ end
134
+
135
+ When <tt>stop</tt> is called, TweetStream will return from the block
136
+ the last successfully yielded status, allowing you to make note of
137
+ it in your application as necessary.
138
+
139
+ == Daemonizing
140
+
141
+ It is also possible to create a daemonized script quite easily
142
+ using the TweetStream library:
143
+
144
+ # The third argument is an optional process name
145
+ TweetStream::Daemon.new('username','password', 'tracker').track('term1', 'term2') do |status|
146
+ # do something in the background
147
+ end
148
+
149
+ If you put the above into a script and run the script with <tt>ruby scriptname.rb</tt>, you will see a list of daemonization commands such
150
+ as start, stop, and run.
151
+
152
+ == Note on Patches/Pull Requests
153
+
154
+ * Fork the project.
155
+ * Make your feature addition or bug fix.
156
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
157
+ * 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)
158
+ * Send me a pull request. Bonus points for topic branches.
159
+
160
+ == Contributors
161
+
162
+ * Michael Bleigh (initial gem)
163
+
164
+ == Copyright
165
+
166
+ Copyright (c) 2009 Intridea, Inc. (http://www.intridea.com/). See LICENSE for details.
@@ -0,0 +1,5 @@
1
+ == Version 1.0.0
2
+
3
+ * Swappable JSON backend support
4
+ * Switches to use EventMachine instead of Yajl for the HTTP Stream
5
+ * Support reconnect and on_error
@@ -0,0 +1,60 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "xh5-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 = "eric@xhfive.com"
11
+ gem.homepage = "http://github.com/erichurst/tweetstream"
12
+ gem.authors = ["Michael Bleigh"]
13
+ gem.files = FileList["[A-Z]*", "{lib,spec,examples}/**/*"] - FileList["**/*.log"]
14
+ gem.add_development_dependency "rspec"
15
+ gem.add_dependency 'xh5-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
45
+
46
+ task :default => :spec
47
+
48
+ require 'rake/rdoctask'
49
+ Rake::RDocTask.new do |rdoc|
50
+ if File.exist?('VERSION')
51
+ version = File.read('VERSION')
52
+ else
53
+ version = ""
54
+ 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
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.0
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'tweetstream'
3
+ require 'ruby-growl'
4
+
5
+ if args_start = ARGV.index('--')
6
+ username, password = ARGV[args_start + 1].split(':')
7
+ tracks = ARGV[args_start + 2 .. -1]
8
+ puts "Starting a GrowlTweet to track: #{tracks.inspect}"
9
+ end
10
+
11
+ TweetStream::Daemon.new(username,password).track(*tracks) do |status|
12
+ g = Growl.new 'localhost', 'growltweet', ['tweet']
13
+ g.notify 'tweet', status.user.screen_name, status.text
14
+ end
@@ -0,0 +1,21 @@
1
+ require 'tweetstream/client'
2
+ require 'tweetstream/hash'
3
+ require 'tweetstream/status'
4
+ require 'tweetstream/user'
5
+ require 'tweetstream/daemon'
6
+
7
+ module TweetStream
8
+ class Terminated < ::StandardError; end
9
+ class Error < ::StandardError; end
10
+ class ConnectionError < TweetStream::Error; end
11
+ # A ReconnectError is raised when the maximum number of retries has
12
+ # failed to re-establish a connection.
13
+ class ReconnectError < StandardError
14
+ attr_accessor :timeout, :retries
15
+ def initialize(timeout, retries)
16
+ self.timeout = timeout
17
+ self.retries = retries
18
+ super("Failed to reconnect after #{retries} tries.")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,318 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'eventmachine'
4
+ require 'twitter_stream/json_stream'
5
+ require 'json'
6
+
7
+ module TweetStream
8
+ # Provides simple access to the Twitter Streaming API (http://apiwiki.twitter.com/Streaming-API-Documentation)
9
+ # for Ruby scripts that need to create a long connection to
10
+ # Twitter for tracking and other purposes.
11
+ #
12
+ # Basic usage of the library is to call one of the provided
13
+ # methods and provide a block that will perform actions on
14
+ # a yielded TweetStream::Status. For example:
15
+ #
16
+ # TweetStream::Client.new('user','pass').track('fail') do |status|
17
+ # puts "[#{status.user.screen_name}] #{status.text}"
18
+ # end
19
+ #
20
+ # For information about a daemonized TweetStream client,
21
+ # view the TweetStream::Daemon class.
22
+ class Client
23
+ attr_accessor :consumer_key, :consumer_secret, :access_key, :access_secret
24
+ attr_reader :parser
25
+
26
+ # Set the JSON Parser for this client. Acceptable options are:
27
+ #
28
+ # <tt>:json_gem</tt>:: Parse using the JSON gem.
29
+ # <tt>:json_pure</tt>:: Parse using the pure-ruby implementation of the JSON gem.
30
+ # <tt>:active_support</tt>:: Parse using ActiveSupport::JSON.decode
31
+ # <tt>:yajl</tt>:: Parse using <tt>yajl-ruby</tt>.
32
+ #
33
+ # You may also pass a class that will return a hash with symbolized
34
+ # keys when <tt>YourClass.parse</tt> is called with a JSON string.
35
+ def parser=(parser)
36
+ @parser = parser_from(parser)
37
+ end
38
+
39
+ # Create a new client with the Twitter credentials
40
+ # of the account you want to be using its API quota.
41
+ # You may also set the JSON parsing library as specified
42
+ # in the #parser= setter.
43
+ def initialize(consumer_key, consumer_secret, access_key, access_secret, parser = :json_gem)
44
+ self.consumer_key = consumer_key
45
+ self.consumer_secret = consumer_secret
46
+ self.access_key = access_key
47
+ self.access_secret = access_secret
48
+ self.parser = parser
49
+ end
50
+
51
+ # Returns all public statuses. The Firehose is not a generally
52
+ # available resource. Few applications require this level of access.
53
+ # Creative use of a combination of other resources and various access
54
+ # levels can satisfy nearly every application use case.
55
+ def firehose(query_parameters = {}, &block)
56
+ start('statuses/firehose', query_parameters, &block)
57
+ end
58
+
59
+ # Returns all retweets. The retweet stream is not a generally available
60
+ # resource. Few applications require this level of access. Creative
61
+ # use of a combination of other resources and various access levels
62
+ # can satisfy nearly every application use case. As of 9/11/2009,
63
+ # the site-wide retweet feature has not yet launched,
64
+ # so there are currently few, if any, retweets on this stream.
65
+ def retweet(query_parameters = {}, &block)
66
+ start('statuses/retweet', query_parameters, &block)
67
+ end
68
+
69
+ # Returns a random sample of all public statuses. The default access level
70
+ # provides a small proportion of the Firehose. The "Gardenhose" access
71
+ # level provides a proportion more suitable for data mining and
72
+ # research applications that desire a larger proportion to be statistically
73
+ # significant sample.
74
+ def sample(query_parameters = {}, &block)
75
+ start('statuses/sample', query_parameters, &block)
76
+ end
77
+
78
+ # Specify keywords to track. Queries are subject to Track Limitations,
79
+ # described in Track Limiting and subject to access roles, described in
80
+ # the statuses/filter method. Track keywords are case-insensitive logical
81
+ # ORs. Terms are exact-matched, and also exact-matched ignoring
82
+ # punctuation. Phrases, keywords with spaces, are not supported.
83
+ # Keywords containing punctuation will only exact match tokens.
84
+ # Query parameters may be passed as the last argument.
85
+ def track(*keywords, &block)
86
+ query_params = keywords.pop if keywords.last.is_a?(::Hash)
87
+ query_params ||= {}
88
+ filter(query_params.merge(:track => keywords), &block)
89
+ end
90
+
91
+ # Returns public statuses from or in reply to a set of users. Mentions
92
+ # ("Hello @user!") and implicit replies ("@user Hello!" created without
93
+ # pressing the reply "swoosh") are not matched. Requires integer user
94
+ # IDs, not screen names. Query parameters may be passed as the last argument.
95
+ def follow(*user_ids, &block)
96
+ query_params = user_ids.pop if user_ids.last.is_a?(::Hash)
97
+ query_params ||= {}
98
+ filter(query_params.merge(:follow => user_ids), &block)
99
+ end
100
+
101
+ def locations(coords, &block)
102
+ filter(query_params.merge(:locations => coords), &block)
103
+ end
104
+
105
+ # Make a call to the statuses/filter method of the Streaming API,
106
+ # you may provide <tt>:follow</tt>, <tt>:track</tt> or both as options
107
+ # to follow the tweets of specified users or track keywords. This
108
+ # method is provided separately for cases when it would conserve the
109
+ # number of HTTP connections to combine track and follow.
110
+ def filter(query_params = {}, &block)
111
+ [:follow, :track, :locations].each do |param|
112
+ if query_params[param].is_a?(Array)
113
+ query_params[param] = query_params[param].collect{|q| q.to_s}.join(',')
114
+ elsif query_params[param]
115
+ query_params[param] = query_params[param].to_s
116
+ end
117
+ end
118
+ start('statuses/filter', query_params.merge(:method => :post), &block)
119
+ end
120
+
121
+ def site_follow(*user_ids, &block)
122
+ query_params ||= {:follow => user_ids}
123
+
124
+ if query_params[:follow].is_a?(Array)
125
+ query_params[:follow] = query_params[:follow].collect{|q| q.to_s}.join(',')
126
+ elsif query_params[:follow]
127
+ query_params[:follow] = query_params[:follow].to_s
128
+ end
129
+ query_params[:site_streams] = query_params[:follow]
130
+ query_params[:track] = ["foo"]
131
+
132
+ start('site', query_params.merge(:method => :post, :host => 'betastream.twitter.com', :version => '2b'), &block)
133
+ end
134
+
135
+ # Set a Proc to be run when a deletion notice is received
136
+ # from the Twitter stream. For example:
137
+ #
138
+ # @client = TweetStream::Client.new('user','pass')
139
+ # @client.on_delete do |status_id, user_id|
140
+ # Tweet.delete(status_id)
141
+ # end
142
+ #
143
+ # Block must take two arguments: the status id and the user id.
144
+ # If no block is given, it will return the currently set
145
+ # deletion proc. When a block is given, the TweetStream::Client
146
+ # object is returned to allow for chaining.
147
+ def on_delete(&block)
148
+ if block_given?
149
+ @on_delete = block
150
+ self
151
+ else
152
+ @on_delete
153
+ end
154
+ end
155
+
156
+ # Set a Proc to be run when a rate limit notice is received
157
+ # from the Twitter stream. For example:
158
+ #
159
+ # @client = TweetStream::Client.new('user','pass')
160
+ # @client.on_limit do |discarded_count|
161
+ # # Make note of discarded count
162
+ # end
163
+ #
164
+ # Block must take one argument: the number of discarded tweets.
165
+ # If no block is given, it will return the currently set
166
+ # limit proc. When a block is given, the TweetStream::Client
167
+ # object is returned to allow for chaining.
168
+ def on_limit(&block)
169
+ if block_given?
170
+ @on_limit = block
171
+ self
172
+ else
173
+ @on_limit
174
+ end
175
+ end
176
+
177
+ # Set a Proc to be run when an HTTP error is encountered in the
178
+ # processing of the stream. Note that TweetStream will automatically
179
+ # try to reconnect, this is for reference only. Don't panic!
180
+ #
181
+ # @client = TweetStream::Client.new('user','pass')
182
+ # @client.on_error do |message|
183
+ # # Make note of error message
184
+ # end
185
+ #
186
+ # Block must take one argument: the error message.
187
+ # If no block is given, it will return the currently set
188
+ # error proc. When a block is given, the TweetStream::Client
189
+ # object is returned to allow for chaining.
190
+ def on_error(&block)
191
+ if block_given?
192
+ @on_error = block
193
+ self
194
+ else
195
+ @on_error
196
+ end
197
+ end
198
+
199
+ def start(path, query_parameters = {}, &block) #:nodoc:
200
+
201
+ host = query_parameters.delete(:host) || 'stream.twitter.com'
202
+ version = query_parameters.delete(:version) || '1'
203
+
204
+ method = query_parameters.delete(:method) || :get
205
+ delete_proc = query_parameters.delete(:delete) || self.on_delete
206
+ limit_proc = query_parameters.delete(:limit) || self.on_limit
207
+ error_proc = query_parameters.delete(:error) || self.on_error
208
+
209
+ uri = method == :get ? build_uri(path, version, query_parameters) : build_uri(path, version)
210
+
211
+ oauth = {
212
+ :consumer_key => self.consumer_key,
213
+ :consumer_secret => self.consumer_secret,
214
+ :access_key => self.access_key,
215
+ :access_secret => self.access_secret
216
+ }
217
+
218
+ EventMachine::run {
219
+ @stream = TwitterStream::JSONStream.connect(
220
+ :path => uri,
221
+ :host => host,
222
+ :oauth => oauth,
223
+ :filters => query_parameters[:track],
224
+ :follow => query_parameters[:follow],
225
+ :locations => query_parameters[:locations],
226
+ :site_streams => query_parameters[:site_streams],
227
+ :method => method.to_s.upcase,
228
+ :content => (method == :post ? build_post_body(query_parameters) : ''),
229
+ :user_agent => 'TweetStream'
230
+ )
231
+
232
+ @stream.each_item do |item|
233
+ raw_hash = @parser.decode(item)
234
+
235
+ unless raw_hash.is_a?(::Hash)
236
+ error_proc.call("Unexpected JSON object in stream: #{item}")
237
+ next
238
+ end
239
+
240
+ hash = TweetStream::Hash.new(raw_hash) # @parser.parse(item)
241
+
242
+ if hash[:delete] && hash[:delete][:status]
243
+ delete_proc.call(hash[:delete][:status][:id], hash[:delete][:status][:user_id]) if delete_proc.is_a?(Proc)
244
+ elsif hash[:limit] && hash[:limit][:track]
245
+ limit_proc.call(hash[:limit][:track]) if limit_proc.is_a?(Proc)
246
+ elsif hash[:text] && hash[:user]
247
+ @last_status = TweetStream::Status.new(hash)
248
+
249
+ # Give the block the option to receive either one
250
+ # or two arguments, depending on its arity.
251
+ case block.arity
252
+ when 1
253
+ yield @last_status
254
+ when 2
255
+ yield @last_status, self
256
+ end
257
+ elsif hash[:for_user]
258
+
259
+ @last_status = TweetStream::Status.new(hash[:messages] || hash[:message])
260
+
261
+ # Give the block the option to receive either one
262
+ # or two arguments, depending on its arity.
263
+ case block.arity
264
+ when 2
265
+ yield @last_status, hash[:for_user]
266
+ when 3
267
+ yield @last_status, hash[:for_user], self
268
+ end
269
+ end
270
+ end
271
+
272
+ @stream.on_error do |message|
273
+ error_proc.call(message) if error_proc.is_a?(Proc)
274
+ end
275
+
276
+ @stream.on_max_reconnects do |timeout, retries|
277
+ raise TweetStream::ReconnectError.new(timeout, retries)
278
+ end
279
+ }
280
+ end
281
+
282
+ # Terminate the currently running TweetStream.
283
+ def stop
284
+ EventMachine.stop_event_loop
285
+ @last_status
286
+ end
287
+
288
+ protected
289
+
290
+ def parser_from(parser)
291
+ case parser
292
+ when Class
293
+ parser
294
+ when Symbol
295
+ require "tweetstream/parsers/#{parser.to_s}"
296
+ eval("TweetStream::Parsers::#{parser.to_s.split('_').map{|s| s.capitalize}.join('')}")
297
+ end
298
+ end
299
+
300
+ def build_uri(path, version = '1', query_parameters = {}) #:nodoc:
301
+ URI.parse("/#{version}/#{path}.json#{build_query_parameters(query_parameters)}")
302
+ end
303
+
304
+ def build_query_parameters(query)
305
+ query.size > 0 ? "?#{build_post_body(query)}" : ''
306
+ end
307
+
308
+ def build_post_body(query) #:nodoc:
309
+ return '' unless query && query.is_a?(::Hash) && query.size > 0
310
+ pairs = []
311
+
312
+ query.each_pair do |k,v|
313
+ pairs << "#{k.to_s}=#{CGI.escape(v.to_s)}"
314
+ end
315
+ pairs.join('&')
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,39 @@
1
+ require 'daemons'
2
+
3
+ # A daemonized TweetStream client that will allow you to
4
+ # create backgroundable scripts for application specific
5
+ # processes. For instance, if you create a script called
6
+ # <tt>tracker.rb</tt> and fill it with this:
7
+ #
8
+ # require 'rubygems'
9
+ # require 'tweetstream'
10
+ #
11
+ # TweetStream::Daemon.new('user','pass', 'tracker').track('intridea') do |status|
12
+ # # do something here
13
+ # end
14
+ #
15
+ # And then you call this from the shell:
16
+ #
17
+ # ruby tracker.rb start
18
+ #
19
+ # A daemon process will spawn that will automatically
20
+ # run the code in the passed block whenever a new tweet
21
+ # matching your search term ('intridea' in this case)
22
+ # is posted.
23
+ #
24
+ class TweetStream::Daemon < TweetStream::Client
25
+ # Initialize a Daemon with the credentials of the
26
+ # Twitter account you wish to use. The daemon has
27
+ # an optional process name for use when querying
28
+ # running processes.
29
+ def initialize(consumer_key, consumer_secret, access_key, access_secret, app_name=nil, parser=:json_gem)
30
+ @app_name = app_name
31
+ super(consumer_key, consumer_secret, access_key, access_secret, parser)
32
+ end
33
+
34
+ def start(path, query_parameters = {}, &block) #:nodoc:
35
+ Daemons.run_proc(@app_name || 'tweetstream', :multiple => true) do
36
+ super(path, query_parameters, &block)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ class TweetStream::Hash < ::Hash #:nodoc: all
2
+ def initialize(other_hash = {})
3
+ other_hash.keys.each do |key|
4
+ value = other_hash[key]
5
+ value = TweetStream::Hash.new(value) if value.is_a?(::Hash)
6
+ self[key.to_sym] = value
7
+ end
8
+ end
9
+
10
+ def method_missing(method_name, *args)
11
+ if key?(method_name.to_sym)
12
+ self[method_name.to_sym]
13
+ else
14
+ super
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_support/json' unless defined?(::ActiveSupport::JSON)
2
+
3
+ module TweetStream
4
+ module Parsers
5
+ class ActiveSupport
6
+ def self.decode(string)
7
+ ::ActiveSupport::JSON.decode(string)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'json' unless defined?(JSON)
2
+
3
+ module TweetStream
4
+ module Parsers
5
+ class JsonGem
6
+ def self.decode(string)
7
+ ::JSON.parse(string)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'json/pure' unless defined?(::JSON)
2
+
3
+ module TweetStream
4
+ module Parsers
5
+ class JsonPure
6
+ def self.decode(string)
7
+ ::JSON.parse(string)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'yajl' unless defined?(Yajl)
2
+
3
+ module TweetStream
4
+ module Parsers
5
+ class Yajl
6
+ def self.decode(string)
7
+ ::Yajl::Parser.new(:symbolize_keys => true).parse(string)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ # A simple Hash wrapper that gives you method-based
2
+ # access to the properties of a Twitter status.
3
+ class TweetStream::Status < TweetStream::Hash
4
+ def initialize(hash)
5
+ super
6
+ self[:user] = TweetStream::User.new(self[:user]) if self[:user]
7
+ end
8
+
9
+ def id
10
+ self[:id] || super
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # A simple Hash wrapper that gives you method-based
2
+ # access to user properties returned by the streamer.
3
+ class TweetStream::User < TweetStream::Hash
4
+ def id
5
+ self[:id] || super
6
+ end
7
+ end
@@ -0,0 +1 @@
1
+ {"favorited":false,"text":"listening to Where U Headed by Universal Playaz. http://iLike.com/s/9zpOZ #musicmonday something for the ladies","in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"<a href=\"http://www.iLike.com\" rel=\"nofollow\">iLike</a>","truncated":false,"created_at":"Tue Sep 22 01:29:13 +0000 2009","user":{"statuses_count":378,"favourites_count":1,"profile_text_color":"666666","location":"Atlanta, Ga","profile_background_image_url":"http://a3.twimg.com/profile_background_images/36516125/Universal_Playaz.jpg","profile_link_color":"2FC2EF","description":"Paper Chaser","following":null,"verified":false,"notifications":null,"profile_sidebar_fill_color":"252429","profile_image_url":"http://a1.twimg.com/profile_images/413331530/DIESELSTATScopy_normal.jpg","url":"http://www.myspace.com/DieselDtheg","profile_sidebar_border_color":"181A1E","screen_name":"DieselD2143","profile_background_tile":true,"followers_count":75,"protected":false,"time_zone":"Eastern Time (US & Canada)","created_at":"Thu Jun 18 15:56:32 +0000 2009","name":"Diesel D","friends_count":119,"profile_background_color":"1A1B1F","id":48392351,"utc_offset":-18000},"in_reply_to_status_id":null,"id":4161231023} {"favorited":false,"text":"David Bowie and Nine Inch Nails perform \"Hurt\" http://bit.ly/AOaWG #musicmonday #nineinchnails #nin","in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"web","truncated":false,"created_at":"Tue Sep 22 01:29:16 +0000 2009","user":{"statuses_count":668,"favourites_count":25,"profile_text_color":"445d85","location":"S\u00e3o Paulo, Brazil","profile_background_image_url":"http://a3.twimg.com/profile_background_images/38174991/GeorgeRomero-oil-400.jpg","profile_link_color":"555757","description":"You think I ain't worth a dollar, but I feel like a millionaire","following":null,"verified":false,"notifications":null,"profile_sidebar_fill_color":"a3a7ad","profile_image_url":"http://a1.twimg.com/profile_images/96034368/n1076431955_30001395_7912_normal.jpg","url":null,"profile_sidebar_border_color":"c7d1ed","screen_name":"RenatonMiranda","profile_background_tile":true,"followers_count":111,"protected":false,"time_zone":"Santiago","created_at":"Sat Mar 14 15:03:59 +0000 2009","name":"Renato Miranda","friends_count":143,"profile_background_color":"287356","id":24379310,"utc_offset":-14400},"in_reply_to_status_id":null,"id":4161232008} {"favorited":false,"text":"#musicmonday ,time to download some songs today!! :)","in_reply_to_user_id":null,"in_reply_to_screen_name":null,"source":"web","truncated":false,"created_at":"Tue Sep 22 01:29:19 +0000 2009","user":{"statuses_count":188,"favourites_count":0,"profile_text_color":"3D1957","location":"under the water","profile_background_image_url":"http://s.twimg.com/a/1253562286/images/themes/theme10/bg.gif","profile_link_color":"FF0000","description":"ask me ","following":null,"verified":false,"notifications":null,"profile_sidebar_fill_color":"7AC3EE","profile_image_url":"http://a1.twimg.com/profile_images/421281292/twit_pic_normal.jpg","url":"http://www.exploretalent.com/contest_video.php?talentnum=2053105&cm_id=3398","profile_sidebar_border_color":"65B0DA","screen_name":"julieanne11343","profile_background_tile":true,"followers_count":9,"protected":false,"time_zone":"Pacific Time (US & Canada)","created_at":"Mon Jul 20 21:08:22 +0000 2009","name":"Julieanne","friends_count":17,"profile_background_color":"642D8B","id":58591151,"utc_offset":-28800},"in_reply_to_status_id":null,"id":4161233120} {"text":"#Musicmonday \"Dont be tardy f0r the party\"","truncated":false,"source":"<a href=\"http://twitterhelp.blogspot.com/2008/05/twitter-via-mobile-web-mtwittercom.html\" rel=\"nofollow\">mobile web</a>","in_reply_to_status_id":null,"favorited":false,"created_at":"Tue Sep 22 01:29:19 +0000 2009","user":{"verified":false,"notifications":null,"profile_sidebar_fill_color":"e0ff92","location":"Dope Girl Island","profile_sidebar_border_color":"87bc44","description":"","following":null,"profile_background_tile":false,"followers_count":29,"profile_image_url":"http://a3.twimg.com/profile_images/217487577/badbad_normal.jpg","time_zone":"Eastern Time (US & Canada)","url":null,"friends_count":65,"profile_background_color":"9ae4e8","screen_name":"SwagGirlOnDeck","protected":false,"statuses_count":847,"favourites_count":0,"created_at":"Fri May 01 16:59:15 +0000 2009","profile_text_color":"000000","name":"Mariah Reta","id":36987168,"profile_background_image_url":"http://s.twimg.com/a/1253301564/images/themes/theme1/bg.png","utc_offset":-18000,"profile_link_color":"0000ff"},"in_reply_to_user_id":null,"id":4161233317,"in_reply_to_screen_name":null}
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
@@ -0,0 +1,25 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'tweetstream'
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+ require 'yajl'
9
+ require 'json'
10
+
11
+ def sample_tweets
12
+ if @tweets
13
+ @tweets
14
+ else
15
+ @tweets = []
16
+ Yajl::Parser.parse(File.open(File.dirname(__FILE__) + '/data/statuses.json', 'r'), :symbolize_keys => true) do |hash|
17
+ @tweets << hash
18
+ end
19
+ @tweets
20
+ end
21
+ end
22
+
23
+ Spec::Runner.configure do |config|
24
+
25
+ end
@@ -0,0 +1,233 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe TweetStream::Client do
4
+ #it 'should set the username and password from the initializers' do
5
+ #@client = TweetStream::Client.new('abc','def','hij','klm')
6
+ #@client.username.should == 'abc'
7
+ #@client.password.should == 'def'
8
+ #end
9
+
10
+ describe '#build_uri' do
11
+ before do
12
+ @client = TweetStream::Client.new('abc','def','hij','klm')
13
+ end
14
+
15
+ it 'should return a URI' do
16
+ @client.send(:build_uri, '').is_a?(URI).should be_true
17
+ end
18
+
19
+ it 'should have the specified path with the version prefix and a json extension' do
20
+ @client.send(:build_uri, 'awesome').path.should == '/1/awesome.json'
21
+ end
22
+
23
+ it 'should add on a query string if such parameters are specified' do
24
+ @client.send(:build_uri, 'awesome', 1, :q => 'abc').query.should == 'q=abc'
25
+ end
26
+ end
27
+
28
+ describe '#build_post_body' do
29
+ before do
30
+ @client = TweetStream::Client.new('abc','def','hij','klm')
31
+ end
32
+
33
+ it 'should return a blank string if passed a nil value' do
34
+ @client.send(:build_post_body, nil).should == ''
35
+ end
36
+
37
+ it 'should return a blank string if passed an empty hash' do
38
+ @client.send(:build_post_body, {}).should == ''
39
+ end
40
+
41
+ it 'should add a query parameter for a key' do
42
+ @client.send(:build_post_body, {:query => 'abc'}).should == 'query=abc'
43
+ end
44
+
45
+ it 'should escape characters in the value' do
46
+ @client.send(:build_post_body, {:query => 'awesome guy'}).should == 'query=awesome+guy'
47
+ end
48
+
49
+ it 'should join multiple pairs together' do
50
+ ['a=b&c=d','c=d&a=b'].include?(@client.send(:build_post_body, {:a => 'b', :c => 'd'})).should be_true
51
+ end
52
+ end
53
+
54
+ describe '#start' do
55
+ before do
56
+ @stream = stub("TwitterStream::JSONStream",
57
+ :connect => true,
58
+ :unbind => true,
59
+ :each_item => true,
60
+ :on_error => true,
61
+ :on_max_reconnects => true,
62
+ :connection_completed => true
63
+ )
64
+ EM.stub!(:run).and_yield
65
+ TwitterStream::JSONStream.stub!(:connect).and_return(@stream)
66
+ @client = TweetStream::Client.new('abc','def','hij','klm')
67
+ end
68
+
69
+ it 'should try to connect via a JSON stream' do
70
+ #TwitterStream::JSONStream.should_receive(:connect).with(
71
+ # :oauth => {:access_key=>"hij", :access_secret=>"klm", :consumer_key=>"abc", :consumer_secret=>"def"},
72
+ # :content => 'track=monday',
73
+ # :path => URI.parse('/1/statuses/filter.json'),
74
+ # :method => 'POST',
75
+ # :user_agent => 'TweetStream'
76
+ #).and_return(@stream)
77
+
78
+ @client.track('monday')
79
+ end
80
+
81
+ describe '#each_item' do
82
+ it 'should call the appropriate parser' do
83
+ @client = TweetStream::Client.new('abc','def','hij','klm',:active_support)
84
+ TweetStream::Parsers::ActiveSupport.should_receive(:decode).and_return({})
85
+ @stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
86
+ @client.track('abc','def')
87
+ end
88
+
89
+ it 'should yield a TweetStream::Status' do
90
+ @stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
91
+ @client.track('abc'){|s| s.should be_kind_of(TweetStream::Status)}
92
+ end
93
+
94
+ it 'should also yield the client if a block with arity 2 is given' do
95
+ @stream.should_receive(:each_item).and_yield(sample_tweets[0].to_json)
96
+ @client.track('abc'){|s,c| c.should == @client}
97
+ end
98
+
99
+ it 'should include the proper values' do
100
+ tweet = sample_tweets[0]
101
+ tweet[:id] = 123
102
+ tweet[:user][:screen_name] = 'monkey'
103
+ tweet[:text] = "Oo oo aa aa"
104
+ @stream.should_receive(:each_item).and_yield(tweet.to_json)
105
+ @client.track('abc') do |s|
106
+ s[:id].should == 123
107
+ s.user.screen_name.should == 'monkey'
108
+ s.text.should == 'Oo oo aa aa'
109
+ end
110
+ end
111
+
112
+ it 'should call the on_delete if specified' do
113
+ delete = '{ "delete": { "status": { "id": 1234, "user_id": 3 } } }'
114
+ @stream.should_receive(:each_item).and_yield(delete)
115
+ @client.on_delete do |id, user_id|
116
+ id.should == 1234
117
+ user_id.should == 3
118
+ end.track('abc')
119
+ end
120
+
121
+ it 'should call the on_limit if specified' do
122
+ limit = '{ "limit": { "track": 1234 } }'
123
+ @stream.should_receive(:each_item).and_yield(limit)
124
+ @client.on_limit do |track|
125
+ track.should == 1234
126
+ end.track('abc')
127
+ end
128
+
129
+ it 'should call on_error if a non-hash response is received' do
130
+ @stream.should_receive(:each_item).and_yield('["favorited"]')
131
+ @client.on_error do |message|
132
+ message.should == 'Unexpected JSON object in stream: ["favorited"]'
133
+ end.track('abc')
134
+ end
135
+ end
136
+
137
+ describe '#on_error' do
138
+ it 'should pass the message on to the error block' do
139
+ @stream.should_receive(:on_error).and_yield('Uh oh')
140
+ @client.on_error do |m|
141
+ m.should == 'Uh oh'
142
+ end.track('abc')
143
+ end
144
+ end
145
+
146
+ describe '#on_max_reconnects' do
147
+ it 'should raise a ReconnectError' do
148
+ @stream.should_receive(:on_max_reconnects).and_yield(30, 20)
149
+ lambda{@client.track('abc')}.should raise_error(TweetStream::ReconnectError) do |e|
150
+ e.timeout.should == 30
151
+ e.retries.should == 20
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ describe ' API methods' do
158
+ before do
159
+ @client = TweetStream::Client.new('abc','def','hij','klm')
160
+ end
161
+
162
+ %w(firehose retweet sample).each do |method|
163
+ it "##{method} should make a call to start with \"statuses/#{method}\"" do
164
+ @client.should_receive(:start).once.with('statuses/' + method, {})
165
+ @client.send(method)
166
+ end
167
+ end
168
+
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)
171
+ @client.track('test')
172
+ end
173
+
174
+ it '#track should comma-join multiple arguments' do
175
+ @client.should_receive(:start).once.with('statuses/filter', :track => 'foo,bar,baz', :method => :post)
176
+ @client.track('foo', 'bar', 'baz')
177
+ end
178
+
179
+ 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 => '123', :method => :post)
181
+ @client.follow(123)
182
+ end
183
+
184
+ it '#follow should comma-join multiple arguments' do
185
+ @client.should_receive(:start).once.with('statuses/filter', :follow => '123,456', :method => :post)
186
+ @client.follow(123, 456)
187
+ end
188
+
189
+ 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 => '123', :method => :post)
191
+ @client.filter(:follow => 123)
192
+ end
193
+ end
194
+
195
+ %w(on_delete on_limit).each do |proc_setter|
196
+ describe "##{proc_setter}" do
197
+ before do
198
+ @client = TweetStream::Client.new('abc','def','hij','klm')
199
+ end
200
+
201
+ it 'should set when a block is given' do
202
+ proc = Proc.new{|a,b| puts a }
203
+ @client.send(proc_setter, &proc)
204
+ @client.send(proc_setter).should == proc
205
+ end
206
+ end
207
+ end
208
+
209
+ describe '#track' do
210
+ before do
211
+ @client = TweetStream::Client.new('abc','def','hij','klm')
212
+ end
213
+
214
+ 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.track('rock')
217
+ end
218
+ end
219
+
220
+ describe 'instance .stop' do
221
+ it 'should call EventMachine::stop_event_loop' do
222
+ EventMachine.should_receive :stop_event_loop
223
+ TweetStream::Client.new('abc','def','hij','klm').stop.should be_nil
224
+ end
225
+
226
+ it 'should return the last status yielded' do
227
+ EventMachine.should_receive :stop_event_loop
228
+ client = TweetStream::Client.new('abc','def','hij','klm')
229
+ client.send(:instance_variable_set, :@last_status, {})
230
+ client.stop.should == {}
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe TweetStream::Hash do
4
+ it 'should be initialized by passing in an existing hash' do
5
+ TweetStream::Hash.new(:abc => 123)[:abc].should == 123
6
+ end
7
+
8
+ it 'should symbolize incoming keys' do
9
+ TweetStream::Hash.new('abc' => 123)[:abc].should == 123
10
+ end
11
+
12
+ it 'should allow access via method calls' do
13
+ TweetStream::Hash.new(:abc => 123).abc.should == 123
14
+ end
15
+
16
+ it 'should still throw NoMethod for non-existent keys' do
17
+ lambda{TweetStream::Hash.new({}).akabi}.should raise_error(NoMethodError)
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe 'TweetStream JSON Parsers' do
4
+ it 'should default to the JSON Gem' do
5
+ TweetStream::Client.new('abc','def','hij','klm').parser.should == TweetStream::Parsers::JsonGem
6
+ end
7
+
8
+ [:json_gem, :yajl, :active_support, :json_pure].each do |engine|
9
+ describe "#{engine} parsing" do
10
+ before do
11
+ @client = TweetStream::Client.new('abc','def','hij','klm',engine)
12
+ @class_name = "TweetStream::Parsers::#{engine.to_s.split('_').map{|s| s.capitalize}.join('')}"
13
+ end
14
+
15
+ it 'should set the parser to the appropriate class' do
16
+ @client.parser.to_s == @class_name
17
+ end
18
+
19
+ it 'should be settable via client.parser=' do
20
+ @client.parser = nil
21
+ @client.parser.should be_nil
22
+ @client.parser = engine
23
+ @client.parser.to_s.should == @class_name
24
+ end
25
+ end
26
+ end
27
+
28
+ class FakeParser
29
+ def self.decode(text)
30
+ {}
31
+ end
32
+ end
33
+
34
+ it 'should be settable to a class' do
35
+ @client = TweetStream::Client.new('abc','def','hij','klm')
36
+ @client.parser = FakeParser
37
+ @client.parser.should == FakeParser
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe TweetStream::Status do
4
+ it 'should modify the :user key into a TweetStream::User object' do
5
+ @status = TweetStream::Status.new(:user => {:screen_name => 'bob'})
6
+ @status.user.is_a?(TweetStream::User).should be_true
7
+ @status.user.screen_name.should == 'bob'
8
+ end
9
+
10
+ it 'should override the #id method for itself and the user' do
11
+ @status = TweetStream::Status.new(:id => 123, :user => {:id => 345})
12
+ @status.id.should == 123
13
+ @status.user.id.should == 345
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe TweetStream do
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xh5-tweetstream
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 1
8
+ - 0
9
+ version: 1.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Michael Bleigh
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-12 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: xh5-twitter-stream
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: daemons
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ type: :runtime
55
+ version_requirements: *id003
56
+ description: TweetStream allows you to easily consume the Twitter Streaming API utilizing the YAJL Ruby gem.
57
+ email: eric@xhfive.com
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files:
63
+ - LICENSE
64
+ - README.rdoc
65
+ files:
66
+ - LICENSE
67
+ - README.rdoc
68
+ - RELEASE_NOTES.rdoc
69
+ - Rakefile
70
+ - VERSION
71
+ - examples/growl_daemon.rb
72
+ - lib/tweetstream.rb
73
+ - lib/tweetstream/client.rb
74
+ - lib/tweetstream/daemon.rb
75
+ - lib/tweetstream/hash.rb
76
+ - lib/tweetstream/parsers/active_support.rb
77
+ - lib/tweetstream/parsers/json_gem.rb
78
+ - lib/tweetstream/parsers/json_pure.rb
79
+ - lib/tweetstream/parsers/yajl.rb
80
+ - lib/tweetstream/status.rb
81
+ - lib/tweetstream/user.rb
82
+ - spec/data/statuses.json
83
+ - spec/spec.opts
84
+ - spec/spec_helper.rb
85
+ - spec/tweetstream/client_spec.rb
86
+ - spec/tweetstream/hash_spec.rb
87
+ - spec/tweetstream/parser_spec.rb
88
+ - spec/tweetstream/status_spec.rb
89
+ - spec/tweetstream_spec.rb
90
+ has_rdoc: true
91
+ homepage: http://github.com/erichurst/tweetstream
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --charset=UTF-8
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ requirements: []
114
+
115
+ rubyforge_project:
116
+ rubygems_version: 1.3.6
117
+ signing_key:
118
+ specification_version: 3
119
+ summary: TweetStream is a simple wrapper for consuming the Twitter Streaming API.
120
+ test_files:
121
+ - spec/spec_helper.rb
122
+ - spec/tweetstream/client_spec.rb
123
+ - spec/tweetstream/hash_spec.rb
124
+ - spec/tweetstream/parser_spec.rb
125
+ - spec/tweetstream/status_spec.rb
126
+ - spec/tweetstream_spec.rb
127
+ - examples/growl_daemon.rb