yt 0.5.4 → 0.5.5
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 +3 -0
- data/README.md +2 -4
- data/bin/yt +0 -7
- data/lib/yt/actions/delete.rb +3 -3
- data/lib/yt/actions/insert.rb +3 -3
- data/lib/yt/actions/list.rb +4 -6
- data/lib/yt/actions/update.rb +7 -5
- data/lib/yt/associations/authentications.rb +114 -0
- data/lib/yt/associations.rb +1 -0
- data/lib/yt/collections/annotations.rb +2 -2
- data/lib/yt/collections/authentications.rb +42 -0
- data/lib/yt/collections/base.rb +1 -1
- data/lib/yt/collections/playlist_items.rb +1 -1
- data/lib/yt/collections/snippets.rb +1 -2
- data/lib/yt/collections/subscriptions.rb +1 -1
- data/lib/yt/collections/user_infos.rb +2 -2
- data/lib/yt/config.rb +0 -2
- data/lib/yt/errors/missing_auth.rb +50 -0
- data/lib/yt/errors/no_items.rb +5 -9
- data/lib/yt/errors/request_error.rb +52 -0
- data/lib/yt/models/account.rb +17 -44
- data/lib/yt/models/annotation.rb +117 -115
- data/lib/yt/models/authentication.rb +27 -0
- data/lib/yt/models/base.rb +9 -5
- data/lib/yt/models/channel.rb +6 -4
- data/lib/yt/models/configuration.rb +27 -35
- data/lib/yt/models/description.rb +61 -59
- data/lib/yt/models/details_set.rb +26 -24
- data/lib/yt/models/id.rb +3 -1
- data/lib/yt/models/playlist.rb +38 -36
- data/lib/yt/models/playlist_item.rb +29 -27
- data/lib/yt/models/rating.rb +18 -16
- data/lib/yt/models/request.rb +95 -73
- data/lib/yt/models/resource.rb +19 -17
- data/lib/yt/models/snippet.rb +39 -37
- data/lib/yt/models/status.rb +20 -18
- data/lib/yt/models/subscription.rb +24 -22
- data/lib/yt/models/url.rb +68 -68
- data/lib/yt/models/user_info.rb +51 -49
- data/lib/yt/models/video.rb +6 -4
- data/lib/yt/version.rb +1 -1
- data/spec/associations/device_auth/authentications_spec.rb +78 -0
- data/spec/associations/device_auth/channels_spec.rb +2 -4
- data/spec/associations/device_auth/details_sets_spec.rb +4 -5
- data/spec/associations/device_auth/ids_spec.rb +2 -3
- data/spec/associations/device_auth/playlist_items_spec.rb +3 -4
- data/spec/associations/device_auth/playlists_spec.rb +14 -15
- data/spec/associations/device_auth/ratings_spec.rb +2 -4
- data/spec/associations/device_auth/snippets_spec.rb +5 -7
- data/spec/associations/device_auth/subscriptions_spec.rb +2 -4
- data/spec/associations/device_auth/user_infos_spec.rb +2 -5
- data/spec/associations/device_auth/videos_spec.rb +3 -5
- data/spec/associations/server_auth/details_sets_spec.rb +1 -1
- data/spec/associations/server_auth/ids_spec.rb +1 -1
- data/spec/associations/server_auth/playlist_items_spec.rb +1 -1
- data/spec/associations/server_auth/playlists_spec.rb +1 -1
- data/spec/associations/server_auth/snippets_spec.rb +1 -1
- data/spec/associations/server_auth/videos_spec.rb +1 -1
- data/spec/collections/playlist_items_spec.rb +3 -4
- data/spec/collections/subscriptions_spec.rb +2 -3
- data/spec/errors/missing_auth_spec.rb +10 -0
- data/spec/errors/no_items_spec.rb +2 -1
- data/spec/errors/request_error_spec.rb +18 -0
- data/spec/models/configuration_spec.rb +0 -17
- data/spec/models/description_spec.rb +5 -5
- data/spec/models/request_spec.rb +1 -7
- data/spec/models/subscription_spec.rb +2 -3
- data/spec/models/url_spec.rb +6 -6
- data/spec/support/fail_matcher.rb +1 -1
- data/spec/support/global_hooks.rb +33 -0
- metadata +15 -14
- data/lib/yt/errors/base.rb +0 -43
- data/lib/yt/errors/error.rb +0 -8
- data/lib/yt/errors/failed.rb +0 -17
- data/lib/yt/errors/unauthenticated.rb +0 -34
- data/spec/errors/failed_spec.rb +0 -9
- data/spec/errors/unauthenticated_spec.rb +0 -9
- data/spec/support/device_app.rb +0 -15
- data/spec/support/server_app.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c609731e55cd81b71dfbc3f53f47147dac54b51
|
4
|
+
data.tar.gz: 0993daf654fda172520af7d6d01e8620ff66fbf6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60d5bf311041934ec83bd36cfff50905d04e3ef8444f90e8be39ad3249da24618ef308bdee47688785a5bdc8d8024d91dc2a253a44d3cf204d331e6209eb66cb
|
7
|
+
data.tar.gz: fea7bc7fb323a51c21be93a0108e8d3350f808c8a4bb56cc956d2db5d4cf8c720fe6bdb8e492f39f65337d76d8843edf9898cd2464f9d354adc9ca089d0a834b
|
data/Gemfile.lock
CHANGED
data/HISTORY.md
CHANGED
@@ -7,6 +7,9 @@ v0.5 - 2014/05/16
|
|
7
7
|
* Add `has_one :id` to resources, to retrieve the ID of resources initialized by URL
|
8
8
|
* Raise an error if some `has_one` associations are not found (id, snippet, details set, user info)
|
9
9
|
* Don't check for the right :scope if Account is initialized with credentials
|
10
|
+
* Move models in Yt::Models but still auto-include them in the main namespace
|
11
|
+
* New Authentication model to separate `access_token` and `refresh_token` from Account
|
12
|
+
* New types of Errors that render more verbose errors and the failing request in cURL syntax
|
10
13
|
|
11
14
|
v0.4 - 2014/05/09
|
12
15
|
--------------------
|
data/README.md
CHANGED
@@ -8,6 +8,7 @@ Yt helps you write apps that need to interact with the YouTube API V3.
|
|
8
8
|
[](https://travis-ci.org/Fullscreen/yt)
|
9
9
|
[](https://coveralls.io/r/Fullscreen/yt)
|
10
10
|
[](https://codeclimate.com/github/Fullscreen/yt)
|
11
|
+
[](http://inch-pages.github.io/github/Fullscreen/yt)
|
11
12
|
|
12
13
|
After [registering your app](#registering-your-app), you can run commands like:
|
13
14
|
|
@@ -169,7 +170,6 @@ Next, add the following snippet of code to the initializer of your app:
|
|
169
170
|
|
170
171
|
```ruby
|
171
172
|
Yt.configure do |config|
|
172
|
-
config.scenario = :server_app
|
173
173
|
config.api_key = '123456789012345678901234567890'
|
174
174
|
end
|
175
175
|
```
|
@@ -246,7 +246,6 @@ refresh token, then add the following snippet of code to the initializer of your
|
|
246
246
|
|
247
247
|
```ruby
|
248
248
|
Yt.configure do |config|
|
249
|
-
config.scenario = :device_app
|
250
249
|
config.client_id = '1234567890.apps.googleusercontent.com'
|
251
250
|
config.client_secret = '1234567890'
|
252
251
|
end
|
@@ -282,7 +281,6 @@ is equivalent to configuration your app with the initializer:
|
|
282
281
|
|
283
282
|
```ruby
|
284
283
|
Yt.configure do |config|
|
285
|
-
config.scenario = :device_app
|
286
284
|
config.client_id = '1234567890.apps.googleusercontent.com'
|
287
285
|
config.client_secret = '1234567890'
|
288
286
|
config.api_key = '123456789012345678901234567890'
|
@@ -302,7 +300,7 @@ To install on your system, run
|
|
302
300
|
|
303
301
|
To use inside a bundled Ruby project, add this line to the Gemfile:
|
304
302
|
|
305
|
-
gem 'yt', '~> 0.5.
|
303
|
+
gem 'yt', '~> 0.5.5'
|
306
304
|
|
307
305
|
Since the gem follows [Semantic Versioning](http://semver.org),
|
308
306
|
indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
|
data/bin/yt
CHANGED
@@ -9,13 +9,6 @@ end
|
|
9
9
|
|
10
10
|
############################
|
11
11
|
|
12
|
-
Yt.configure do |config|
|
13
|
-
config.scenario = :server_app
|
14
|
-
config.api_key = ENV['YT_TEST_APP_SERVER_API_KEY']
|
15
|
-
end
|
16
|
-
|
17
|
-
############################
|
18
|
-
|
19
12
|
channel = Yt::Channel.new id: ARGV[0] || 'UCxO1tY8h1AhOz0T4ENwmpow'
|
20
13
|
|
21
14
|
puts "Title: #{channel.title}"
|
data/lib/yt/actions/delete.rb
CHANGED
@@ -7,16 +7,16 @@ module Yt
|
|
7
7
|
private
|
8
8
|
|
9
9
|
def do_delete(extra_delete_params = {})
|
10
|
-
request = Request.new delete_params.merge(extra_delete_params)
|
10
|
+
request = Yt::Request.new delete_params.merge(extra_delete_params)
|
11
11
|
response = request.run
|
12
|
-
raise unless response.is_a? Net::HTTPNoContent
|
13
12
|
yield response.body
|
14
13
|
end
|
15
14
|
|
16
15
|
def delete_params
|
17
|
-
|
16
|
+
{}.tap do |params|
|
18
17
|
params[:method] = :delete
|
19
18
|
params[:auth] = @auth
|
19
|
+
params[:expected_response] = Net::HTTPNoContent
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/yt/actions/insert.rb
CHANGED
@@ -7,17 +7,17 @@ module Yt
|
|
7
7
|
private
|
8
8
|
|
9
9
|
def do_insert(extra_insert_params = {})
|
10
|
-
request = Request.new insert_params.merge(extra_insert_params)
|
10
|
+
request = Yt::Request.new insert_params.merge(extra_insert_params)
|
11
11
|
response = request.run
|
12
|
-
raise unless response.is_a? Net::HTTPOK
|
13
12
|
@items = []
|
14
13
|
new_item response.body
|
15
14
|
end
|
16
15
|
|
17
16
|
def insert_params
|
18
|
-
|
17
|
+
{}.tap do |params|
|
19
18
|
params[:method] = :post
|
20
19
|
params[:auth] = @auth
|
20
|
+
params[:expected_response] = Net::HTTPOK
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
data/lib/yt/actions/list.rb
CHANGED
@@ -8,9 +8,7 @@ module Yt
|
|
8
8
|
alias size count
|
9
9
|
|
10
10
|
def first!
|
11
|
-
first.tap
|
12
|
-
raise Errors::NoItems unless item
|
13
|
-
end
|
11
|
+
first.tap{|item| raise Errors::NoItems unless item}
|
14
12
|
end
|
15
13
|
|
16
14
|
private
|
@@ -50,9 +48,8 @@ module Yt
|
|
50
48
|
end
|
51
49
|
|
52
50
|
def fetch_page(params = {})
|
53
|
-
request = Request.new params
|
51
|
+
request = Yt::Request.new params
|
54
52
|
response = request.run
|
55
|
-
raise unless response.is_a? Net::HTTPOK
|
56
53
|
token = response.body['nextPageToken']
|
57
54
|
items = response.body.fetch 'items', []
|
58
55
|
{items: items, token: token}
|
@@ -61,10 +58,11 @@ module Yt
|
|
61
58
|
def list_params
|
62
59
|
path = "/youtube/v3/#{self.class.to_s.demodulize.camelize :lower}"
|
63
60
|
|
64
|
-
|
61
|
+
{}.tap do |params|
|
65
62
|
params[:method] = :get
|
66
63
|
params[:auth] = @auth
|
67
64
|
params[:path] = path
|
65
|
+
params[:exptected_response] = Net::HTTPOK
|
68
66
|
end
|
69
67
|
end
|
70
68
|
end
|
data/lib/yt/actions/update.rb
CHANGED
@@ -3,18 +3,20 @@ require 'yt/models/request'
|
|
3
3
|
module Yt
|
4
4
|
module Actions
|
5
5
|
module Update
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def do_update(extra_update_params = {})
|
10
|
+
request = Yt::Request.new update_params.deep_merge(extra_update_params)
|
8
11
|
response = request.run
|
9
|
-
expected_response = options.fetch :expect, Net::HTTPNoContent
|
10
|
-
raise unless response.is_a? expected_response
|
11
12
|
yield response.body
|
12
13
|
end
|
13
14
|
|
14
15
|
def update_params
|
15
|
-
|
16
|
+
{}.tap do |params|
|
16
17
|
params[:method] = :put
|
17
18
|
params[:auth] = @auth
|
19
|
+
params[:expected_response] = Net::HTTPNoContent
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'yt/collections/authentications'
|
2
|
+
require 'yt/config'
|
3
|
+
require 'yt/errors/no_items'
|
4
|
+
require 'yt/errors/missing_auth'
|
5
|
+
|
6
|
+
module Yt
|
7
|
+
module Associations
|
8
|
+
# Provides the `has_one :access_token` method to YouTube resources, which
|
9
|
+
# allows to access to content detail set-specific methods like `access_token`.
|
10
|
+
# YouTube resources with access tokens are: accounts.
|
11
|
+
module Authentications
|
12
|
+
def authentication
|
13
|
+
@authentication = current_authentication
|
14
|
+
@authentication ||= new_authentication || refreshed_authentication!
|
15
|
+
end
|
16
|
+
|
17
|
+
def authentication_url
|
18
|
+
host = 'accounts.google.com'
|
19
|
+
path = '/o/oauth2/auth'
|
20
|
+
query = authentication_url_params.to_param
|
21
|
+
URI::HTTPS.build(host: host, path: path, query: query).to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def current_authentication
|
27
|
+
@authentication ||= Yt::Authentication.new current_data if @access_token
|
28
|
+
@authentication unless @authentication.nil? || @authentication.expired?
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_data
|
32
|
+
{}.tap do |data|
|
33
|
+
data['access_token'] = @access_token
|
34
|
+
data['expires_at'] = @expires_at
|
35
|
+
data['refresh_token'] = @refresh_token
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Tries to obtain an access token using the authorization code (which
|
40
|
+
# can only be used once). On failure, does not raise an error because
|
41
|
+
# the access token might still be retrieved with a refresh token.
|
42
|
+
def new_authentication
|
43
|
+
new_authentications.first!
|
44
|
+
rescue Errors::NoItems => error
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Tries to obtain an access token using the refresh token (which can
|
49
|
+
# be used multiple times). On failure, raise an error because there are
|
50
|
+
# no more options to obtain an access token.
|
51
|
+
def refreshed_authentication!
|
52
|
+
refreshed_authentications.first!
|
53
|
+
rescue Errors::NoItems => error
|
54
|
+
raise Errors::MissingAuth, error.to_param
|
55
|
+
end
|
56
|
+
|
57
|
+
def new_authentications
|
58
|
+
@new_authentications ||= Collections::Authentications.of(self).tap do |auth|
|
59
|
+
auth.auth_params = new_authentication_params
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def refreshed_authentications
|
64
|
+
@refreshed_authentications ||= Collections::Authentications.of(self).tap do |auth|
|
65
|
+
auth.auth_params = refreshed_authentication_params
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def authentication_url_params
|
70
|
+
{}.tap do |params|
|
71
|
+
params[:client_id] = client_id
|
72
|
+
params[:scope] = authentication_scope
|
73
|
+
params[:redirect_uri] = @redirect_uri
|
74
|
+
params[:response_type] = :code
|
75
|
+
params[:access_type] = :offline
|
76
|
+
# params[:include_granted_scopes] = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def authentication_scope
|
81
|
+
@scopes.map do |scope|
|
82
|
+
"https://www.googleapis.com/auth/#{scope}"
|
83
|
+
end.join(' ') if @scopes.is_a?(Array)
|
84
|
+
end
|
85
|
+
|
86
|
+
def new_authentication_params
|
87
|
+
{}.tap do |params|
|
88
|
+
params[:client_id] = client_id
|
89
|
+
params[:client_secret] = client_secret
|
90
|
+
params[:code] = @authorization_code
|
91
|
+
params[:redirect_uri] = @redirect_uri
|
92
|
+
params[:grant_type] = :authorization_code
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def refreshed_authentication_params
|
97
|
+
{}.tap do |params|
|
98
|
+
params[:client_id] = client_id
|
99
|
+
params[:client_secret] = client_secret
|
100
|
+
params[:refresh_token] = @refresh_token
|
101
|
+
params[:grant_type] = :refresh_token
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def client_id
|
106
|
+
Yt.configuration.client_id
|
107
|
+
end
|
108
|
+
|
109
|
+
def client_secret
|
110
|
+
Yt.configuration.client_secret
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/yt/associations.rb
CHANGED
@@ -17,13 +17,13 @@ module Yt
|
|
17
17
|
params[:host] = 'www.youtube.com'
|
18
18
|
params[:path] = '/annotations_invideo'
|
19
19
|
params[:params] = {video_id: @parent.id}
|
20
|
+
params[:expected_response] = Net::HTTPOK
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
23
24
|
def next_page
|
24
|
-
request = Request.new list_params
|
25
|
+
request = Yt::Request.new list_params
|
25
26
|
response = request.run
|
26
|
-
raise unless response.is_a? Net::HTTPOK
|
27
27
|
@page_token = nil
|
28
28
|
|
29
29
|
document = response.body.fetch('document', {})['annotations'] || {}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/authentication'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Authentications < Base
|
7
|
+
attr_accessor :auth_params
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def new_item(data)
|
12
|
+
Yt::Authentication.new data
|
13
|
+
end
|
14
|
+
|
15
|
+
def list_params
|
16
|
+
super.tap do |params|
|
17
|
+
params[:host] = 'accounts.google.com'
|
18
|
+
params[:path] = '/o/oauth2/token'
|
19
|
+
params[:body_type] = :form
|
20
|
+
params[:method] = :post
|
21
|
+
params[:auth] = nil
|
22
|
+
params[:body] = auth_params
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def more_pages?
|
27
|
+
auth_params.values.all?
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_page
|
31
|
+
request = Yt::Request.new list_params
|
32
|
+
Array.wrap request.run.body
|
33
|
+
rescue Yt::Error => error
|
34
|
+
expected?(error) ? [] : raise(error)
|
35
|
+
end
|
36
|
+
|
37
|
+
def expected?(error)
|
38
|
+
error.kind == 'invalid_grant'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/yt/collections/base.rb
CHANGED
@@ -11,7 +11,7 @@ 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
|
14
|
+
rescue Yt::Error => error
|
15
15
|
ignorable_errors = error.reasons & ['videoNotFound', 'forbidden']
|
16
16
|
raise error unless options[:ignore_errors] && ignorable_errors.any?
|
17
17
|
end
|
@@ -12,10 +12,9 @@ module Yt
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def list_params
|
15
|
-
parents_path = @parent.class.to_s.demodulize.underscore.pluralize
|
16
15
|
super.tap do |params|
|
17
16
|
params[:params] = {id: @parent.id, part: 'snippet'}
|
18
|
-
params[:path] = "/youtube/v3/#{
|
17
|
+
params[:path] = "/youtube/v3/#{@parent.kind.pluralize}"
|
19
18
|
end
|
20
19
|
end
|
21
20
|
end
|
@@ -14,13 +14,13 @@ module Yt
|
|
14
14
|
def list_params
|
15
15
|
super.tap do |params|
|
16
16
|
params[:path] = '/oauth2/v2/userinfo'
|
17
|
+
params[:expected_response] = Net::HTTPOK
|
17
18
|
end
|
18
19
|
end
|
19
20
|
|
20
21
|
def next_page
|
21
|
-
request = Request.new list_params
|
22
|
+
request = Yt::Request.new list_params
|
22
23
|
response = request.run
|
23
|
-
raise unless response.is_a? Net::HTTPOK
|
24
24
|
@page_token = nil
|
25
25
|
|
26
26
|
Array.wrap response.body
|
data/lib/yt/config.rb
CHANGED
@@ -13,7 +13,6 @@ module Yt
|
|
13
13
|
# @example A server-to-server YouTube client app
|
14
14
|
#
|
15
15
|
# Yt.configure do |config|
|
16
|
-
# config.scenario = :server_app
|
17
16
|
# config.api_key = 'ABCDEFGHIJ1234567890'
|
18
17
|
# end
|
19
18
|
#
|
@@ -30,7 +29,6 @@ module Yt
|
|
30
29
|
#
|
31
30
|
# @example
|
32
31
|
# Yt.configure do |config|
|
33
|
-
# config.scenario = :server_app
|
34
32
|
# config.api_key = 'ABCDEFGHIJ1234567890'
|
35
33
|
# end
|
36
34
|
# @see Yt::Configuration
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'yt/errors/request_error'
|
2
|
+
require 'yt/config'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Errors
|
6
|
+
class MissingAuth < RequestError
|
7
|
+
private
|
8
|
+
|
9
|
+
def explanation
|
10
|
+
'A request to YouTube API was sent without a valid authentication'
|
11
|
+
end
|
12
|
+
|
13
|
+
def more_details
|
14
|
+
if [Yt.configuration.client_id, Yt.configuration.api_key].none?
|
15
|
+
<<-MSG.gsub(/^ {10}/, '')
|
16
|
+
In order to perform this request, you need to register your app with
|
17
|
+
Google Developers Console (https://console.developers.google.com).
|
18
|
+
|
19
|
+
Make sure your app has access to the Google+ and YouTube APIs.
|
20
|
+
|
21
|
+
If your app requires read-only access to public YouTube data, then
|
22
|
+
generate a server API key and set its value with the initializer:
|
23
|
+
|
24
|
+
Yt.configure do |config|
|
25
|
+
config.api_key = '123456789012345678901234567890'
|
26
|
+
end
|
27
|
+
|
28
|
+
or through an environment variable:
|
29
|
+
|
30
|
+
export YT_API_KEY="123456789012345678901234567890"
|
31
|
+
|
32
|
+
If your app needs to perform actions on behalf of YouTube accounts,
|
33
|
+
then generate a client ID and SECRET and set their values with the
|
34
|
+
initializer:
|
35
|
+
|
36
|
+
Yt.configure do |config|
|
37
|
+
config.client_id = '1234567890.apps.googleusercontent.com'
|
38
|
+
config.client_secret = '1234567890'
|
39
|
+
end
|
40
|
+
|
41
|
+
or through environment variables:
|
42
|
+
|
43
|
+
export YT_CLIENT_ID="1234567890.apps.googleusercontent.com"
|
44
|
+
export YT_CLIENT_SECRET="1234567890"
|
45
|
+
MSG
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/yt/errors/no_items.rb
CHANGED
@@ -1,16 +1,12 @@
|
|
1
|
-
require 'yt/errors/
|
1
|
+
require 'yt/errors/request_error'
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Errors
|
5
|
-
class NoItems <
|
6
|
-
|
7
|
-
<<-MSG.gsub(/^ {6}/, '')
|
8
|
-
A request to YouTube API V3 returned no items (but some were expected):
|
9
|
-
#{response_body}
|
5
|
+
class NoItems < RequestError
|
6
|
+
private
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
MSG
|
8
|
+
def explanation
|
9
|
+
'A request to YouTube API returned no items but some were expected'
|
14
10
|
end
|
15
11
|
end
|
16
12
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Yt
|
2
|
+
module Errors
|
3
|
+
class RequestError < StandardError
|
4
|
+
def initialize(msg = nil)
|
5
|
+
@msg = msg
|
6
|
+
super msg
|
7
|
+
end
|
8
|
+
|
9
|
+
def message
|
10
|
+
<<-MSG.gsub(/^ {8}/, '')
|
11
|
+
#{explanation}:
|
12
|
+
#{response_body}
|
13
|
+
|
14
|
+
You can retry the same request manually by running:
|
15
|
+
#{request_curl}
|
16
|
+
#{more_details}
|
17
|
+
MSG
|
18
|
+
end
|
19
|
+
|
20
|
+
def kind
|
21
|
+
response_body.fetch 'error', {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def reasons
|
25
|
+
kind.fetch('errors', []).map{|e| e['reason']}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def explanation
|
31
|
+
'A request to YouTube API failed'
|
32
|
+
end
|
33
|
+
|
34
|
+
def more_details
|
35
|
+
end
|
36
|
+
|
37
|
+
def response_body
|
38
|
+
json['response_body']
|
39
|
+
end
|
40
|
+
|
41
|
+
def request_curl
|
42
|
+
json['request_curl']
|
43
|
+
end
|
44
|
+
|
45
|
+
def json
|
46
|
+
@json ||= JSON(@msg) rescue {}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Error = Errors::RequestError
|
52
|
+
end
|
data/lib/yt/models/account.rb
CHANGED
@@ -1,52 +1,25 @@
|
|
1
1
|
require 'yt/models/base'
|
2
|
-
require 'yt/config'
|
3
2
|
|
4
3
|
module Yt
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:name, :given_name, :family_name, :profile_url, :avatar_url, :locale, :hd]
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@access_token ||= refresh_access_token || get_access_token
|
20
|
-
end
|
21
|
-
|
22
|
-
def auth
|
23
|
-
self
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
# Obtain a new access token using the refresh token
|
29
|
-
def refresh_access_token
|
30
|
-
if @refresh_token
|
31
|
-
body = {grant_type: 'refresh_token', refresh_token: @refresh_token}
|
32
|
-
request = Request.new auth_params.deep_merge(body: body)
|
33
|
-
response = request.run
|
34
|
-
response.body['access_token']
|
4
|
+
module Models
|
5
|
+
# Provides methods to access a YouTube account.
|
6
|
+
class Account < Base
|
7
|
+
has_one :authentication, delegate: [:access_token, :refresh_token, :expires_at]
|
8
|
+
has_one :channel, delegate: [:videos, :playlists, :create_playlist, :delete_playlists, :update_playlists]
|
9
|
+
has_one :user_info, delegate: [:id, :email, :has_verified_email?, :gender, :name, :given_name, :family_name, :profile_url, :avatar_url, :locale, :hd]
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@access_token = options[:access_token]
|
13
|
+
@refresh_token = options[:refresh_token]
|
14
|
+
@expires_at = options[:expires_at]
|
15
|
+
@authorization_code = options[:authorization_code]
|
16
|
+
@redirect_uri = options[:redirect_uri]
|
17
|
+
@scopes = options[:scopes]
|
35
18
|
end
|
36
|
-
end
|
37
19
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
path: '/o/oauth2/token',
|
42
|
-
format: :json,
|
43
|
-
body_type: :form,
|
44
|
-
method: :post,
|
45
|
-
body: {
|
46
|
-
client_id: Yt.configuration.client_id,
|
47
|
-
client_secret: Yt.configuration.client_secret
|
48
|
-
}
|
49
|
-
}
|
20
|
+
def auth
|
21
|
+
self
|
22
|
+
end
|
50
23
|
end
|
51
24
|
end
|
52
25
|
end
|