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 ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gemtest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ Gemfile.lock
7
+ coverage/*
8
+ .yardoc/*
9
+ doc/*
10
+ .bundle
11
+ .swp
12
+ .idea
13
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format doc
data/.yardopts ADDED
@@ -0,0 +1,4 @@
1
+ --markup markdown
2
+ -
3
+ RELEASE_NOTES.md
4
+ LICENSE.md
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
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
+
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Intridea, Inc.
1
+ Copyright (c) 2011 Intridea, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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.
@@ -1,4 +1,5 @@
1
- == Version 1.0.0
1
+ Version 1.0.0
2
+ =============
2
3
 
3
4
  * Swappable JSON backend support
4
5
  * Switches to use EventMachine instead of Yajl for the HTTP Stream
data/Rakefile CHANGED
@@ -1,60 +1,19 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
3
 
4
- begin
5
- require 'jeweler'
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
- require 'rake/rdoctask'
49
- Rake::RDocTask.new do |rdoc|
50
- if File.exist?('VERSION')
51
- version = File.read('VERSION')
52
- else
53
- version = ""
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
@@ -2,7 +2,7 @@ require 'uri'
2
2
  require 'cgi'
3
3
  require 'eventmachine'
4
4
  require 'twitter/json_stream'
5
- require 'json'
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
- # 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)
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(user, pass, parser = :json_gem)
44
- self.username = user
45
- self.password = pass
46
- self.parser = parser
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
- uri = method == :get ? build_uri(path, query_parameters) : build_uri(path)
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
- @stream = Twitter::JSONStream.connect(
189
- :path => uri,
190
- :auth => "#{URI.encode self.username}:#{URI.encode self.password}",
191
- :method => method.to_s.upcase,
192
- :content => (method == :post ? build_post_body(query_parameters) : ''),
193
- :user_agent => 'TweetStream',
194
- :ssl => true
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
- raw_hash = @parser.decode(item)
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) # @parser.parse(item)
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
- case parser
245
- when Class
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