yt 0.4.10 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|