yt 0.4.10 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +1 -1
  3. data/HISTORY.md +10 -0
  4. data/README.md +7 -3
  5. data/TODO.md +6 -2
  6. data/lib/yt/actions/delete.rb +1 -1
  7. data/lib/yt/actions/delete_all.rb +1 -1
  8. data/lib/yt/actions/insert.rb +1 -1
  9. data/lib/yt/actions/list.rb +8 -2
  10. data/lib/yt/actions/update.rb +1 -1
  11. data/lib/yt/associations.rb +1 -0
  12. data/lib/yt/associations/details_sets.rb +1 -1
  13. data/lib/yt/associations/ids.rb +20 -0
  14. data/lib/yt/associations/snippets.rb +1 -1
  15. data/lib/yt/associations/subscriptions.rb +2 -2
  16. data/lib/yt/associations/user_infos.rb +1 -1
  17. data/lib/yt/collections/base.rb +1 -0
  18. data/lib/yt/collections/details_sets.rb +0 -1
  19. data/lib/yt/collections/ids.rb +22 -0
  20. data/lib/yt/collections/playlist_items.rb +3 -3
  21. data/lib/yt/collections/playlists.rb +0 -1
  22. data/lib/yt/collections/snippets.rb +0 -1
  23. data/lib/yt/collections/subscriptions.rb +4 -3
  24. data/lib/yt/collections/user_infos.rb +0 -2
  25. data/lib/yt/collections/videos.rb +0 -1
  26. data/lib/yt/errors/base.rb +43 -0
  27. data/lib/yt/errors/error.rb +8 -0
  28. data/lib/yt/errors/failed.rb +17 -0
  29. data/lib/yt/errors/no_items.rb +17 -0
  30. data/lib/yt/errors/unauthenticated.rb +34 -0
  31. data/lib/yt/models/account.rb +1 -17
  32. data/lib/yt/models/base.rb +1 -0
  33. data/lib/yt/models/description.rb +11 -35
  34. data/lib/yt/models/id.rb +4 -0
  35. data/lib/yt/models/request.rb +102 -0
  36. data/lib/yt/models/resource.rb +13 -2
  37. data/lib/yt/models/subscription.rb +3 -2
  38. data/lib/yt/models/url.rb +88 -0
  39. data/lib/yt/version.rb +1 -1
  40. data/spec/associations/device_auth/details_sets_spec.rb +1 -1
  41. data/spec/associations/device_auth/ids_spec.rb +19 -0
  42. data/spec/associations/device_auth/playlist_items_spec.rb +3 -3
  43. data/spec/associations/device_auth/snippets_spec.rb +2 -2
  44. data/spec/associations/device_auth/user_infos_spec.rb +7 -2
  45. data/spec/associations/server_auth/details_sets_spec.rb +4 -4
  46. data/spec/associations/server_auth/ids_spec.rb +18 -0
  47. data/spec/associations/server_auth/snippets_spec.rb +2 -2
  48. data/spec/collections/playlist_items_spec.rb +25 -5
  49. data/spec/collections/subscriptions_spec.rb +6 -4
  50. data/spec/errors/failed_spec.rb +9 -0
  51. data/spec/errors/no_items_spec.rb +9 -0
  52. data/spec/errors/unauthenticated_spec.rb +9 -0
  53. data/spec/models/account_spec.rb +0 -0
  54. data/spec/models/description_spec.rb +2 -2
  55. data/spec/models/request_spec.rb +29 -0
  56. data/spec/models/resource_spec.rb +21 -0
  57. data/spec/models/subscription_spec.rb +6 -4
  58. data/spec/models/url_spec.rb +72 -0
  59. data/spec/support/fail_matcher.rb +14 -0
  60. metadata +32 -4
  61. data/lib/yt/actions/request.rb +0 -112
  62. data/lib/yt/actions/request_error.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b768b3bf2f8df33d6464c3e5576d216eb56a40b6
4
- data.tar.gz: 5d9157b38126cdae31aed2bd15192978a1656339
3
+ metadata.gz: 618a1921ea46452fc9fcb6cc28cf44ab44d3370a
4
+ data.tar.gz: 5fc33026503612fa0387a7ce7ceabc5251f254af
5
5
  SHA512:
6
- metadata.gz: 536cbae00ed870974c980366668f9a87f0bc7c8e0f8d1cf506fe077b307a5fb1fcb1d49486e7d444f40af1fff1d86c30719c22934a338cc8fb45891c213193ba
7
- data.tar.gz: f8df59e9464f5f771266136789fa73bd2faa6649422a183aaba54e4987b1ca12b1fe5fa79daaeb5b8af3bbb360691292269c90814caaddb09afd3a52db665a53
6
+ metadata.gz: 07759995fcf879ae9a3e4277882113eabb423bbc9da0a3f8fc0b815a06cdb61a0c72a5e69960c652e3d6127e004d94c18a120797097b78e6aaf7beb7148bdd44
7
+ data.tar.gz: c5c246314f34b478b7014a79277bdce941b69daeb9c25e61f6ad4551fd5b6cfacfad283fcfe70c7fda91bf3c0a60ea8ce82852699f31b99a47a20a8dd48a9809
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- yt (0.4.10)
4
+ yt (0.5.3)
5
5
  activesupport
6
6
 
7
7
  GEM
data/HISTORY.md CHANGED
@@ -1,3 +1,13 @@
1
+ v0.5 - 2014/05/16
2
+ -----------------
3
+
4
+ * More complete custom exception Yt::Error, with code, body and curl
5
+ * Replace `:ignore_not_found` and `:ignore_duplicates` with `:ignore_errors`
6
+ * Allow resources to be initialized with a url, such as Yt::Resource.new url: 'youtube.com/fullscreen'
7
+ * Add `has_one :id` to resources, to retrieve the ID of resources initialized by URL
8
+ * Raise an error if some `has_one` associations are not found (id, snippet, details set, user info)
9
+ * Don't check for the right :scope if Account is initialized with credentials
10
+
1
11
  v0.4 - 2014/05/09
2
12
  --------------------
3
13
 
data/README.md CHANGED
@@ -302,7 +302,7 @@ To install on your system, run
302
302
 
303
303
  To use inside a bundled Ruby project, add this line to the Gemfile:
304
304
 
305
- gem 'yt', '~> 0.4.10'
305
+ gem 'yt', '~> 0.5.3'
306
306
 
307
307
  Since the gem follows [Semantic Versioning](http://semver.org),
308
308
  indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
@@ -389,8 +389,12 @@ export YT_TEST_DEVICE_CLIENT_SECRET="1234567890"
389
389
  export YT_TEST_SERVER_API_KEY="123456789012345678901234567890"
390
390
  ```
391
391
 
392
- [ TODO: Complete this section. Explain how get and store the refresh token,
393
- making sure all the required scopes are authorized. ]
392
+ [ TODO:
393
+ Complete this section.
394
+ Explain how get and store the refresh token, making sure all the required scopes are authorized:
395
+ 'https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
396
+ ]
397
+
394
398
 
395
399
 
396
400
  How to contribute
data/TODO.md CHANGED
@@ -1,3 +1,9 @@
1
+ * methods like Yt::Account.new(params = {}) should use HashWithIndifferentAccess
2
+ * add canonical_url to Resource, then use it in promo
3
+
4
+ * once in a while, Google fails with 500 error and just retrying after some
5
+ seconds fixes it, so we should retry every 500 at least
6
+
1
7
  * find by url (either video or channel or.. playlist)
2
8
  * Google accounts?
3
9
  * ENV support
@@ -7,5 +13,3 @@
7
13
  * operations like subscribe that require authentication should not fail if
8
14
  called on Yt::Channel without auth but, similarly to account, show the prompt
9
15
  or ask for the device code
10
-
11
- * scope
@@ -1,4 +1,4 @@
1
- require 'yt/actions/request'
1
+ require 'yt/models/request'
2
2
 
3
3
  module Yt
4
4
  module Actions
@@ -1,5 +1,5 @@
1
1
  require 'yt/actions/list'
2
- require 'yt/actions/request'
2
+ require 'yt/models/request'
3
3
 
4
4
  module Yt
5
5
  module Actions
@@ -1,4 +1,4 @@
1
- require 'yt/actions/request'
1
+ require 'yt/models/request'
2
2
 
3
3
  module Yt
4
4
  module Actions
@@ -1,12 +1,18 @@
1
- require 'yt/actions/request'
1
+ require 'yt/models/request'
2
+ require 'yt/errors/no_items'
2
3
 
3
4
  module Yt
4
5
  module Actions
5
6
  module List
6
-
7
7
  delegate :count, :first, :any?, :each, :map, :find, to: :list
8
8
  alias size count
9
9
 
10
+ def first!
11
+ first.tap do |item|
12
+ raise Errors::NoItems unless item
13
+ end
14
+ end
15
+
10
16
  private
11
17
 
12
18
  def list
@@ -1,4 +1,4 @@
1
- require 'yt/actions/request'
1
+ require 'yt/models/request'
2
2
 
3
3
  module Yt
4
4
  module Actions
@@ -13,6 +13,7 @@ module Yt
13
13
  autoload :Annotations
14
14
  autoload :Channels
15
15
  autoload :DetailsSets
16
+ autoload :Ids
16
17
  autoload :PlaylistItems
17
18
  autoload :Playlists
18
19
  autoload :Ratings
@@ -7,7 +7,7 @@ module Yt
7
7
  # YouTube resources with content details are: videos.
8
8
  module DetailsSets
9
9
  def details_set
10
- @detail_set ||= details_sets.first
10
+ @detail_set ||= details_sets.first!
11
11
  end
12
12
 
13
13
  private
@@ -0,0 +1,20 @@
1
+ require 'yt/collections/ids'
2
+
3
+ module Yt
4
+ module Associations
5
+ # Provides the `has_one :id` method to YouTube resources, which
6
+ # allows to retrieve the id of a resource knowing only its username.
7
+ # YouTube resources with ids are: resources.
8
+ module Ids
9
+ def id
10
+ @id ||= ids.first!
11
+ end
12
+
13
+ private
14
+
15
+ def ids
16
+ @ids ||= Collections::Ids.of self
17
+ end
18
+ end
19
+ end
20
+ end
@@ -7,7 +7,7 @@ module Yt
7
7
  # YouTube resources with content details are: videos and channels.
8
8
  module Snippets
9
9
  def snippet
10
- @snippet ||= snippets.first
10
+ @snippet ||= snippets.first!
11
11
  end
12
12
 
13
13
  private
@@ -15,7 +15,7 @@ module Yt
15
15
  end
16
16
 
17
17
  def subscribe
18
- subscriptions.insert ignore_duplicates: true
18
+ subscriptions.insert ignore_errors: true
19
19
  end
20
20
 
21
21
  def subscribe!
@@ -23,7 +23,7 @@ module Yt
23
23
  end
24
24
 
25
25
  def unsubscribe
26
- subscriptions.delete_all({}, ignore_not_found: true)
26
+ subscriptions.delete_all({}, ignore_errors: true)
27
27
  end
28
28
 
29
29
  def unsubscribe!
@@ -7,7 +7,7 @@ module Yt
7
7
  # YouTube resources with user infos are: accounts.
8
8
  module UserInfos
9
9
  def user_info
10
- @user_info ||= user_infos.first
10
+ @user_info ||= user_infos.first!
11
11
  end
12
12
 
13
13
  private
@@ -1,6 +1,7 @@
1
1
  require 'yt/actions/delete_all'
2
2
  require 'yt/actions/insert'
3
3
  require 'yt/actions/list'
4
+ require 'yt/errors/error'
4
5
 
5
6
  module Yt
6
7
  module Collections
@@ -14,7 +14,6 @@ module Yt
14
14
  def list_params
15
15
  super.tap do |params|
16
16
  params[:params] = {maxResults: 50, part: 'contentDetails', id: @parent.id}
17
- params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
18
17
  params[:path] = '/youtube/v3/videos'
19
18
  end
20
19
  end
@@ -0,0 +1,22 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/id'
3
+
4
+ module Yt
5
+ module Collections
6
+ class Ids < Base
7
+
8
+ private
9
+
10
+ def new_item(data)
11
+ Yt::Id.new data['id']
12
+ end
13
+
14
+ def list_params
15
+ super.tap do |params|
16
+ params[:params] = {forUsername: @parent.username, part: 'id'}
17
+ params[:path] = "/youtube/v3/#{@parent.kind.pluralize}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -11,8 +11,9 @@ module Yt
11
11
  resource["#{attrs[:kind]}Id"] = attrs[:id]
12
12
  snippet = {playlistId: @parent.id, resourceId: resource}
13
13
  do_insert body: {snippet: snippet}, params: {part: 'snippet,status'}
14
- rescue Yt::RequestError => error
15
- raise error unless options[:ignore_errors] && (error.reasons.include?('videoNotFound') || error.reasons.include?('forbidden'))
14
+ rescue Errors::Base => error
15
+ ignorable_errors = error.reasons & ['videoNotFound', 'forbidden']
16
+ raise error unless options[:ignore_errors] && ignorable_errors.any?
16
17
  end
17
18
 
18
19
  def delete_all(params = {})
@@ -28,7 +29,6 @@ module Yt
28
29
  def list_params
29
30
  super.tap do |params|
30
31
  params[:params] = {maxResults: 50, part: 'snippet,status', playlistId: @parent.id}
31
- params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
32
32
  end
33
33
  end
34
34
 
@@ -32,7 +32,6 @@ module Yt
32
32
  def list_params
33
33
  super.tap do |params|
34
34
  params[:params] = {maxResults: 50, part: 'snippet,status', channelId: @parent.id}
35
- params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
36
35
  end
37
36
  end
38
37
 
@@ -15,7 +15,6 @@ module Yt
15
15
  parents_path = @parent.class.to_s.demodulize.underscore.pluralize
16
16
  super.tap do |params|
17
17
  params[:params] = {id: @parent.id, part: 'snippet'}
18
- params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
19
18
  params[:path] = "/youtube/v3/#{parents_path}"
20
19
  end
21
20
  end
@@ -8,8 +8,9 @@ module Yt
8
8
  def insert(options = {})
9
9
  throttle
10
10
  do_insert
11
- rescue Yt::RequestError => error
12
- raise error unless options[:ignore_duplicates] && error.reasons.include?('subscriptionDuplicate')
11
+ rescue Errors::Base => error
12
+ ignorable_errors = error.reasons & ['subscriptionDuplicate']
13
+ raise error unless options[:ignore_errors] && ignorable_errors.any?
13
14
  end
14
15
 
15
16
  def delete_all(params = {}, options = {})
@@ -28,7 +29,7 @@ module Yt
28
29
  # To overcome this, if we have just updated the subscription, we must
29
30
  # wait some time before requesting it again.
30
31
  #
31
- def throttle(seconds = 9)
32
+ def throttle(seconds = 10)
32
33
  @last_changed_at ||= Time.now - seconds
33
34
  wait = [@last_changed_at - Time.now + seconds, 0].max
34
35
  sleep wait
@@ -14,8 +14,6 @@ module Yt
14
14
  def list_params
15
15
  super.tap do |params|
16
16
  params[:path] = '/oauth2/v2/userinfo'
17
- # TODO: Remove youtube from here, implement incremental scopes
18
- params[:scope] = 'https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
19
17
  end
20
18
  end
21
19
 
@@ -14,7 +14,6 @@ module Yt
14
14
  def list_params
15
15
  super.tap do |params|
16
16
  params[:params] = {channelId: @parent.id, type: :video, maxResults: 50, part: 'snippet'}
17
- params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
18
17
  params[:path] = '/youtube/v3/search'
19
18
  end
20
19
  end
@@ -0,0 +1,43 @@
1
+ module Yt
2
+ module Errors
3
+ class Base < StandardError
4
+ def initialize(msg = nil)
5
+ @msg = msg
6
+ super msg
7
+ end
8
+
9
+ def reasons
10
+ errors = response_body.fetch('error', {}).fetch 'errors', []
11
+ errors.map{|e| e['reason']}
12
+ end
13
+
14
+ private
15
+
16
+ def response_code
17
+ Integer(response['code']) rescue nil
18
+ end
19
+
20
+ def response_body
21
+ body = response['body']
22
+ JSON(body) rescue {body: body}
23
+ end
24
+
25
+ def request_curl
26
+ # TODO.. continue
27
+ %Q{curl -X #{request['method'].to_s.upcase} "#{request['url']}"}
28
+ end
29
+
30
+ def request
31
+ json['request'] || {}
32
+ end
33
+
34
+ def response
35
+ json['response'] || {}
36
+ end
37
+
38
+ def json
39
+ JSON(@msg) rescue {}
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,8 @@
1
+ require 'yt/errors/base'
2
+
3
+ # Just an alias in the main namespace, so external code can `rescue Yt::Error`
4
+ # rather than `rescue Yt::Error::Base`
5
+ module Yt
6
+ class Error < Errors::Base
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ require 'yt/errors/base'
2
+
3
+ module Yt
4
+ module Errors
5
+ class Failed < Base
6
+ def message
7
+ <<-MSG.gsub(/^ {6}/, '')
8
+ A request to YouTube API V3 failed (code #{response_code}):
9
+ #{response_body}
10
+
11
+ You can retry the same request manually by running:
12
+ #{request_curl}
13
+ MSG
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'yt/errors/base'
2
+
3
+ module Yt
4
+ module Errors
5
+ class NoItems < Base
6
+ def message
7
+ <<-MSG.gsub(/^ {6}/, '')
8
+ A request to YouTube API V3 returned no items (but some were expected):
9
+ #{response_body}
10
+
11
+ You can retry the same request manually by running:
12
+ #{request_curl}
13
+ MSG
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ require 'yt/errors/base'
2
+
3
+ module Yt
4
+ module Errors
5
+ class Unauthenticated < Base
6
+ def message
7
+ <<-MSG.gsub(/^ {6}/, '')
8
+ A request to YouTube API V3 was sent without the required authentication:
9
+
10
+ #{request_curl}
11
+
12
+ In order to perform this request, you need to register your app with
13
+ Google Developers Console (https://console.developers.google.com).
14
+
15
+ Make sure your app has access to the Google+ and YouTube APIs.
16
+ Generate a client ID, client secret and server API key, then pass their
17
+ values to Yt. One way of doing this is through an initializer:
18
+
19
+ Yt.configure do |config|
20
+ config.client_id = '1234567890.apps.googleusercontent.com'
21
+ config.client_secret = '1234567890'
22
+ config.api_key = '123456789012345678901234567890'
23
+ end
24
+
25
+ An alternative (but equivalent) way is throught environment variables:
26
+
27
+ export YT_CLIENT_ID="1234567890.apps.googleusercontent.com"
28
+ export YT_CLIENT_SECRET="1234567890"
29
+ export YT_API_KEY="123456789012345678901234567890"
30
+ MSG
31
+ end
32
+ end
33
+ end
34
+ end