nddrylliog_youtube_it 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Gemfile +5 -0
  2. data/Gemfile.lock +38 -0
  3. data/Manifest.txt +37 -0
  4. data/README.rdoc +296 -0
  5. data/Rakefile +21 -0
  6. data/VERSION +1 -0
  7. data/lib/youtube_it.rb +91 -0
  8. data/lib/youtube_it/chain_io.rb +76 -0
  9. data/lib/youtube_it/client.rb +469 -0
  10. data/lib/youtube_it/middleware/faraday_authheader.rb +24 -0
  11. data/lib/youtube_it/middleware/faraday_oauth.rb +21 -0
  12. data/lib/youtube_it/middleware/faraday_oauth2.rb +13 -0
  13. data/lib/youtube_it/middleware/faraday_youtubeit.rb +30 -0
  14. data/lib/youtube_it/model/activity.rb +17 -0
  15. data/lib/youtube_it/model/author.rb +13 -0
  16. data/lib/youtube_it/model/category.rb +11 -0
  17. data/lib/youtube_it/model/comment.rb +16 -0
  18. data/lib/youtube_it/model/contact.rb +19 -0
  19. data/lib/youtube_it/model/content.rb +18 -0
  20. data/lib/youtube_it/model/message.rb +12 -0
  21. data/lib/youtube_it/model/playlist.rb +11 -0
  22. data/lib/youtube_it/model/rating.rb +23 -0
  23. data/lib/youtube_it/model/subscription.rb +7 -0
  24. data/lib/youtube_it/model/thumbnail.rb +17 -0
  25. data/lib/youtube_it/model/user.rb +28 -0
  26. data/lib/youtube_it/model/video.rb +243 -0
  27. data/lib/youtube_it/parser.rb +543 -0
  28. data/lib/youtube_it/record.rb +12 -0
  29. data/lib/youtube_it/request/base_search.rb +76 -0
  30. data/lib/youtube_it/request/error.rb +15 -0
  31. data/lib/youtube_it/request/standard_search.rb +43 -0
  32. data/lib/youtube_it/request/user_search.rb +47 -0
  33. data/lib/youtube_it/request/video_search.rb +105 -0
  34. data/lib/youtube_it/request/video_upload.rb +552 -0
  35. data/lib/youtube_it/response/video_search.rb +41 -0
  36. data/lib/youtube_it/version.rb +4 -0
  37. data/test/files/recorded_response.xml +1 -0
  38. data/test/files/youtube_video_response.xml +53 -0
  39. data/test/helper.rb +9 -0
  40. data/test/test.mov +0 -0
  41. data/test/test_chain_io.rb +63 -0
  42. data/test/test_client.rb +504 -0
  43. data/test/test_field_search.rb +48 -0
  44. data/test/test_video.rb +48 -0
  45. data/test/test_video_feed_parser.rb +264 -0
  46. data/test/test_video_search.rb +147 -0
  47. data/youtube_it.gemspec +95 -0
  48. metadata +149 -0
@@ -0,0 +1,76 @@
1
+ require 'delegate'
2
+ #:stopdoc:
3
+
4
+ # Stream wrapper that reads IOs in succession. Can be fed to Net::HTTP as post body stream. We use it internally to stream file content
5
+ # instead of reading whole video files into memory. Strings passed to the constructor will be wrapped in StringIOs. By default it will auto-close
6
+ # file handles when they have been read completely to prevent our uploader from leaking file handles
7
+ #
8
+ # chain = ChainIO.new(File.open(__FILE__), File.open('/etc/passwd'), "abcd")
9
+ class YouTubeIt::ChainIO
10
+ attr_accessor :autoclose
11
+
12
+ def initialize(*any_ios)
13
+ @autoclose = true
14
+ @chain = any_ios.flatten.map{|e| e.respond_to?(:read) ? e : StringIO.new(e.to_s) }
15
+ end
16
+
17
+ def read(buffer_size = 1024)
18
+ # Read off the first element in the stack
19
+ current_io = @chain.shift
20
+ return false if !current_io
21
+
22
+ buf = current_io.read(buffer_size)
23
+ if !buf && @chain.empty? # End of streams
24
+ release_handle(current_io) if @autoclose
25
+ false
26
+ elsif !buf # This IO is depleted, but next one is available
27
+ release_handle(current_io) if @autoclose
28
+ read(buffer_size)
29
+ elsif buf.length < buffer_size # This IO is depleted, but we were asked for more
30
+ release_handle(current_io) if @autoclose
31
+ buf + (read(buffer_size - buf.length) || '') # and recurse
32
+ else # just return the buffer
33
+ @chain.unshift(current_io) # put the current back
34
+ buf
35
+ end
36
+ end
37
+
38
+ # Predict the length of all embedded IOs. Will automatically send file size.
39
+ def expected_length
40
+ @chain.inject(0) do | len, io |
41
+ if io.respond_to?(:length)
42
+ len + (io.length - io.pos)
43
+ elsif io.is_a?(File)
44
+ len + File.size(io.path) - io.pos
45
+ else
46
+ raise "Cannot predict length of #{io.inspect}"
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+ def release_handle(io)
53
+ io.close if io.respond_to?(:close)
54
+ end
55
+ end
56
+
57
+ # Net::HTTP only can send chunks of 1024 bytes. This is very inefficient, so we have a spare IO that will send more when asked for 1024.
58
+ # We use delegation because the read call is recursive.
59
+ class YouTubeIt::GreedyChainIO < DelegateClass(YouTubeIt::ChainIO)
60
+ BIG_CHUNK = 512 * 1024 # 500 kb
61
+
62
+ def initialize(*with_ios)
63
+ __setobj__(YouTubeIt::ChainIO.new(with_ios))
64
+ end
65
+
66
+ def read(any_buffer_size)
67
+ __getobj__.read(BIG_CHUNK)
68
+ end
69
+
70
+ def length()
71
+ __getobj__.expected_length
72
+ end
73
+
74
+ end
75
+
76
+ #:startdoc:
@@ -0,0 +1,469 @@
1
+ class YouTubeIt
2
+ class Client
3
+ include YouTubeIt::Logging
4
+ # Previously this was a logger instance but we now do it globally
5
+
6
+ def initialize *params
7
+ if params.first.is_a?(Hash)
8
+ hash_options = params.first
9
+ @user = hash_options[:username]
10
+ @pass = hash_options[:password]
11
+ @dev_key = hash_options[:dev_key]
12
+ @client_id = hash_options[:client_id] || "youtube_it"
13
+ @legacy_debug_flag = hash_options[:debug]
14
+ elsif params.first
15
+ puts "* warning: the method YouTubeIt::Client.new(user, passwd, dev_key) is deprecated, use YouTubeIt::Client.new(:username => 'user', :password => 'passwd', :dev_key => 'dev_key')"
16
+ @user = params.shift
17
+ @pass = params.shift
18
+ @dev_key = params.shift
19
+ @client_id = params.shift || "youtube_it"
20
+ @legacy_debug_flag = params.shift
21
+ end
22
+ end
23
+
24
+ # Retrieves an array of standard feed, custom query, or user videos.
25
+ #
26
+ # === Parameters
27
+ # If fetching videos for a standard feed:
28
+ # params<Symbol>:: Accepts a symbol of :top_rated, :top_favorites, :most_viewed,
29
+ # :most_popular, :most_recent, :most_discussed, :most_linked,
30
+ # :most_responded, :recently_featured, and :watch_on_mobile.
31
+ #
32
+ # You can find out more specific information about what each standard feed provides
33
+ # by visiting: http://code.google.com/apis/youtube/reference.html#Standard_feeds
34
+ #
35
+ # options<Hash> (optional):: Accepts the options of :time, :page (default is 1),
36
+ # and :per_page (default is 25). :offset and :max_results
37
+ # can also be passed for a custom offset.
38
+ #
39
+ # If fetching videos by tags, categories, query:
40
+ # params<Hash>:: Accepts the keys :tags, :categories, :query, :order_by,
41
+ # :author, :safe_search, :response_format, :video_format, :page (default is 1),
42
+ # and :per_page(default is 25)
43
+ #
44
+ # options<Hash>:: Not used. (Optional)
45
+ #
46
+ # If fetching videos for a particular user:
47
+ # params<Hash>:: Key of :user with a value of the username.
48
+ # options<Hash>:: Not used. (Optional)
49
+ # === Returns
50
+ # YouTubeIt::Response::VideoSearch
51
+ def videos_by(params, options={})
52
+ request_params = params.respond_to?(:to_hash) ? params : options
53
+ request_params[:page] = integer_or_default(request_params[:page], 1)
54
+
55
+ request_params[:dev_key] = @dev_key if @dev_key
56
+
57
+ unless request_params[:max_results]
58
+ request_params[:max_results] = integer_or_default(request_params[:per_page], 25)
59
+ end
60
+
61
+ unless request_params[:offset]
62
+ request_params[:offset] = calculate_offset(request_params[:page], request_params[:max_results] )
63
+ end
64
+
65
+ if params.respond_to?(:to_hash) and not params[:user]
66
+ request = YouTubeIt::Request::VideoSearch.new(request_params)
67
+ elsif (params.respond_to?(:to_hash) && params[:user]) || (params == :favorites)
68
+ request = YouTubeIt::Request::UserSearch.new(params, request_params)
69
+ else
70
+ request = YouTubeIt::Request::StandardSearch.new(params, request_params)
71
+ end
72
+
73
+ logger.debug "Submitting request [url=#{request.url}]." if @legacy_debug_flag
74
+ parser = YouTubeIt::Parser::VideosFeedParser.new(request.url)
75
+ parser.parse
76
+ end
77
+
78
+ # Retrieves a single YouTube video.
79
+ #
80
+ # === Parameters
81
+ # vid<String>:: The ID or URL of the video that you'd like to retrieve.
82
+ # user<String>:: The user that uploaded the video that you'd like to retrieve.
83
+ #
84
+ # === Returns
85
+ # YouTubeIt::Model::Video
86
+ def video_by(video)
87
+ vid = nil
88
+ vid_regex = /(?:youtube.com|youtu.be).*(?:\/|v=)([\w-]+)/
89
+ if video =~ vid_regex
90
+ vid = $1
91
+ else
92
+ vid = video
93
+ end
94
+ video_id ="http://gdata.youtube.com/feeds/api/videos/#{vid}?v=2#{@dev_key ? '&key='+@dev_key : ''}"
95
+ parser = YouTubeIt::Parser::VideoFeedParser.new(video_id)
96
+ parser.parse
97
+ end
98
+
99
+ def video_by_user(user, vid)
100
+ video_id = "http://gdata.youtube.com/feeds/api/users/#{user}/uploads/#{vid}?v=2#{@dev_key ? '&key='+@dev_key : ''}"
101
+ parser = YouTubeIt::Parser::VideoFeedParser.new(video_id)
102
+ parser.parse
103
+ end
104
+
105
+ def video_upload(data, opts = {})
106
+ client.upload(data, opts)
107
+ end
108
+
109
+ def video_update(video_id, opts = {})
110
+ client.update(video_id, opts)
111
+ end
112
+
113
+ def video_delete(video_id)
114
+ client.delete(video_id)
115
+ end
116
+
117
+ def message_delete(message_id)
118
+ client.delete_message(message_id)
119
+ end
120
+
121
+ def upload_token(options, nexturl = "http://www.youtube.com/my_videos")
122
+ client.get_upload_token(options, nexturl)
123
+ end
124
+
125
+ def add_comment(video_id, comment)
126
+ client.add_comment(video_id, comment)
127
+ end
128
+
129
+ # opts is converted to get params and appended to comments gdata api url
130
+ # eg opts = { 'max-results' => 10, 'start-index' => 20 }
131
+ # hash does _not_ play nice with symbols
132
+ def comments(video_id, opts = {})
133
+ client.comments(video_id, opts)
134
+ end
135
+
136
+ def add_favorite(video_id)
137
+ client.add_favorite(video_id)
138
+ end
139
+
140
+ def delete_favorite(video_id)
141
+ client.delete_favorite(video_id)
142
+ end
143
+
144
+ def favorites(user = nil, opts = {})
145
+ client.favorites(user, opts)
146
+ end
147
+
148
+ def profile(user = nil)
149
+ client.profile(user)
150
+ end
151
+
152
+ # Fetches a user's activity feed.
153
+ def activity(user = nil, opts = {})
154
+ client.get_activity(user, opts)
155
+ end
156
+
157
+ def playlist(playlist_id)
158
+ client.playlist playlist_id
159
+ end
160
+
161
+ def playlists(user = nil)
162
+ client.playlists(user)
163
+ end
164
+
165
+ def add_playlist(options)
166
+ client.add_playlist(options)
167
+ end
168
+
169
+ def update_playlist(playlist_id, options)
170
+ client.update_playlist(playlist_id, options)
171
+ end
172
+
173
+ def add_video_to_playlist(playlist_id, video_id)
174
+ client.add_video_to_playlist(playlist_id, video_id)
175
+ end
176
+
177
+ def delete_video_from_playlist(playlist_id, playlist_entry_id)
178
+ client.delete_video_from_playlist(playlist_id, playlist_entry_id)
179
+ end
180
+
181
+ def delete_playlist(playlist_id)
182
+ client.delete_playlist(playlist_id)
183
+ end
184
+
185
+ def like_video(video_id)
186
+ client.rate_video(video_id, 'like')
187
+ end
188
+
189
+ def dislike_video(video_id)
190
+ client.rate_video(video_id, 'dislike')
191
+ end
192
+
193
+ def subscribe_channel(channel_name)
194
+ client.subscribe_channel(channel_name)
195
+ end
196
+
197
+ def unsubscribe_channel(subscription_id)
198
+ client.unsubscribe_channel(subscription_id)
199
+ end
200
+
201
+ def subscriptions(user_id = nil)
202
+ client.subscriptions(user_id)
203
+ end
204
+
205
+ def enable_http_debugging
206
+ client.enable_http_debugging
207
+ end
208
+
209
+ def add_response(original_video_id, response_video_id)
210
+ client.add_response(original_video_id, response_video_id)
211
+ end
212
+
213
+ def delete_response(original_video_id, response_video_id)
214
+ client.delete_response(original_video_id, response_video_id)
215
+ end
216
+
217
+ def current_user
218
+ client.get_current_user
219
+ end
220
+
221
+ # Gets the authenticated users video with the given ID. It may be private.
222
+ def my_video(video_id)
223
+ client.get_my_video(video_id)
224
+ end
225
+
226
+ # Gets all videos
227
+ def my_videos(opts = {})
228
+ client.get_my_videos(opts)
229
+ end
230
+
231
+ # Gets all of the user's contacts/friends.
232
+ def my_contacts(opts = {})
233
+ client.get_my_contacts(opts)
234
+ end
235
+
236
+ # Send video message
237
+ def send_message(opts = {})
238
+ client.send_message(opts)
239
+ end
240
+
241
+ # Gets all of the user's messages/inbox.
242
+ def my_messages(opts = {})
243
+ client.get_my_messages(opts)
244
+ end
245
+
246
+ # Gets the user's watch history
247
+ def watch_history
248
+ client.get_watch_history
249
+ end
250
+
251
+ # Gets new subscription videos
252
+ def new_subscription_videos(user_id = nil)
253
+ client.new_subscription_videos(user_id)
254
+ end
255
+
256
+ private
257
+
258
+ def client
259
+ @client ||= YouTubeIt::Upload::VideoUpload.new(:username => @user, :password => @pass, :dev_key => @dev_key)
260
+ end
261
+
262
+ def calculate_offset(page, per_page)
263
+ page == 1 ? 1 : ((per_page * page) - per_page + 1)
264
+ end
265
+
266
+ def integer_or_default(value, default)
267
+ value = value.to_i
268
+ value > 0 ? value : default
269
+ end
270
+ end
271
+
272
+ class AuthSubClient < Client
273
+ def initialize *params
274
+ if params.first.is_a?(Hash)
275
+ hash_options = params.first
276
+ @authsub_token = hash_options[:token]
277
+ @dev_key = hash_options[:dev_key]
278
+ @client_id = hash_options[:client_id] || "youtube_it"
279
+ @legacy_debug_flag = hash_options[:debug]
280
+ else
281
+ puts "* warning: the method YouTubeIt::AuthSubClient.new(token, dev_key) is depricated, use YouTubeIt::AuthSubClient.new(:token => 'token', :dev_key => 'dev_key')"
282
+ @authsub_token = params.shift
283
+ @dev_key = params.shift
284
+ @client_id = params.shift || "youtube_it"
285
+ @legacy_debug_flag = params.shift
286
+ end
287
+ end
288
+
289
+ def create_session_token
290
+ response = nil
291
+ session_token_url = "/accounts/AuthSubSessionToken"
292
+
293
+ http_connection do |session|
294
+ response = session.get2('https://%s' % session_token_url,session_token_header).body
295
+ end
296
+ @authsub_token = response.sub('Token=','')
297
+ end
298
+
299
+ def revoke_session_token
300
+ response = nil
301
+ session_token_url = "/accounts/AuthSubRevokeToken"
302
+
303
+ http_connection do |session|
304
+ response = session.get2('https://%s' % session_token_url,session_token_header).code
305
+ end
306
+ response.to_s == '200' ? true : false
307
+ end
308
+
309
+ def session_token_info
310
+ response = nil
311
+ session_token_url = "/accounts/AuthSubTokenInfo"
312
+
313
+ http_connection do |session|
314
+ response = session.get2('https://%s' % session_token_url,session_token_header)
315
+ end
316
+ {:code => response.code, :body => response.body }
317
+ end
318
+
319
+ private
320
+ def client
321
+ @client ||= YouTubeIt::Upload::VideoUpload.new(:dev_key => @dev_key, :authsub_token => @authsub_token)
322
+ end
323
+
324
+ def session_token_header
325
+ {
326
+ "Content-Type" => "application/x-www-form-urlencoded",
327
+ "Authorization" => "AuthSub token=#{@authsub_token}"
328
+ }
329
+ end
330
+
331
+ def http_connection
332
+ http = Net::HTTP.new("www.google.com")
333
+ http.set_debug_output(logger) if @http_debugging
334
+ http.start do |session|
335
+ yield(session)
336
+ end
337
+ end
338
+ end
339
+
340
+ class OAuthClient < Client
341
+ def initialize *params
342
+ if params.first.is_a?(Hash)
343
+ hash_options = params.first
344
+ @consumer_key = hash_options[:consumer_key]
345
+ @consumer_secret = hash_options[:consumer_secret]
346
+ @user = hash_options[:username]
347
+ @dev_key = hash_options[:dev_key]
348
+ @client_id = hash_options[:client_id] || "youtube_it"
349
+ @legacy_debug_flag = hash_options[:debug]
350
+ else
351
+ puts "* warning: the method YouTubeIt::OAuthClient.new(consumer_key, consumer_secrect, dev_key) is depricated, use YouTubeIt::OAuthClient.new(:consumer_key => 'consumer key', :consumer_secret => 'consumer secret', :dev_key => 'dev_key')"
352
+ @consumer_key = params.shift
353
+ @consumer_secret = params.shift
354
+ @dev_key = params.shift
355
+ @user = params.shift
356
+ @client_id = params.shift || "youtube_it"
357
+ @legacy_debug_flag = params.shift
358
+ end
359
+ end
360
+
361
+ def consumer
362
+ @consumer ||= ::OAuth::Consumer.new(@consumer_key,@consumer_secret,{
363
+ :site=>"https://www.google.com",
364
+ :request_token_path=>"/accounts/OAuthGetRequestToken",
365
+ :authorize_path=>"/accounts/OAuthAuthorizeToken",
366
+ :access_token_path=>"/accounts/OAuthGetAccessToken"})
367
+ end
368
+
369
+ def request_token(callback)
370
+ @request_token = consumer.get_request_token({:oauth_callback => callback},{:scope => "http://gdata.youtube.com"})
371
+ end
372
+
373
+ def access_token
374
+ @access_token = ::OAuth::AccessToken.new(consumer, @atoken, @asecret)
375
+ end
376
+
377
+ def config_token
378
+ {
379
+ :consumer_key => @consumer_key,
380
+ :consumer_secret => @consumer_secret,
381
+ :token => @atoken,
382
+ :token_secret => @asecret
383
+ }
384
+ end
385
+
386
+ def authorize_from_request(rtoken,rsecret,verifier)
387
+ request_token = ::OAuth::RequestToken.new(consumer,rtoken,rsecret)
388
+ access_token = request_token.get_access_token({:oauth_verifier => verifier})
389
+ @atoken,@asecret = access_token.token, access_token.secret
390
+ end
391
+
392
+ def authorize_from_access(atoken,asecret)
393
+ @atoken,@asecret = atoken, asecret
394
+ end
395
+
396
+ def current_user
397
+ profile = access_token.get("http://gdata.youtube.com/feeds/api/users/default")
398
+ response_code = profile.code.to_i
399
+
400
+ if response_code/10 == 20 # success
401
+ REXML::Document.new(profile.body).elements["entry"].elements['author'].elements['name'].text
402
+ elsif response_code == 403 || response_code == 401 # auth failure
403
+ raise YouTubeIt::Upload::AuthenticationError.new(profile.inspect, response_code)
404
+ else
405
+ raise YouTubeIt::Upload::UploadError.new(profile.inspect, response_code)
406
+ end
407
+ end
408
+
409
+ private
410
+
411
+ def client
412
+ # IMPORTANT: make sure authorize_from_access is called before client is fetched
413
+ @client ||= YouTubeIt::Upload::VideoUpload.new(:username => current_user, :dev_key => @dev_key, :access_token => access_token, :config_token => config_token)
414
+ end
415
+
416
+ end
417
+
418
+ class OAuth2Client < YouTubeIt::Client
419
+ def initialize(options)
420
+ @client_id = options[:client_id]
421
+ @client_secret = options[:client_secret]
422
+ @client_access_token = options[:client_access_token]
423
+ @client_refresh_token = options[:client_refresh_token]
424
+ @client_token_expires_at = options[:client_token_expires_at]
425
+ @dev_key = options[:dev_key]
426
+ @legacy_debug_flag = options[:debug]
427
+ end
428
+
429
+ def oauth_client
430
+ @oauth_client ||= ::OAuth2::Client.new(@client_id, @client_secret,
431
+ :site => "https://accounts.google.com",
432
+ :authorize_url => '/o/oauth2/auth',
433
+ :token_url => '/o/oauth2/token')
434
+ end
435
+
436
+ def access_token
437
+ @access_token ||= ::OAuth2::AccessToken.new(oauth_client, @client_access_token, :refresh_token => @client_refresh_token, :expires_at => @client_token_expires_at)
438
+ end
439
+
440
+ def refresh_access_token!
441
+ new_access_token = access_token.refresh!
442
+ require 'thread' unless Thread.respond_to?(:exclusive)
443
+ Thread.exclusive do
444
+ @access_token = new_access_token
445
+ @client = nil
446
+ end
447
+ @access_token
448
+ end
449
+
450
+ def current_user
451
+ profile = access_token.get("http://gdata.youtube.com/feeds/api/users/default")
452
+ response_code = profile.status
453
+
454
+ if response_code/10 == 20 # success
455
+ REXML::Document.new(profile.body).elements["entry"].elements['author'].elements['name'].text
456
+ elsif response_code == 403 || response_code == 401 # auth failure
457
+ raise YouTubeIt::Upload::AuthenticationError.new(profile.inspect, response_code)
458
+ else
459
+ raise YouTubeIt::Upload::UploadError.new(profile.inspect, response_code)
460
+ end
461
+ end
462
+
463
+ private
464
+
465
+ def client
466
+ @client ||= YouTubeIt::Upload::VideoUpload.new(:username => current_user, :access_token => access_token, :dev_key => @dev_key)
467
+ end
468
+ end
469
+ end