tubeclip 0.1.0

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