neufelry-twitter 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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