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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/HISTORY.md +10 -0
- data/README.md +7 -3
- data/TODO.md +6 -2
- data/lib/yt/actions/delete.rb +1 -1
- data/lib/yt/actions/delete_all.rb +1 -1
- data/lib/yt/actions/insert.rb +1 -1
- data/lib/yt/actions/list.rb +8 -2
- data/lib/yt/actions/update.rb +1 -1
- data/lib/yt/associations.rb +1 -0
- data/lib/yt/associations/details_sets.rb +1 -1
- data/lib/yt/associations/ids.rb +20 -0
- data/lib/yt/associations/snippets.rb +1 -1
- data/lib/yt/associations/subscriptions.rb +2 -2
- data/lib/yt/associations/user_infos.rb +1 -1
- data/lib/yt/collections/base.rb +1 -0
- data/lib/yt/collections/details_sets.rb +0 -1
- data/lib/yt/collections/ids.rb +22 -0
- data/lib/yt/collections/playlist_items.rb +3 -3
- data/lib/yt/collections/playlists.rb +0 -1
- data/lib/yt/collections/snippets.rb +0 -1
- data/lib/yt/collections/subscriptions.rb +4 -3
- data/lib/yt/collections/user_infos.rb +0 -2
- data/lib/yt/collections/videos.rb +0 -1
- data/lib/yt/errors/base.rb +43 -0
- data/lib/yt/errors/error.rb +8 -0
- data/lib/yt/errors/failed.rb +17 -0
- data/lib/yt/errors/no_items.rb +17 -0
- data/lib/yt/errors/unauthenticated.rb +34 -0
- data/lib/yt/models/account.rb +1 -17
- data/lib/yt/models/base.rb +1 -0
- data/lib/yt/models/description.rb +11 -35
- data/lib/yt/models/id.rb +4 -0
- data/lib/yt/models/request.rb +102 -0
- data/lib/yt/models/resource.rb +13 -2
- data/lib/yt/models/subscription.rb +3 -2
- data/lib/yt/models/url.rb +88 -0
- data/lib/yt/version.rb +1 -1
- data/spec/associations/device_auth/details_sets_spec.rb +1 -1
- data/spec/associations/device_auth/ids_spec.rb +19 -0
- data/spec/associations/device_auth/playlist_items_spec.rb +3 -3
- data/spec/associations/device_auth/snippets_spec.rb +2 -2
- data/spec/associations/device_auth/user_infos_spec.rb +7 -2
- data/spec/associations/server_auth/details_sets_spec.rb +4 -4
- data/spec/associations/server_auth/ids_spec.rb +18 -0
- data/spec/associations/server_auth/snippets_spec.rb +2 -2
- data/spec/collections/playlist_items_spec.rb +25 -5
- data/spec/collections/subscriptions_spec.rb +6 -4
- data/spec/errors/failed_spec.rb +9 -0
- data/spec/errors/no_items_spec.rb +9 -0
- data/spec/errors/unauthenticated_spec.rb +9 -0
- data/spec/models/account_spec.rb +0 -0
- data/spec/models/description_spec.rb +2 -2
- data/spec/models/request_spec.rb +29 -0
- data/spec/models/resource_spec.rb +21 -0
- data/spec/models/subscription_spec.rb +6 -4
- data/spec/models/url_spec.rb +72 -0
- data/spec/support/fail_matcher.rb +14 -0
- metadata +32 -4
- data/lib/yt/actions/request.rb +0 -112
- data/lib/yt/actions/request_error.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 618a1921ea46452fc9fcb6cc28cf44ab44d3370a
|
4
|
+
data.tar.gz: 5fc33026503612fa0387a7ce7ceabc5251f254af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07759995fcf879ae9a3e4277882113eabb423bbc9da0a3f8fc0b815a06cdb61a0c72a5e69960c652e3d6127e004d94c18a120797097b78e6aaf7beb7148bdd44
|
7
|
+
data.tar.gz: c5c246314f34b478b7014a79277bdce941b69daeb9c25e61f6ad4551fd5b6cfacfad283fcfe70c7fda91bf3c0a60ea8ce82852699f31b99a47a20a8dd48a9809
|
data/Gemfile.lock
CHANGED
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.
|
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:
|
393
|
-
|
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
|
data/lib/yt/actions/delete.rb
CHANGED
data/lib/yt/actions/insert.rb
CHANGED
data/lib/yt/actions/list.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
-
require 'yt/
|
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
|
data/lib/yt/actions/update.rb
CHANGED
data/lib/yt/associations.rb
CHANGED
@@ -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
|
@@ -15,7 +15,7 @@ module Yt
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def subscribe
|
18
|
-
subscriptions.insert
|
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({},
|
26
|
+
subscriptions.delete_all({}, ignore_errors: true)
|
27
27
|
end
|
28
28
|
|
29
29
|
def unsubscribe!
|
data/lib/yt/collections/base.rb
CHANGED
@@ -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
|
15
|
-
|
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
|
|
@@ -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
|
12
|
-
|
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 =
|
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,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
|