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