youtube_it 2.1.4 → 2.1.5

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.
@@ -1,9 +1,11 @@
1
+ # encoding: UTF-8
2
+
1
3
  class YouTubeIt
2
4
  module Parser #:nodoc:
3
5
  class FeedParser #:nodoc:
4
6
  def initialize(content)
5
7
  @content = (content =~ URI::regexp(%w(http https)) ? open(content).read : content)
6
-
8
+
7
9
  rescue OpenURI::HTTPError => e
8
10
  raise OpenURI::HTTPError.new(e.io.status[0],e)
9
11
  rescue
@@ -16,23 +18,27 @@ class YouTubeIt
16
18
  end
17
19
 
18
20
  def parse_videos
19
- doc = REXML::Document.new(@content)
21
+ doc = Nokogiri::XML(@content)
20
22
  videos = []
21
- doc.elements.each("*/entry") do |video|
23
+ doc.css("entry").each do |video|
22
24
  videos << parse_entry(video)
23
25
  end
24
26
  videos
25
27
  end
28
+
29
+ def remove_bom str
30
+ str.gsub /\xEF\xBB\xBF|/, ''
31
+ end
26
32
  end
27
33
 
28
34
  class CommentsFeedParser < FeedParser #:nodoc:
29
35
  # return array of comments
30
36
  def parse_content(content)
31
- doc = REXML::Document.new(content.body)
32
- feed = doc.elements["feed"]
37
+ doc = Nokogiri::XML(content.body)
38
+ feed = doc.at("feed")
33
39
 
34
40
  comments = []
35
- feed.elements.each("entry") do |entry|
41
+ feed.css("entry").each do |entry|
36
42
  comments << parse_entry(entry)
37
43
  end
38
44
  return comments
@@ -41,31 +47,38 @@ class YouTubeIt
41
47
  protected
42
48
  def parse_entry(entry)
43
49
  author = YouTubeIt::Model::Author.new(
44
- :name => (entry.elements["author"].elements["name"].text rescue nil),
45
- :uri => (entry.elements["author"].elements["uri"].text rescue nil)
50
+ :name => (entry.at("author/name").text rescue nil),
51
+ :uri => (entry.at("author/uri").text rescue nil)
46
52
  )
47
53
  YouTubeIt::Model::Comment.new(
48
54
  :author => author,
49
- :content => entry.elements["content"].text,
50
- :published => entry.elements["published"].text,
51
- :title => entry.elements["title"].text,
52
- :updated => entry.elements["updated "].text,
53
- :url => entry.elements["id"].text
55
+ :content => remove_bom(entry.at("content").text),
56
+ :published => entry.at("published").text,
57
+ :title => remove_bom(entry.at("title").text),
58
+ :updated => entry.at("updated").text,
59
+ :url => entry.at("id").text,
60
+ :reply_to => parse_reply(entry)
54
61
  )
55
62
  end
63
+
64
+ def parse_reply(entry)
65
+ if link = entry.at_xpath("xmlns:link[@rel='http://gdata.youtube.com/schemas/2007#in-reply-to']")
66
+ link["href"].split('/').last.gsub(/\?client.*/, '')
67
+ end
68
+ end
56
69
  end
57
70
 
58
71
  class PlaylistFeedParser < FeedParser #:nodoc:
59
72
 
60
73
  def parse_content(content)
61
- xml = REXML::Document.new(content.body)
62
- entry = xml.elements["entry"] || xml.elements["feed"]
74
+ xml = Nokogiri::XML(content.body)
75
+ entry = xml.at("entry") || xml.at("feed")
63
76
  YouTubeIt::Model::Playlist.new(
64
- :title => entry.elements["title"].text,
65
- :summary => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
66
- :description => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
67
- :playlist_id => entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':',''),
68
- :published => entry.elements["published"] ? entry.elements["published"].text : nil,
77
+ :title => entry.at("title") && entry.at("title").text,
78
+ :summary => ((entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text rescue nil),
79
+ :description => ((entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text rescue nil),
80
+ :playlist_id => (entry.at("id").text[/playlist:(\w+)/, 1] rescue nil),
81
+ :published => entry.at("published") ? entry.at("published").text : nil,
69
82
  :response_code => content.status,
70
83
  :xml => content.body)
71
84
  end
@@ -75,398 +88,433 @@ class YouTubeIt
75
88
 
76
89
  # return array of playlist objects
77
90
  def parse_content(content)
78
- doc = REXML::Document.new(content.body)
79
- feed = doc.elements["feed"]
80
-
91
+ doc = Nokogiri::XML(content.body)
92
+ feed = doc.at("feed")
93
+
81
94
  playlists = []
82
- feed.elements.each("entry") do |entry|
95
+ feed.css("entry").each do |entry|
83
96
  playlists << parse_entry(entry)
84
97
  end
85
98
  return playlists
86
99
  end
87
-
100
+
88
101
  protected
89
-
102
+
90
103
  def parse_entry(entry)
91
104
  YouTubeIt::Model::Playlist.new(
92
- :title => entry.elements["title"].text,
93
- :summary => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
94
- :description => (entry.elements["summary"] || entry.elements["media:group"].elements["media:description"]).text,
95
- :playlist_id => entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':',''),
96
- :published => entry.elements["published"] ? entry.elements["published"].text : nil,
105
+ :title => entry.at("title").text,
106
+ :summary => (entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text,
107
+ :description => (entry.at("summary") || entry.at_xpath("media:group").at_xpath("media:description")).text,
108
+ :playlist_id => entry.at("id").text[/playlist([^<]+)/, 1].sub(':',''),
109
+ :published => entry.at("published") ? entry.at("published").text : nil,
97
110
  :response_code => nil,
98
111
  :xml => nil)
99
112
  end
100
113
  end
101
-
114
+
102
115
  # Returns an array of the user's activity
103
116
  class ActivityParser < FeedParser
104
117
  def parse_content(content)
105
- doc = REXML::Document.new(content.body)
106
- feed = doc.elements["feed"]
107
-
108
- activity = []
109
- feed.elements.each("entry") do |entry|
110
- parsed_activity = parse_activity(entry)
111
- if parsed_activity
112
- activity << parsed_activity
118
+ doc = Nokogiri::XML(content.body)
119
+ feed = doc.at("feed")
120
+
121
+ activities = []
122
+ feed.css("entry").each do |entry|
123
+ if parsed_activity = parse_activity(entry)
124
+ activities << parsed_activity
113
125
  end
114
126
  end
115
-
116
- return activity
127
+
128
+ return activities
117
129
  end
118
-
130
+
119
131
  protected
120
-
132
+
121
133
  # Parses the user's activity feed.
122
134
  def parse_activity(entry)
123
135
  # Figure out what kind of activity we have
124
136
  video_type = nil
125
137
  parsed_activity = nil
126
- entry.elements.each("category") do |category_tag|
127
- if category_tag.attributes["scheme"]=="http://gdata.youtube.com/schemas/2007/userevents.cat"
128
- video_type = category_tag.attributes["term"]
138
+ entry.css("category").each do |category_tag|
139
+ if category_tag["scheme"] == "http://gdata.youtube.com/schemas/2007/userevents.cat"
140
+ video_type = category_tag["term"]
129
141
  end
130
142
  end
131
-
143
+
132
144
  if video_type
133
145
  case video_type
134
146
  when "video_rated"
135
147
  parsed_activity = YouTubeIt::Model::Activity.new(
136
148
  :type => "video_rated",
137
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
138
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
149
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
150
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
139
151
  :videos => parse_activity_videos(entry),
140
- :video_id => entry.elements["yt:videoid"] ? entry.elements["yt:videoid"].text : nil
152
+ :video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
141
153
  )
142
154
  when "video_shared"
143
155
  parsed_activity = YouTubeIt::Model::Activity.new(
144
156
  :type => "video_shared",
145
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
146
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
157
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
158
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
147
159
  :videos => parse_activity_videos(entry),
148
- :video_id => entry.elements["yt:videoid"] ? entry.elements["yt:videoid"].text : nil
160
+ :video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
149
161
  )
150
162
  when "video_favorited"
151
163
  parsed_activity = YouTubeIt::Model::Activity.new(
152
164
  :type => "video_favorited",
153
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
154
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
165
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
166
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
155
167
  :videos => parse_activity_videos(entry),
156
- :video_id => entry.elements["yt:videoid"] ? entry.elements["yt:videoid"].text : nil
168
+ :video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
157
169
  )
158
170
  when "video_commented"
159
171
  # Load the comment and video URL
160
172
  comment_thread_url = nil
161
173
  video_url = nil
162
- entry.elements.each("link") do |link_tag|
163
- case link_tag.attributes["rel"]
174
+ entry.css("link").each do |link_tag|
175
+ case link_tag["rel"]
164
176
  when "http://gdata.youtube.com/schemas/2007#comments"
165
- comment_thread_url = link_tag.attributes["href"]
177
+ comment_thread_url = link_tag["href"]
166
178
  when "http://gdata.youtube.com/schemas/2007#video"
167
- video_url = link_tag.attributes["href"]
179
+ video_url = link_tag["href"]
168
180
  else
169
181
  # Invalid rel type, do nothing
170
182
  end
171
183
  end
172
-
184
+
173
185
  parsed_activity = YouTubeIt::Model::Activity.new(
174
186
  :type => "video_commented",
175
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
176
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
187
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
188
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
177
189
  :videos => parse_activity_videos(entry),
178
- :video_id => entry.elements["yt:videoid"] ? entry.elements["yt:videoid"].text : nil,
190
+ :video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil,
179
191
  :comment_thread_url => comment_thread_url,
180
192
  :video_url => video_url
181
193
  )
182
194
  when "video_uploaded"
183
195
  parsed_activity = YouTubeIt::Model::Activity.new(
184
196
  :type => "video_uploaded",
185
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
186
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
197
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
198
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
187
199
  :videos => parse_activity_videos(entry),
188
- :video_id => entry.elements["yt:videoid"] ? entry.elements["yt:videoid"].text : nil
200
+ :video_id => entry.at_xpath("yt:videoid") ? entry.at_xpath("yt:videoid").text : nil
189
201
  )
190
202
  when "friend_added"
191
203
  parsed_activity = YouTubeIt::Model::Activity.new(
192
204
  :type => "friend_added",
193
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
194
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
195
- :username => entry.elements["yt:username"] ? entry.elements["yt:username"].text : nil
205
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
206
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
207
+ :username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil
196
208
  )
197
209
  when "user_subscription_added"
198
210
  parsed_activity = YouTubeIt::Model::Activity.new(
199
211
  :type => "user_subscription_added",
200
- :time => entry.elements["updated"] ? entry.elements["updated"].text : nil,
201
- :author => entry.elements["author"].elements["name"] ? entry.elements["author"].elements["name"].text : nil,
202
- :username => entry.elements["yt:username"] ? entry.elements["yt:username"].text : nil
212
+ :time => entry.at("updated") ? entry.at("updated").text : nil,
213
+ :author => entry.at("author/name") ? entry.at("author/name").text : nil,
214
+ :username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil
203
215
  )
204
216
  else
205
217
  # Invalid activity type, just let it return nil
206
218
  end
207
219
  end
208
-
220
+
209
221
  return parsed_activity
210
222
  end
211
-
223
+
212
224
  # If a user enabled inline attribute videos may be included in results.
213
225
  def parse_activity_videos(entry)
214
226
  videos = []
215
-
216
- entry.elements.each("link") do |link_tag|
217
- if link_tag.attributes["rel"]=="http://gdata.youtube.com/schemas/2007#video"
218
- videos << YouTubeIt::Parser::VideoFeedParser.new(link_tag).parse
219
- end
227
+
228
+ entry.css("link").each do |link_tag|
229
+ videos << YouTubeIt::Parser::VideoFeedParser.new(link_tag).parse if link_tag.at("entry")
220
230
  end
221
-
231
+
222
232
  if videos.size <= 0
223
233
  videos = nil
224
234
  end
225
-
235
+
226
236
  return videos
227
237
  end
228
238
  end
229
-
239
+
230
240
  # Returns an array of the user's contacts
231
241
  class ContactsParser < FeedParser
232
242
  def parse_content(content)
233
- doc = REXML::Document.new(content.body)
234
- feed = doc.elements["feed"]
235
-
243
+ doc = Nokogiri::XML(content.body)
244
+ feed = doc.at("feed")
245
+
236
246
  contacts = []
237
- feed.elements.each("entry") do |entry|
247
+ feed.css("entry").each do |entry|
238
248
  temp_contact = YouTubeIt::Model::Contact.new(
239
- :title => entry.elements["title"] ? entry.elements["title"].text : nil,
240
- :username => entry.elements["yt:username"] ? entry.elements["yt:username"].text : nil,
241
- :status => entry.elements["yt:status"] ? entry.elements["yt:status"].text : nil
249
+ :title => entry.at("title") ? entry.at("title").text : nil,
250
+ :username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil,
251
+ :status => entry.at_xpath("yt:status") ? entry.at_xpath("yt:status").text : nil
242
252
  )
243
-
253
+
244
254
  contacts << temp_contact
245
255
  end
246
-
256
+
247
257
  return contacts
248
258
  end
249
259
  end
250
-
260
+
251
261
  # Returns an array of the user's messages
252
262
  class MessagesParser < FeedParser
253
263
  def parse_content(content)
254
- doc = REXML::Document.new(content.body)
255
- puts content.body
256
- puts "doc..."
257
- puts doc.inspect
258
- feed = doc.elements["feed"]
259
-
264
+ doc = Nokogiri::XML(content.body)
265
+ feed = doc.at("feed")
266
+
260
267
  messages = []
261
- feed.elements.each("entry") do |entry|
262
- author = entry.elements["author"]
268
+ feed.css("entry").each do |entry|
269
+ author = entry.at("author")
263
270
  temp_message = YouTubeIt::Model::Message.new(
264
- :id => entry.elements["id"] ? entry.elements["id"].text.gsub(/.+:inbox:/, "") : nil,
265
- :title => entry.elements["title"] ? entry.elements["title"].text : nil,
266
- :name => author && author.elements["name"] ? author.elements["name"].text : nil,
267
- :summary => entry.elements["summary"] ? entry.elements["summary"].text : nil,
268
- :published => entry.elements["published"] ? entry.elements["published"].text : nil
271
+ :id => entry.at("id") ? entry.at("id").text.gsub(/.+:inbox:/, "") : nil,
272
+ :title => entry.at("title") ? entry.at("title").text : nil,
273
+ :name => author && author.at("name") ? author.at("name").text : nil,
274
+ :summary => entry.at("summary") ? entry.at("summary").text : nil,
275
+ :published => entry.at("published") ? entry.at("published").text : nil
269
276
  )
270
-
277
+
271
278
  messages << temp_message
272
279
  end
273
-
280
+
274
281
  return messages
275
282
  end
276
283
  end
277
284
 
278
285
  class ProfileFeedParser < FeedParser #:nodoc:
279
286
  def parse_content(content)
280
- xml = REXML::Document.new(content.body)
281
- entry = xml.elements["entry"] || xml.elements["feed"]
287
+ xml = Nokogiri::XML(content.body)
288
+ entry = xml.at("entry") || xml.at("feed")
289
+ parse_entry(entry)
290
+ end
291
+ def parse_entry(entry)
282
292
  YouTubeIt::Model::User.new(
283
- :age => entry.elements["yt:age"] ? entry.elements["yt:age"].text : nil,
284
- :username => entry.elements["yt:username"] ? entry.elements["yt:username"].text : nil,
285
- :company => entry.elements["yt:company"] ? entry.elements["yt:company"].text : nil,
286
- :gender => entry.elements["yt:gender"] ? entry.elements["yt:gender"].text : nil,
287
- :hobbies => entry.elements["yt:hobbies"] ? entry.elements["yt:hobbies"].text : nil,
288
- :hometown => entry.elements["yt:hometown"] ? entry.elements["yt:hometown"].text : nil,
289
- :location => entry.elements["yt:location"] ? entry.elements["yt:location"].text : nil,
290
- :last_login => entry.elements["yt:statistics"].attributes["lastWebAccess"],
291
- :join_date => entry.elements["published"] ? entry.elements["published"].text : nil,
292
- :movies => entry.elements["yt:movies"] ? entry.elements["yt:movies"].text : nil,
293
- :music => entry.elements["yt:music"] ? entry.elements["yt:music"].text : nil,
294
- :occupation => entry.elements["yt:occupation"] ? entry.elements["yt:occupation"].text : nil,
295
- :relationship => entry.elements["yt:relationship"] ? entry.elements["yt:relationship"].text : nil,
296
- :school => entry.elements["yt:school"] ? entry.elements["yt:school"].text : nil,
297
- :avatar => entry.elements["media:thumbnail"] ? entry.elements["media:thumbnail"].attributes["url"] : nil,
298
- :subscribers => entry.elements["yt:statistics"].attributes["subscriberCount"],
299
- :videos_watched => entry.elements["yt:statistics"].attributes["videoWatchCount"],
300
- :view_count => entry.elements["yt:statistics"].attributes["viewCount"],
301
- :upload_views => entry.elements["yt:statistics"].attributes["totalUploadViews"]
293
+ :age => entry.at_xpath("yt:age") ? entry.at_xpath("yt:age").text : nil,
294
+ :username => entry.at_xpath("yt:username") ? entry.at_xpath("yt:username").text : nil,
295
+ :username_display => (entry.at_xpath("yt:username")['display'] rescue nil),
296
+ :user_id => (entry.at_xpath("xmlns:author/yt:userId").text rescue nil),
297
+ :last_name => (entry.at_xpath("yt:lastName").text rescue nil),
298
+ :first_name => (entry.at_xpath("yt:firstName").text rescue nil),
299
+ :company => entry.at_xpath("yt:company") ? entry.at_xpath("yt:company").text : nil,
300
+ :gender => entry.at_xpath("yt:gender") ? entry.at_xpath("yt:gender").text : nil,
301
+ :hobbies => entry.at_xpath("yt:hobbies") ? entry.at_xpath("yt:hobbies").text : nil,
302
+ :hometown => entry.at_xpath("yt:hometown") ? entry.at_xpath("yt:hometown").text : nil,
303
+ :location => entry.at_xpath("yt:location") ? entry.at_xpath("yt:location").text : nil,
304
+ :last_login => entry.at_xpath("yt:statistics")["lastWebAccess"],
305
+ :join_date => entry.at("published") ? entry.at("published").text : nil,
306
+ :movies => entry.at_xpath("yt:movies") ? entry.at_xpath("yt:movies").text : nil,
307
+ :music => entry.at_xpath("yt:music") ? entry.at_xpath("yt:music").text : nil,
308
+ :occupation => entry.at_xpath("yt:occupation") ? entry.at_xpath("yt:occupation").text : nil,
309
+ :relationship => entry.at_xpath("yt:relationship") ? entry.at_xpath("yt:relationship").text : nil,
310
+ :school => entry.at_xpath("yt:school") ? entry.at_xpath("yt:school").text : nil,
311
+ :avatar => entry.at_xpath("media:thumbnail") ? entry.at_xpath("media:thumbnail")["url"] : nil,
312
+ :upload_count => (entry.at_xpath('gd:feedLink[@rel="http://gdata.youtube.com/schemas/2007#user.uploads"]')['countHint'].to_i rescue nil),
313
+ :max_upload_duration => (entry.at_xpath("yt:maxUploadDuration")['seconds'].to_i rescue nil),
314
+ :subscribers => entry.at_xpath("yt:statistics")["subscriberCount"],
315
+ :videos_watched => entry.at_xpath("yt:statistics")["videoWatchCount"],
316
+ :view_count => entry.at_xpath("yt:statistics")["viewCount"],
317
+ :upload_views => entry.at_xpath("yt:statistics")["totalUploadViews"],
318
+ :insight_uri => (entry.at_xpath('xmlns:link[@rel="http://gdata.youtube.com/schemas/2007#insight.views"]')['href'] rescue nil)
302
319
  )
303
320
  end
304
321
  end
305
-
322
+
323
+ class BatchProfileFeedParser < ProfileFeedParser
324
+ def parse_content(content)
325
+ Nokogiri::XML(content.body).xpath("//xmlns:entry").map do |entry|
326
+ entry.namespaces.each {|name, url| entry.document.root.add_namespace name, url }
327
+ username = entry.at_xpath('batch:id', entry.namespaces).text
328
+ result = catch(:result) do
329
+ case entry.at_xpath('batch:status', entry.namespaces)['code'].to_i
330
+ when 200...300 then parse_entry(entry)
331
+ else nil
332
+ end
333
+ end
334
+ { username => result }
335
+ end.reduce({},:merge)
336
+ end
337
+ end
338
+
306
339
  class SubscriptionFeedParser < FeedParser #:nodoc:
307
340
 
308
341
  def parse_content(content)
309
- doc = REXML::Document.new(content.body)
310
- feed = doc.elements["feed"]
311
-
342
+ doc = Nokogiri::XML(content.body)
343
+ feed = doc.at("feed")
344
+
312
345
  subscriptions = []
313
- feed.elements.each("entry") do |entry|
346
+ feed.css("entry").each do |entry|
314
347
  subscriptions << parse_entry(entry)
315
348
  end
316
349
  return subscriptions
317
350
  end
318
-
351
+
319
352
  protected
320
-
353
+
321
354
  def parse_entry(entry)
322
355
  YouTubeIt::Model::Subscription.new(
323
- :title => entry.elements["title"].text,
324
- :id => entry.elements["id"].text[/subscription([^<]+)/, 1].sub(':',''),
325
- :published => entry.elements["published"] ? entry.elements["published"].text : nil
356
+ :title => entry.at("title").text,
357
+ :id => entry.at("id").text[/subscription([^<]+)/, 1].sub(':',''),
358
+ :published => entry.at("published") ? entry.at("published").text : nil
326
359
  )
327
360
  end
328
361
  end
329
-
362
+
330
363
 
331
364
  class VideoFeedParser < FeedParser #:nodoc:
332
365
 
333
366
  def parse_content(content)
334
- doc = (content.is_a?(REXML::Element)) ? content : REXML::Document.new(content)
335
-
336
- entry = doc.elements["entry"]
367
+ doc = (content.is_a?(Nokogiri::XML::Document)) ? content : Nokogiri::XML(content)
368
+
369
+ entry = doc.at "entry"
337
370
  parse_entry(entry)
338
371
  end
339
372
 
340
373
  protected
341
374
  def parse_entry(entry)
342
- video_id = entry.elements["id"].text
343
- published_at = entry.elements["published"] ? Time.parse(entry.elements["published"].text) : nil
344
- updated_at = entry.elements["updated"] ? Time.parse(entry.elements["updated"].text) : nil
375
+ video_id = entry.at("id").text
376
+ published_at = entry.at("published") ? Time.parse(entry.at("published").text) : nil
377
+ uploaded_at = entry.at_xpath("media:group/yt:uploaded") ? Time.parse(entry.at_xpath("media:group/yt:uploaded").text) : nil
378
+ updated_at = entry.at("updated") ? Time.parse(entry.at("updated").text) : nil
345
379
 
346
380
  # parse the category and keyword lists
347
381
  categories = []
348
382
  keywords = []
349
- entry.elements.each("category") do |category|
383
+ entry.css("category").each do |category|
350
384
  # determine if it's really a category, or just a keyword
351
- scheme = category.attributes["scheme"]
385
+ scheme = category["scheme"]
352
386
  if (scheme =~ /\/categories\.cat$/)
353
387
  # it's a category
354
388
  categories << YouTubeIt::Model::Category.new(
355
- :term => category.attributes["term"],
356
- :label => category.attributes["label"])
389
+ :term => category["term"],
390
+ :label => category["label"])
357
391
 
358
392
  elsif (scheme =~ /\/keywords\.cat$/)
359
393
  # it's a keyword
360
- keywords << category.attributes["term"]
394
+ keywords << category["term"]
361
395
  end
362
396
  end
363
397
 
364
- title = entry.elements["title"].text
365
- html_content = entry.elements["content"] ? entry.elements["content"].text : nil
398
+ title = entry.at("title").text
399
+ html_content = nil #entry.at("content") ? entry.at("content").text : nil
366
400
 
367
401
  # parse the author
368
- author_element = entry.elements["author"]
402
+ author_element = entry.at("author")
369
403
  author = nil
370
404
  if author_element
371
405
  author = YouTubeIt::Model::Author.new(
372
- :name => author_element.elements["name"].text,
373
- :uri => author_element.elements["uri"].text)
406
+ :name => author_element.at("name").text,
407
+ :uri => author_element.at("uri").text)
374
408
  end
375
- media_group = entry.elements["media:group"]
409
+ media_group = entry.at_xpath('media:group')
376
410
 
377
411
  ytid = nil
378
- unless media_group.elements["yt:videoid"].nil?
379
- ytid = media_group.elements["yt:videoid"].text
380
- end
412
+ unless media_group.at_xpath("yt:videoid").nil?
413
+ ytid = media_group.at_xpath("yt:videoid").text
414
+ end
381
415
 
382
416
  # if content is not available on certain region, there is no media:description, media:player or yt:duration
383
417
  description = ""
384
- unless media_group.elements["media:description"].nil?
385
- description = media_group.elements["media:description"].text
418
+ unless media_group.at_xpath("media:description").nil?
419
+ description = media_group.at_xpath("media:description").text
386
420
  end
387
421
 
388
422
  # if content is not available on certain region, there is no media:description, media:player or yt:duration
389
423
  duration = 0
390
- unless media_group.elements["yt:duration"].nil?
391
- duration = media_group.elements["yt:duration"].attributes["seconds"].to_i
424
+ unless media_group.at_xpath("yt:duration").nil?
425
+ duration = media_group.at_xpath("yt:duration")["seconds"].to_i
392
426
  end
393
427
 
394
428
  # if content is not available on certain region, there is no media:description, media:player or yt:duration
395
429
  player_url = ""
396
- unless media_group.elements["media:player"].nil?
397
- player_url = media_group.elements["media:player"].attributes["url"]
430
+ unless media_group.at_xpath("media:player").nil?
431
+ player_url = media_group.at_xpath("media:player")["url"]
398
432
  end
399
433
 
400
- unless media_group.elements["yt:aspectRatio"].nil?
401
- widescreen = media_group.elements["yt:aspectRatio"].text == 'widescreen' ? true : false
434
+ unless media_group.at_xpath("yt:aspectRatio").nil?
435
+ widescreen = media_group.at_xpath("yt:aspectRatio").text == 'widescreen' ? true : false
402
436
  end
403
437
 
404
438
  media_content = []
405
- media_group.elements.each("media:content") do |mce|
439
+ media_group.xpath("media:content").each do |mce|
406
440
  media_content << parse_media_content(mce)
407
441
  end
408
442
 
409
443
  # parse thumbnails
410
444
  thumbnails = []
411
- media_group.elements.each("media:thumbnail") do |thumb_element|
445
+ media_group.xpath("media:thumbnail").each do |thumb_element|
412
446
  # TODO: convert time HH:MM:ss string to seconds?
413
447
  thumbnails << YouTubeIt::Model::Thumbnail.new(
414
- :url => thumb_element.attributes["url"],
415
- :height => thumb_element.attributes["height"].to_i,
416
- :width => thumb_element.attributes["width"].to_i,
417
- :time => thumb_element.attributes["time"])
448
+ :url => thumb_element["url"],
449
+ :height => thumb_element["height"].to_i,
450
+ :width => thumb_element["width"].to_i,
451
+ :time => thumb_element["time"])
418
452
  end
419
453
 
420
- rating_element = entry.elements["gd:rating"]
421
- extended_rating_element = entry.elements["yt:rating"]
454
+ rating_element = entry.at_xpath("gd:rating")
455
+ extended_rating_element = entry.at_xpath("yt:rating")
422
456
 
423
457
  rating = nil
424
458
  if rating_element
425
459
  rating_values = {
426
- :min => rating_element.attributes["min"].to_i,
427
- :max => rating_element.attributes["max"].to_i,
428
- :rater_count => rating_element.attributes["numRaters"].to_i,
429
- :average => rating_element.attributes["average"].to_f
460
+ :min => rating_element["min"].to_i,
461
+ :max => rating_element["max"].to_i,
462
+ :rater_count => rating_element["numRaters"].to_i,
463
+ :average => rating_element["average"].to_f
430
464
  }
431
465
 
432
466
  if extended_rating_element
433
- rating_values[:likes] = extended_rating_element.attributes["numLikes"].to_i
434
- rating_values[:dislikes] = extended_rating_element.attributes["numDislikes"].to_i
467
+ rating_values[:likes] = extended_rating_element["numLikes"].to_i
468
+ rating_values[:dislikes] = extended_rating_element["numDislikes"].to_i
435
469
  end
436
470
 
437
471
  rating = YouTubeIt::Model::Rating.new(rating_values)
438
472
  end
439
473
 
440
- if (el = entry.elements["yt:statistics"])
441
- view_count, favorite_count = el.attributes["viewCount"].to_i, el.attributes["favoriteCount"].to_i
474
+ if (el = entry.at_xpath("yt:statistics"))
475
+ view_count, favorite_count = el["viewCount"].to_i, el["favoriteCount"].to_i
442
476
  else
443
477
  view_count, favorite_count = 0,0
444
478
  end
445
479
 
446
- noembed = entry.elements["yt:noembed"] ? true : false
447
- racy = entry.elements["media:rating"] ? true : false
480
+ comment_feed = entry.at_xpath('gd:comments/gd:feedLink[@rel="http://gdata.youtube.com/schemas/2007#comments"]')
481
+ comment_count = comment_feed ? comment_feed['countHint'].to_i : 0
482
+ #puts comment_count.inspect
483
+
484
+ access_control = entry.xpath('yt:accessControl').map do |e|
485
+ { e['action'] => e['permission'] }
486
+ end.compact.reduce({},:merge)
448
487
 
449
- if where = entry.elements["georss:where"]
450
- position = where.elements["gml:Point"].elements["gml:pos"].text
451
- latitude, longitude = position.split(" ")
488
+ noembed = entry.at_xpath("yt:noembed") ? true : false
489
+ safe_search = entry.at_xpath("media:rating") ? true : false
490
+
491
+ if entry.namespaces['xmlns:georss'] and where = entry.at_xpath("georss:where")
492
+ position = where.at_xpath("gml:Point").at_xpath("gml:pos").text
493
+ latitude, longitude = position.split.map &:to_f
452
494
  end
453
-
454
- control = entry.elements["app:control"]
455
- state = { :name => "published" }
456
- if control && control.elements["yt:state"]
457
- state = {
458
- :name => control.elements["yt:state"].attributes["name"],
459
- :reason_code => control.elements["yt:state"].attributes["reasonCode"],
460
- :help_url => control.elements["yt:state"].attributes["helpUrl"],
461
- :copy => control.elements["yt:state"].text
462
- }
463
-
495
+
496
+ if entry.namespaces['xmlns:app']
497
+ control = entry.at_xpath("app:control")
498
+ state = { :name => "published" }
499
+ if control && control.at_xpath("yt:state")
500
+ state = {
501
+ :name => control.at_xpath("yt:state")["name"],
502
+ :reason_code => control.at_xpath("yt:state")["reasonCode"],
503
+ :help_url => control.at_xpath("yt:state")["helpUrl"],
504
+ :copy => control.at_xpath("yt:state").text
505
+ }
506
+ end
464
507
  end
465
508
 
509
+ insight_uri = (entry.at_xpath('xmlns:link[@rel="http://gdata.youtube.com/schemas/2007#insight.views"]')['href'] rescue nil)
510
+
511
+ perm_private = media_group.at_xpath("yt:private") ? true : false
512
+
466
513
  YouTubeIt::Model::Video.new(
467
514
  :video_id => video_id,
468
515
  :published_at => published_at,
469
516
  :updated_at => updated_at,
517
+ :uploaded_at => uploaded_at,
470
518
  :categories => categories,
471
519
  :keywords => keywords,
472
520
  :title => title,
@@ -480,24 +528,27 @@ class YouTubeIt
480
528
  :rating => rating,
481
529
  :view_count => view_count,
482
530
  :favorite_count => favorite_count,
531
+ :comment_count => comment_count,
532
+ :access_control => access_control,
483
533
  :widescreen => widescreen,
484
534
  :noembed => noembed,
485
- :racy => racy,
486
- :where => where,
535
+ :safe_search => safe_search,
487
536
  :position => position,
488
537
  :latitude => latitude,
489
538
  :longitude => longitude,
490
539
  :state => state,
491
- :unique_id => ytid)
540
+ :insight_uri => insight_uri,
541
+ :unique_id => ytid,
542
+ :perm_private => perm_private)
492
543
  end
493
544
 
494
- def parse_media_content (media_content_element)
495
- content_url = media_content_element.attributes["url"]
496
- format_code = media_content_element.attributes["yt:format"].to_i
545
+ def parse_media_content (elem)
546
+ content_url = elem["url"]
547
+ format_code = elem["format"].to_i
497
548
  format = YouTubeIt::Model::Video::Format.by_code(format_code)
498
- duration = media_content_element.attributes["duration"].to_i
499
- mime_type = media_content_element.attributes["type"]
500
- default = (media_content_element.attributes["isDefault"] == "true")
549
+ duration = elem["duration"].to_i
550
+ mime_type = elem["type"]
551
+ default = (elem["isDefault"] == "true")
501
552
 
502
553
  YouTubeIt::Model::Content.new(
503
554
  :url => content_url,
@@ -513,16 +564,16 @@ class YouTubeIt
513
564
  private
514
565
  def parse_content(content)
515
566
  videos = []
516
- doc = REXML::Document.new(content)
517
- feed = doc.elements["feed"]
567
+ doc = Nokogiri::XML(content)
568
+ feed = doc.at "feed"
518
569
  if feed
519
- feed_id = feed.elements["id"].text
520
- updated_at = Time.parse(feed.elements["updated"].text)
521
- total_result_count = feed.elements["openSearch:totalResults"].text.to_i
522
- offset = feed.elements["openSearch:startIndex"].text.to_i
523
- max_result_count = feed.elements["openSearch:itemsPerPage"].text.to_i
570
+ feed_id = feed.at("id").text
571
+ updated_at = Time.parse(feed.at("updated").text)
572
+ total_result_count = feed.at_xpath("openSearch:totalResults").text.to_i
573
+ offset = feed.at_xpath("openSearch:startIndex").text.to_i
574
+ max_result_count = feed.at_xpath("openSearch:itemsPerPage").text.to_i
524
575
 
525
- feed.elements.each("entry") do |entry|
576
+ feed.css("entry").each do |entry|
526
577
  videos << parse_entry(entry)
527
578
  end
528
579
  end