yt 0.4.10 → 0.5.3

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.
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