baron-twitter 0.4.2

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.
Files changed (70) hide show
  1. data/History +126 -0
  2. data/License +19 -0
  3. data/Manifest +69 -0
  4. data/README +84 -0
  5. data/Rakefile +42 -0
  6. data/bin/twitter +14 -0
  7. data/examples/blocks.rb +15 -0
  8. data/examples/direct_messages.rb +29 -0
  9. data/examples/favorites.rb +20 -0
  10. data/examples/friends_followers.rb +25 -0
  11. data/examples/friendships.rb +13 -0
  12. data/examples/identica_timeline.rb +7 -0
  13. data/examples/location.rb +8 -0
  14. data/examples/posting.rb +9 -0
  15. data/examples/replies.rb +27 -0
  16. data/examples/search.rb +18 -0
  17. data/examples/sent_messages.rb +27 -0
  18. data/examples/timeline.rb +34 -0
  19. data/examples/twitter.rb +27 -0
  20. data/examples/verify_credentials.rb +13 -0
  21. data/lib/twitter.rb +32 -0
  22. data/lib/twitter/base.rb +284 -0
  23. data/lib/twitter/cli.rb +334 -0
  24. data/lib/twitter/cli/config.rb +9 -0
  25. data/lib/twitter/cli/helpers.rb +109 -0
  26. data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
  27. data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
  28. data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
  29. data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
  30. data/lib/twitter/cli/models/account.rb +33 -0
  31. data/lib/twitter/cli/models/configuration.rb +13 -0
  32. data/lib/twitter/cli/models/tweet.rb +20 -0
  33. data/lib/twitter/direct_message.rb +22 -0
  34. data/lib/twitter/easy_class_maker.rb +43 -0
  35. data/lib/twitter/rate_limit_status.rb +19 -0
  36. data/lib/twitter/search.rb +101 -0
  37. data/lib/twitter/search_result.rb +83 -0
  38. data/lib/twitter/search_result_info.rb +82 -0
  39. data/lib/twitter/status.rb +22 -0
  40. data/lib/twitter/user.rb +37 -0
  41. data/lib/twitter/version.rb +3 -0
  42. data/spec/base_spec.rb +139 -0
  43. data/spec/cli/helper_spec.rb +49 -0
  44. data/spec/direct_message_spec.rb +35 -0
  45. data/spec/fixtures/follower_ids.xml +11 -0
  46. data/spec/fixtures/followers.xml +706 -0
  47. data/spec/fixtures/friend_ids.xml +12 -0
  48. data/spec/fixtures/friends.xml +609 -0
  49. data/spec/fixtures/friends_for.xml +584 -0
  50. data/spec/fixtures/friends_lite.xml +192 -0
  51. data/spec/fixtures/friends_timeline.xml +66 -0
  52. data/spec/fixtures/friendship_already_exists.xml +5 -0
  53. data/spec/fixtures/friendship_created.xml +12 -0
  54. data/spec/fixtures/public_timeline.xml +148 -0
  55. data/spec/fixtures/rate_limit_status.xml +7 -0
  56. data/spec/fixtures/search_result_info.yml +147 -0
  57. data/spec/fixtures/search_results.json +1 -0
  58. data/spec/fixtures/status.xml +25 -0
  59. data/spec/fixtures/user.xml +38 -0
  60. data/spec/fixtures/user_timeline.xml +465 -0
  61. data/spec/search_spec.rb +100 -0
  62. data/spec/spec.opts +1 -0
  63. data/spec/spec_helper.rb +23 -0
  64. data/spec/status_spec.rb +40 -0
  65. data/spec/user_spec.rb +42 -0
  66. data/twitter.gemspec +45 -0
  67. data/website/css/common.css +47 -0
  68. data/website/images/terminal_output.png +0 -0
  69. data/website/index.html +147 -0
  70. metadata +187 -0
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
+
5
+ twitter = Twitter::Base.new(config['email'], config['password'])
6
+
7
+ puts twitter.create_friendship('orderedlist').name
8
+ puts twitter.follow('orderedlist').name
9
+ puts twitter.leave('orderedlist').name
10
+ puts twitter.destroy_friendship('orderedlist').name
11
+
12
+ puts twitter.friendship_exists?('jnunemaker', 'orderedlist').inspect
13
+ puts twitter.friendship_exists?('jnunemaker', 'ze').inspect
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
+
5
+ identica = Twitter::Base.new(config['email'], config['password'], :api_host => 'identi.ca/api')
6
+
7
+ identica.timeline(:public).each { |s| puts s.text, s.user.name, '' }
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
+
5
+ twitter = Twitter::Base.new(config['email'], config['password'])
6
+
7
+ puts twitter.update_location('Hollywood, CA').location
8
+ puts twitter.update_delivery_device('none')
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
+
5
+ twitter = Twitter::Base.new(config['email'], config['password'])
6
+ puts twitter.post("This is a test from the example file").inspect
7
+
8
+ # sending a direct message
9
+ # puts twitter.d('jnunemaker', 'this is a test').inspect
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
4
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
5
+
6
+ twitter = Twitter::Base.new(config['email'], config['password'])
7
+
8
+ puts 'SINCE'
9
+ twitter.replies(:since => Time.now - 5.day).each do |s|
10
+ puts "- #{s.text}"
11
+ end
12
+ puts
13
+ puts
14
+
15
+ puts 'SINCE_ID'
16
+ twitter.replies(:since_id => 863081345).each do |s|
17
+ puts "- #{s.text}"
18
+ end
19
+ puts
20
+ puts
21
+
22
+ puts 'PAGE'
23
+ twitter.replies(:page => 1).each do |s|
24
+ puts "- #{s.text}"
25
+ end
26
+ puts
27
+ puts
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+
4
+ Twitter::Search.new('httparty').each { |r| puts r.inspect,'' }
5
+ Twitter::Search.new('httparty').page(2).each { |r| puts r.inspect, '' }
6
+
7
+ # search = Twitter::Search.new
8
+ # search.from('jnunemaker').to('oaknd1').each { |r| puts r.inspect, '' }
9
+ # pp search.result
10
+ # search.clear
11
+
12
+ # search.from('jnunemaker').to('oaknd1').since(814529437).containing('milk').each { |r| puts r.inspect, '' }
13
+ # search.clear
14
+ #
15
+ # search.geocode('40.757929', '-73.985506', '50mi').containing('holland').each { |r| puts r.inspect, '' }
16
+ # search.clear
17
+
18
+ # pp search.from('jnunemaker').fetch()
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
4
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
5
+
6
+ twitter = Twitter::Base.new(config['email'], config['password'])
7
+
8
+ puts 'SINCE'
9
+ twitter.sent_messages(:since => Time.now - 5.day).each do |s|
10
+ puts "- #{s.text}"
11
+ end
12
+ puts
13
+ puts
14
+
15
+ puts 'SINCE_ID'
16
+ twitter.sent_messages(:since_id => 33505386).each do |s|
17
+ puts "- #{s.text}"
18
+ end
19
+ puts
20
+ puts
21
+
22
+ puts 'PAGE'
23
+ twitter.sent_messages(:page => 1).each do |s|
24
+ puts "- #{s.text}"
25
+ end
26
+ puts
27
+ puts
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
4
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
5
+
6
+ twitter = Twitter::Base.new(config['email'], config['password'])
7
+
8
+ puts 'SINCE'
9
+ twitter.timeline(:user, :since => Time.now - 1.day).each do |s|
10
+ puts "- #{s.text}"
11
+ end
12
+ puts
13
+ puts
14
+
15
+ puts 'SINCE_ID'
16
+ twitter.timeline(:user, :since_id => 865547074).each do |s|
17
+ puts "- #{s.text}"
18
+ end
19
+ puts
20
+ puts
21
+
22
+ puts 'COUNT'
23
+ twitter.timeline(:user, :count => 1).each do |s|
24
+ puts "- #{s.text}"
25
+ end
26
+ puts
27
+ puts
28
+
29
+ puts 'PAGE'
30
+ twitter.timeline(:user, :page => 1).each do |s|
31
+ puts "- #{s.text}"
32
+ end
33
+ puts
34
+ puts
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
+
5
+ puts "Public Timeline", "=" * 50
6
+ Twitter::Base.new(config['email'], config['password']).timeline(:public).each do |s|
7
+ puts s.text, s.user.name
8
+ puts
9
+ end
10
+
11
+ puts '', "Friends Timeline", "=" * 50
12
+ Twitter::Base.new(config['email'], config['password']).timeline.each do |s|
13
+ puts s.text, s.user.name
14
+ puts
15
+ end
16
+
17
+ puts '', "Friends", "=" * 50
18
+ Twitter::Base.new(config['email'], config['password']).friends.each do |u|
19
+ puts u.name, u.status.text
20
+ puts
21
+ end
22
+
23
+ puts '', "Followers", "=" * 50
24
+ Twitter::Base.new(config['email'], config['password']).followers.each do |u|
25
+ puts u.name, u.status.text
26
+ puts
27
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
+ config = YAML::load(open(ENV['HOME'] + '/.twitter'))
4
+
5
+ twitter = Twitter::Base.new(config['email'], config['password'])
6
+
7
+ puts twitter.verify_credentials
8
+
9
+ begin
10
+ Twitter::Base.new('asdf', 'foobar').verify_credentials
11
+ rescue => error
12
+ puts error.message
13
+ end
data/lib/twitter.rb ADDED
@@ -0,0 +1,32 @@
1
+ require 'uri'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'yaml'
5
+ require 'time'
6
+ require 'rubygems'
7
+ require 'hpricot'
8
+
9
+ $:.unshift(File.dirname(__FILE__))
10
+ require 'twitter/version'
11
+ require 'twitter/easy_class_maker'
12
+ require 'twitter/base'
13
+ require 'twitter/user'
14
+ require 'twitter/search'
15
+ require 'twitter/status'
16
+ require 'twitter/direct_message'
17
+ require 'twitter/rate_limit_status'
18
+ require 'twitter/search_result_info'
19
+ require 'twitter/search_result'
20
+
21
+ module Twitter
22
+ class Unavailable < StandardError; end
23
+ class CantConnect < StandardError; end
24
+ class BadResponse < StandardError; end
25
+ class UnknownTimeline < ArgumentError; end
26
+ class RateExceeded < StandardError; end
27
+ class CantFindUsers < ArgumentError; end
28
+ class AlreadyFollowing < StandardError; end
29
+ class CantFollowUser < StandardError; end
30
+
31
+ SourceName = 'twittergem'
32
+ end
@@ -0,0 +1,284 @@
1
+ # This is the base class for the twitter library. It makes all the requests
2
+ # to twitter, parses the xml (using hpricot) and returns ruby objects to play with.
3
+ #
4
+ # For complete documentation on the options, check out the twitter api docs.
5
+ # http://groups.google.com/group/twitter-development-talk/web/api-documentation
6
+ module Twitter
7
+ class Base
8
+ # Initializes the configuration for making requests to twitter
9
+ # Twitter example:
10
+ # Twitter.new('email/username', 'password')
11
+ #
12
+ # Identi.ca example:
13
+ # Twitter.new('email/username', 'password', :api_host => 'identi.ca/api')
14
+ def initialize(email, password, options={})
15
+ @api_host = options.delete(:api_host) || 'twitter.com'
16
+ @config, @config[:email], @config[:password] = options, email, password
17
+ @proxy_host = options[:proxy_host]
18
+ @proxy_port = options[:proxy_port]
19
+ end
20
+
21
+ # Returns an array of statuses for a timeline; Defaults to your friends timeline.
22
+ def timeline(which=:friends, options={})
23
+ raise UnknownTimeline unless [:friends, :public, :user].include?(which)
24
+ auth = which.to_s.include?('public') ? false : true
25
+ statuses(call("#{which}_timeline", :auth => auth, :since => options[:since], :args => parse_options(options)))
26
+ end
27
+
28
+ # Returns an array of users who are in your friends list
29
+ def friends(options={})
30
+ users(call(:friends, {:args => parse_options(options)}))
31
+ end
32
+
33
+ # Returns an array of users who are friends for the id or username passed in
34
+ def friends_for(id, options={})
35
+ friends(options.merge({:id => id}))
36
+ end
37
+
38
+ # Returns an array of user ids who are friends for the account or the option id/username passed in
39
+ def friend_ids(id_or_screenname = nil)
40
+ path = id_or_screenname ? "friends/ids/#{id_or_screenname}.xml" : "friends/ids.xml"
41
+ doc = request(path, :auth => true)
42
+ (doc/:id).collect {|id| id.innerHTML}
43
+ end
44
+
45
+ # Returns an array of users who are following you
46
+ def followers(options={})
47
+ users(call(:followers, {:args => parse_options(options)}))
48
+ end
49
+
50
+ def followers_for(id, options={})
51
+ followers(options.merge({:id => id}))
52
+ end
53
+
54
+ # Returns an array of user ids who are followers for the account or the option id/username passed in
55
+ def follower_ids(id_or_screenname = nil)
56
+ path = id_or_screenname ? "followers/ids/#{id_or_screenname}.xml" : "followers/ids.xml"
57
+ doc = request(path, :auth => true)
58
+ (doc/:id).collect {|id| id.innerHTML}
59
+ end
60
+
61
+ # Returns a single status for a given id
62
+ def status(id)
63
+ statuses(call("show/#{id}")).first
64
+ end
65
+
66
+ # returns all the profile information and the last status for a user
67
+ def user(id_or_screenname)
68
+ users(request("users/show/#{id_or_screenname}.xml", :auth => true)).first
69
+ end
70
+
71
+ # Returns an array of statuses that are replies
72
+ def replies(options={})
73
+ statuses(call(:replies, :since => options[:since], :args => parse_options(options)))
74
+ end
75
+
76
+ # Destroys a status by id
77
+ def destroy(id)
78
+ call("destroy/#{id}")
79
+ end
80
+
81
+ def rate_limit_status
82
+ RateLimitStatus.new_from_xml request("account/rate_limit_status.xml", :auth => true)
83
+ end
84
+
85
+ # waiting for twitter to correctly implement this in the api as it is documented
86
+ def featured
87
+ users(call(:featured))
88
+ end
89
+
90
+ # Returns an array of all the direct messages for the authenticated user
91
+ def direct_messages(options={})
92
+ doc = request(build_path('direct_messages.xml', parse_options(options)), {:auth => true, :since => options[:since]})
93
+ (doc/:direct_message).inject([]) { |dms, dm| dms << DirectMessage.new_from_xml(dm); dms }
94
+ end
95
+ alias :received_messages :direct_messages
96
+
97
+ # Returns direct messages sent by auth user
98
+ def sent_messages(options={})
99
+ doc = request(build_path('direct_messages/sent.xml', parse_options(options)), {:auth => true, :since => options[:since]})
100
+ (doc/:direct_message).inject([]) { |dms, dm| dms << DirectMessage.new_from_xml(dm); dms }
101
+ end
102
+
103
+ # destroys a give direct message by id if the auth user is a recipient
104
+ def destroy_direct_message(id)
105
+ DirectMessage.new_from_xml(request("direct_messages/destroy/#{id}.xml", :auth => true, :method => :post))
106
+ end
107
+
108
+ # Sends a direct message <code>text</code> to <code>user</code>
109
+ def d(user, text)
110
+ DirectMessage.new_from_xml(request('direct_messages/new.xml', :auth => true, :method => :post, :form_data => {'text' => text, 'user' => user}))
111
+ end
112
+
113
+ # Befriends id_or_screenname for the auth user
114
+ def create_friendship(id_or_screenname)
115
+ users(request("friendships/create/#{id_or_screenname}.xml", :auth => true, :method => :post)).first
116
+ end
117
+
118
+ # Defriends id_or_screenname for the auth user
119
+ def destroy_friendship(id_or_screenname)
120
+ users(request("friendships/destroy/#{id_or_screenname}.xml", :auth => true, :method => :post)).first
121
+ end
122
+
123
+ # Returns true if friendship exists, false if it doesn't.
124
+ def friendship_exists?(user_a, user_b)
125
+ doc = request(build_path("friendships/exists.xml", {:user_a => user_a, :user_b => user_b}), :auth => true)
126
+ doc.at('friends').innerHTML == 'true' ? true : false
127
+ end
128
+
129
+ # Updates your location and returns Twitter::User object
130
+ def update_location(location)
131
+ users(request(build_path('account/update_location.xml', {'location' => location}), :auth => true, :method => :post)).first
132
+ end
133
+
134
+ # Updates your deliver device and returns Twitter::User object
135
+ def update_delivery_device(device)
136
+ users(request(build_path('account/update_delivery_device.xml', {'device' => device}), :auth => true, :method => :post)).first
137
+ end
138
+
139
+ # Turns notifications by id_or_screenname on for auth user.
140
+ def follow(id_or_screenname)
141
+ users(request("notifications/follow/#{id_or_screenname}.xml", :auth => true, :method => :post)).first
142
+ end
143
+
144
+ # Turns notifications by id_or_screenname off for auth user.
145
+ def leave(id_or_screenname)
146
+ users(request("notifications/leave/#{id_or_screenname}.xml", :auth => true, :method => :post)).first
147
+ end
148
+
149
+ # Returns the most recent favorite statuses for the autenticating user
150
+ def favorites(options={})
151
+ statuses(request(build_path('favorites.xml', parse_options(options)), :auth => true))
152
+ end
153
+
154
+ # Favorites the status specified by id for the auth user
155
+ def create_favorite(id)
156
+ statuses(request("favorites/create/#{id}.xml", :auth => true, :method => :post)).first
157
+ end
158
+
159
+ # Un-favorites the status specified by id for the auth user
160
+ def destroy_favorite(id)
161
+ statuses(request("favorites/destroy/#{id}.xml", :auth => true, :method => :post)).first
162
+ end
163
+
164
+ # Blocks the user specified by id for the auth user
165
+ def block(id)
166
+ users(request("blocks/create/#{id}.xml", :auth => true, :method => :post)).first
167
+ end
168
+
169
+ # Unblocks the user specified by id for the auth user
170
+ def unblock(id)
171
+ users(request("blocks/destroy/#{id}.xml", :auth => true, :method => :post)).first
172
+ end
173
+
174
+ # Posts a new update to twitter for auth user.
175
+ def post(status, options={})
176
+ form_data = {'status' => status}
177
+ form_data.merge!({'source' => options[:source]}) if options[:source]
178
+ form_data.merge!({'in_reply_to_status_id' => options[:in_reply_to_status_id]}) if options[:in_reply_to_status_id]
179
+ Status.new_from_xml(request('statuses/update.xml', :auth => true, :method => :post, :form_data => form_data))
180
+ end
181
+ alias :update :post
182
+
183
+ # Verifies the credentials for the auth user.
184
+ # raises Twitter::CantConnect on failure.
185
+ def verify_credentials
186
+ request('account/verify_credentials.xml', :auth => true)
187
+ end
188
+
189
+ private
190
+ # Converts an hpricot doc to an array of statuses
191
+ def statuses(doc)
192
+ (doc/:status).inject([]) { |statuses, status| statuses << Status.new_from_xml(status); statuses }
193
+ end
194
+
195
+ # Converts an hpricot doc to an array of users
196
+ def users(doc)
197
+ (doc/:user).inject([]) { |users, user| users << User.new_from_xml(user); users }
198
+ end
199
+
200
+ # Calls whatever api method requested that deals with statuses
201
+ #
202
+ # ie: call(:public_timeline, :auth => false)
203
+ def call(method, options={})
204
+ options = { :auth => true, :args => {} }.merge(options)
205
+ # Following line needed as lite=false doesn't work in the API: http://tinyurl.com/yo3h5d
206
+ options[:args].delete(:lite) unless options[:args][:lite]
207
+ args = options.delete(:args)
208
+ request(build_path("statuses/#{method.to_s}.xml", args), options)
209
+ end
210
+
211
+ def response(path, options={})
212
+ uri = URI.parse("http://#{@api_host}")
213
+
214
+ begin
215
+ response = Net::HTTP::Proxy(@proxy_host, @proxy_port).start(uri.host, uri.port) do |http|
216
+ klass = Net::HTTP.const_get options[:method].to_s.downcase.capitalize
217
+ req = klass.new("#{uri.path}/#{path}", options[:headers])
218
+ req.basic_auth(@config[:email], @config[:password]) if options[:auth]
219
+ if options[:method].to_s == 'post' && options[:form_data]
220
+ req.set_form_data(options[:form_data])
221
+ end
222
+ http.request(req)
223
+ end
224
+ rescue => error
225
+ raise CantConnect, error.message
226
+ end
227
+ end
228
+
229
+ # Makes a request to twitter.
230
+ def request(path, options={})
231
+ options = {
232
+ :headers => { "User-Agent" => @config[:email] },
233
+ :method => :get,
234
+ }.merge(options)
235
+
236
+ unless options[:since].nil?
237
+ since = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s
238
+ options[:headers]["If-Modified-Since"] = since
239
+ end
240
+
241
+ handle_response!(response(path, options))
242
+ end
243
+
244
+ def handle_response!(response)
245
+ if %w[200 304].include?(response.code)
246
+ response = parse(response.body)
247
+ raise RateExceeded if (response/:hash/:error).text =~ /Rate limit exceeded/
248
+ response
249
+ elsif response.code == '503'
250
+ raise Unavailable, response.message
251
+ elsif response.code == '401'
252
+ raise CantConnect, 'Authentication failed. Check your username and password'
253
+ elsif response.code == '403'
254
+ error_message = (parse(response.body)/:hash/:error).text
255
+ raise CantFindUsers, error_message if error_message =~ /Could not find both specified users/
256
+ raise AlreadyFollowing, error_message if error_message =~ /already on your list/
257
+ raise CantFollowUser, "Response code #{response.code}: #{response.message} #{error_message}"
258
+ else
259
+ raise CantConnect, "Twitter is returning a #{response.code}: #{response.message}"
260
+ end
261
+ end
262
+
263
+ # Given a path and a hash, build a full path with the hash turned into a query string
264
+ def build_path(path, options)
265
+ unless options.nil?
266
+ query = options.inject('') { |str, h| str += "#{CGI.escape(h[0].to_s)}=#{CGI.escape(h[1].to_s)}&"; str }
267
+ path += "?#{query}"
268
+ end
269
+
270
+ path
271
+ end
272
+
273
+ # Tries to get all the options in the correct format before making the request
274
+ def parse_options(options)
275
+ options[:since] = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s if options[:since]
276
+ options
277
+ end
278
+
279
+ # Converts a string response into an Hpricot xml element.
280
+ def parse(response)
281
+ Hpricot.XML(response || '')
282
+ end
283
+ end
284
+ end