yt 0.6.3 → 0.6.4
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 +4 -4
- data/HISTORY.md +1 -0
- data/README.md +1 -1
- data/lib/yt.rb +9 -1
- data/lib/yt/actions/insert.rb +3 -0
- data/lib/yt/collections/annotations.rb +11 -0
- data/lib/yt/collections/playlist_items.rb +5 -6
- data/lib/yt/collections/playlists.rb +5 -6
- data/lib/yt/collections/snippets.rb +5 -0
- data/lib/yt/collections/statuses.rb +7 -0
- data/lib/yt/collections/subscriptions.rb +8 -1
- data/lib/yt/collections/user_infos.rb +10 -1
- data/lib/yt/models/account.rb +3 -3
- data/lib/yt/models/annotation.rb +4 -4
- data/lib/yt/models/authentication.rb +44 -3
- data/lib/yt/models/base.rb +20 -12
- data/lib/yt/models/channel.rb +3 -3
- data/lib/yt/models/content_owner.rb +1 -1
- data/lib/yt/models/description.rb +15 -28
- data/lib/yt/models/details_set.rb +11 -10
- data/lib/yt/models/id.rb +2 -0
- data/lib/yt/models/playlist.rb +20 -1
- data/lib/yt/models/playlist_item.rb +2 -0
- data/lib/yt/models/rating.rb +2 -0
- data/lib/yt/models/request.rb +1 -0
- data/lib/yt/models/snippet.rb +13 -12
- data/lib/yt/models/status.rb +8 -3
- data/lib/yt/models/subscription.rb +2 -0
- data/lib/yt/models/video.rb +3 -3
- data/lib/yt/version.rb +1 -1
- data/spec/associations/no_auth/video_spec.rb +8 -1
- data/spec/models/annotation_spec.rb +30 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec366e6612cd8a61ec3067f58c819426b4da21ad
|
4
|
+
data.tar.gz: 3708e2efffd1b660afe370630d2f9b86e23f9e59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33b816e4a002b24218b08b602c1faf999c854e04fa2edd3a3c84127ee0860463fe68ef8a07577d18830443497347ce55f968320503e5f27706b9e558be3cd884
|
7
|
+
data.tar.gz: 7e802cf425854455fd936424312f1343ce82a7bd35032b373cfb17df6b2c811525668013e91e2d7b7c3999725cf93d211e042981ff8dbbe55d58e7a3fa0eace5
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
yt (0.6.
|
4
|
+
yt (0.6.4)
|
5
5
|
activesupport
|
6
6
|
|
7
7
|
GEM
|
@@ -20,7 +20,7 @@ GEM
|
|
20
20
|
term-ansicolor
|
21
21
|
thor
|
22
22
|
diff-lcs (1.2.5)
|
23
|
-
docile (1.1.
|
23
|
+
docile (1.1.4)
|
24
24
|
i18n (0.6.9)
|
25
25
|
json (1.8.1)
|
26
26
|
mime-types (2.3)
|
@@ -33,9 +33,9 @@ GEM
|
|
33
33
|
rspec-core (~> 3.0.0)
|
34
34
|
rspec-expectations (~> 3.0.0)
|
35
35
|
rspec-mocks (~> 3.0.0)
|
36
|
-
rspec-core (3.0.
|
36
|
+
rspec-core (3.0.1)
|
37
37
|
rspec-support (~> 3.0.0)
|
38
|
-
rspec-expectations (3.0.
|
38
|
+
rspec-expectations (3.0.1)
|
39
39
|
diff-lcs (>= 1.2.0, < 2.0)
|
40
40
|
rspec-support (~> 3.0.0)
|
41
41
|
rspec-mocks (3.0.1)
|
data/HISTORY.md
CHANGED
@@ -6,6 +6,7 @@ v0.6 - 2014/06/05
|
|
6
6
|
* Add the .status association to *every* type of resource (Channel, Video, Playlist)
|
7
7
|
* Allow account.videos to be chained with .where, such as in account.videos.where(q: 'query')
|
8
8
|
* Retry request once when YouTube times out
|
9
|
+
* Handle annotations with "never" as the timestamp, without text, singleton positions, of private videos
|
9
10
|
|
10
11
|
v0.5 - 2014/05/16
|
11
12
|
-----------------
|
data/README.md
CHANGED
@@ -332,7 +332,7 @@ To install on your system, run
|
|
332
332
|
|
333
333
|
To use inside a bundled Ruby project, add this line to the Gemfile:
|
334
334
|
|
335
|
-
gem 'yt', '~> 0.6.
|
335
|
+
gem 'yt', '~> 0.6.4'
|
336
336
|
|
337
337
|
Since the gem follows [Semantic Versioning](http://semver.org),
|
338
338
|
indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
|
data/lib/yt.rb
CHANGED
@@ -1,3 +1,11 @@
|
|
1
1
|
require 'yt/config'
|
2
2
|
require 'yt/models/account'
|
3
|
-
require 'yt/models/content_owner'
|
3
|
+
require 'yt/models/content_owner'
|
4
|
+
|
5
|
+
# An object-oriented Ruby client for YouTube.
|
6
|
+
# Helps creating applications that need to interact with YouTube objects.
|
7
|
+
# Inclused methods to access YouTube Data API V3 resources (channels, videos,
|
8
|
+
# ...), YouTube Analytics API V2 resources (metrics, earnings, ...), and
|
9
|
+
# objects not available through the API (annotations).
|
10
|
+
module Yt
|
11
|
+
end
|
data/lib/yt/actions/insert.rb
CHANGED
@@ -14,7 +14,10 @@ module Yt
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def insert_params
|
17
|
+
path = "/youtube/v3/#{self.class.to_s.demodulize.camelize :lower}"
|
18
|
+
|
17
19
|
{}.tap do |params|
|
20
|
+
params[:path] = path
|
18
21
|
params[:method] = :post
|
19
22
|
params[:auth] = @auth
|
20
23
|
params[:expected_response] = Net::HTTPOK
|
@@ -5,6 +5,8 @@ module Yt
|
|
5
5
|
module Collections
|
6
6
|
# Provides methods to interact with a collection of YouTube annotations.
|
7
7
|
# Resources with annotations are: {Yt::Models::Video videos}.
|
8
|
+
# @note Since there is no (authenticable) API endpoint to retrieve
|
9
|
+
# annotations, only annotations of *public videos* can be retrieved.
|
8
10
|
class Annotations < Base
|
9
11
|
|
10
12
|
private
|
@@ -38,6 +40,15 @@ module Yt
|
|
38
40
|
|
39
41
|
document = response.body.fetch('document', {})['annotations'] || {}
|
40
42
|
Array.wrap document.fetch 'annotation', []
|
43
|
+
rescue Yt::Error => error
|
44
|
+
expected?(error) ? [] : raise(error)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Since there is no API endpoint, retrieving annotations of unknown
|
48
|
+
# videos or of private videos (to which YouTube responds with 403)
|
49
|
+
# should not raise an error, but simply not return any annotation.
|
50
|
+
def expected?(error)
|
51
|
+
error.is_a? Yt::Errors::Forbidden
|
41
52
|
end
|
42
53
|
end
|
43
54
|
end
|
@@ -22,21 +22,20 @@ module Yt
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
+
# @return [Yt::Models::PlaylistItem] a new playlist item initialized with
|
26
|
+
# one of the items returned by asking YouTube for a list of items.
|
27
|
+
# @see https://developers.google.com/youtube/v3/docs/playlistItems#resource
|
25
28
|
def new_item(data)
|
26
29
|
Yt::PlaylistItem.new id: data['id'], snippet: data['snippet'], status: data['status'], auth: @auth
|
27
30
|
end
|
28
31
|
|
32
|
+
# @return [Hash] the parameters to submit to YouTube to list items.
|
33
|
+
# @see https://developers.google.com/youtube/v3/docs/playlistItems/list
|
29
34
|
def list_params
|
30
35
|
super.tap do |params|
|
31
36
|
params[:params] = {maxResults: 50, part: 'snippet,status', playlistId: @parent.id}
|
32
37
|
end
|
33
38
|
end
|
34
|
-
|
35
|
-
def insert_params
|
36
|
-
super.tap do |params|
|
37
|
-
params[:path] = '/youtube/v3/playlistItems'
|
38
|
-
end
|
39
|
-
end
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
@@ -25,21 +25,20 @@ module Yt
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
+
# @return [Yt::Models::Playlist] a new playlist initialized with
|
29
|
+
# one of the items returned by asking YouTube for a list of playlists.
|
30
|
+
# @see https://developers.google.com/youtube/v3/docs/playlists#resource
|
28
31
|
def new_item(data)
|
29
32
|
Yt::Playlist.new id: data['id'], snippet: data['snippet'], status: data['status'], auth: @auth
|
30
33
|
end
|
31
34
|
|
35
|
+
# @return [Hash] the parameters to submit to YouTube to list playlists.
|
36
|
+
# @see https://developers.google.com/youtube/v3/docs/playlist/list
|
32
37
|
def list_params
|
33
38
|
super.tap do |params|
|
34
39
|
params[:params] = {maxResults: 50, part: 'snippet,status', channelId: @parent.id}
|
35
40
|
end
|
36
41
|
end
|
37
|
-
|
38
|
-
def insert_params
|
39
|
-
super.tap do |params|
|
40
|
-
params[:path] = '/youtube/v3/playlists'
|
41
|
-
end
|
42
|
-
end
|
43
42
|
end
|
44
43
|
end
|
45
44
|
end
|
@@ -7,10 +7,15 @@ module Yt
|
|
7
7
|
|
8
8
|
private
|
9
9
|
|
10
|
+
# @return [Yt::Models::Snippet] a new snippet initialized with
|
11
|
+
# one of the items returned by asking YouTube for a list of snippets.
|
10
12
|
def new_item(data)
|
11
13
|
Yt::Snippet.new data: data['snippet']
|
12
14
|
end
|
13
15
|
|
16
|
+
# @return [Hash] the parameters to submit to YouTube to get the
|
17
|
+
# snippet of a resource, for instance a channel.
|
18
|
+
# @see https://developers.google.com/youtube/v3/docs/channels#resource
|
14
19
|
def list_params
|
15
20
|
super.tap do |params|
|
16
21
|
params[:params] = {id: @parent.id, part: 'snippet'}
|
@@ -7,10 +7,17 @@ module Yt
|
|
7
7
|
|
8
8
|
private
|
9
9
|
|
10
|
+
# @return [Yt::Models::Status] a new status initialized with
|
11
|
+
# one of the items returned by asking YouTube for a list of statuses,
|
12
|
+
# of a resource, for instance a channel.
|
13
|
+
# @see https://developers.google.com/youtube/v3/docs/playlists#resource
|
10
14
|
def new_item(data)
|
11
15
|
Yt::Status.new data: data['status']
|
12
16
|
end
|
13
17
|
|
18
|
+
# @return [Hash] the parameters to submit to YouTube to get the status
|
19
|
+
# of a resource, for instance a channel.
|
20
|
+
# @see https://developers.google.com/youtube/v3/docs/channels/list
|
14
21
|
def list_params
|
15
22
|
super.tap do |params|
|
16
23
|
params[:params] = {id: @parent.id, part: 'status'}
|
@@ -20,6 +20,10 @@ module Yt
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
+
# @return [Yt::Models::Subscription] a new subscription initialized with
|
24
|
+
# one of the items returned by asking YouTube for a list of
|
25
|
+
# subscriptions to a channel.
|
26
|
+
# @see https://developers.google.com/youtube/v3/docs/subscriptions#resource
|
23
27
|
def new_item(data)
|
24
28
|
Yt::Subscription.new id: data['id'], auth: @auth
|
25
29
|
end
|
@@ -41,15 +45,18 @@ module Yt
|
|
41
45
|
super params
|
42
46
|
end
|
43
47
|
|
48
|
+
# @return [Hash] the parameters to submit to YouTube to list subscriptions.
|
49
|
+
# @see https://developers.google.com/youtube/v3/docs/subscriptions/list
|
44
50
|
def list_params
|
45
51
|
super.tap do |params|
|
46
52
|
params[:params] = {maxResults: 50, forChannelId: @parent.id, mine: true, part: 'snippet'}
|
47
53
|
end
|
48
54
|
end
|
49
55
|
|
56
|
+
# @return [Hash] the parameters to submit to YouTube to add a subscriptions.
|
57
|
+
# @see https://developers.google.com/youtube/v3/docs/subscriptions/insert
|
50
58
|
def insert_params
|
51
59
|
super.tap do |params|
|
52
|
-
params[:path] = '/youtube/v3/subscriptions'
|
53
60
|
params[:params] = {part: 'snippet'}
|
54
61
|
params[:body] = {snippet: {resourceId: {channelId: @parent.id}}}
|
55
62
|
end
|
@@ -7,10 +7,15 @@ module Yt
|
|
7
7
|
|
8
8
|
private
|
9
9
|
|
10
|
+
# @return [Yt::Models::UserInfo] a new user info initialized with
|
11
|
+
# one of the items returned by asking YouTube for a list of user infos.
|
10
12
|
def new_item(data)
|
11
13
|
Yt::UserInfo.new data: data
|
12
14
|
end
|
13
15
|
|
16
|
+
# @return [Hash] the parameters to submit to YouTube to get the
|
17
|
+
# user info of an account
|
18
|
+
# @see https://developers.google.com/+/api/latest/people/getOpenIdConnect
|
14
19
|
def list_params
|
15
20
|
super.tap do |params|
|
16
21
|
params[:path] = '/oauth2/v2/userinfo'
|
@@ -18,11 +23,15 @@ module Yt
|
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
26
|
+
# next_page is overloaded here because, differently from the other
|
27
|
+
# endpoints, asking for the user info does not return a paginated result,
|
28
|
+
# so @page_token has to be explcitly set to nil, and the result wrapped
|
29
|
+
# in an Array.
|
21
30
|
def next_page
|
22
31
|
request = Yt::Request.new list_params
|
23
32
|
response = request.run
|
24
33
|
@page_token = nil
|
25
|
-
|
34
|
+
|
26
35
|
Array.wrap response.body
|
27
36
|
end
|
28
37
|
end
|
data/lib/yt/models/account.rb
CHANGED
@@ -8,19 +8,19 @@ module Yt
|
|
8
8
|
class Account < Base
|
9
9
|
include Associations::Authentications
|
10
10
|
|
11
|
-
# @!attribute channel
|
11
|
+
# @!attribute [r] channel
|
12
12
|
# @return [Yt::Models::Channel] the account’s channel.
|
13
13
|
has_one :channel
|
14
14
|
delegate :playlists, :create_playlist, :delete_playlists, to: :channel
|
15
15
|
|
16
|
-
# @!attribute user_info
|
16
|
+
# @!attribute [r] user_info
|
17
17
|
# @return [Yt::Models::UserInfo] the account’s profile information.
|
18
18
|
has_one :user_info
|
19
19
|
delegate :id, :email, :has_verified_email?, :gender, :name,
|
20
20
|
:given_name, :family_name, :profile_url, :avatar_url,
|
21
21
|
:locale, :hd, to: :user_info
|
22
22
|
|
23
|
-
# @!attribute videos
|
23
|
+
# @!attribute [r] videos
|
24
24
|
# @return [Yt::Collections::Videos] the videos owned by the account.
|
25
25
|
has_many :videos
|
26
26
|
|
data/lib/yt/models/annotation.rb
CHANGED
@@ -7,7 +7,7 @@ module Yt
|
|
7
7
|
# @option options [String] :data The XML representation of an annotation
|
8
8
|
# @note YouTube API V3 does not provide access to video annotations,
|
9
9
|
# therefore the XML endpoint is used to retrieve them and its response
|
10
|
-
#
|
10
|
+
# is passed to the Annotation initializer.
|
11
11
|
def initialize(options = {})
|
12
12
|
@data = options[:data]
|
13
13
|
end
|
@@ -75,7 +75,7 @@ module Yt
|
|
75
75
|
private
|
76
76
|
|
77
77
|
def text
|
78
|
-
@text ||= @data
|
78
|
+
@text ||= @data['TEXT'] || ''
|
79
79
|
end
|
80
80
|
|
81
81
|
def type
|
@@ -107,7 +107,7 @@ module Yt
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def timestamps
|
110
|
-
@timestamps ||= positions.map do |pos|
|
110
|
+
@timestamps ||= positions.reject{|pos| pos['t'] == 'never'}.map do |pos|
|
111
111
|
regex = %r{(?:|(?<hours>\d*):)(?:|(?<min>\d*):)(?<sec>\d*)\.(?<ms>\d*)}
|
112
112
|
match = pos['t'].match regex
|
113
113
|
hours = (match[:hours] || '0').to_i
|
@@ -118,7 +118,7 @@ module Yt
|
|
118
118
|
end
|
119
119
|
|
120
120
|
def positions
|
121
|
-
@positions ||= region['rectRegion'] || region['anchoredRegion']
|
121
|
+
@positions ||= Array.wrap region['rectRegion'] || region['anchoredRegion']
|
122
122
|
end
|
123
123
|
|
124
124
|
def region
|
@@ -1,9 +1,49 @@
|
|
1
1
|
module Yt
|
2
2
|
module Models
|
3
|
-
# Provides methods to authenticate with YouTube.
|
4
|
-
# @see https://developers.google.com/
|
3
|
+
# Provides methods to authenticate with YouTube (and Google) API.
|
4
|
+
# @see https://developers.google.com/accounts/docs/OAuth2
|
5
5
|
class Authentication
|
6
|
-
|
6
|
+
|
7
|
+
# Before your application can access private data using a Google API,
|
8
|
+
# it must obtain an access token that grants access to that API.
|
9
|
+
# A single access token can grant varying degrees of access to multiple
|
10
|
+
# APIs. A variable parameter called scope controls the set of resources
|
11
|
+
# and operations that an access token permits.
|
12
|
+
# After an application obtains an access token, it sends the token to a
|
13
|
+
# Google API in an HTTP authorization header.
|
14
|
+
# Access tokens are valid only for the set of operations and resources
|
15
|
+
# described in the scope of the token request. For example, if an access
|
16
|
+
# token is issued for the Google+ API, it does not grant access to the
|
17
|
+
# Google Contacts API.
|
18
|
+
#
|
19
|
+
# @return [String] the OAuth2 Google access token.
|
20
|
+
attr_reader :access_token
|
21
|
+
|
22
|
+
# Access tokens have limited lifetimes. If your application needs access
|
23
|
+
# to a Google API beyond the lifetime of a single access token, it can
|
24
|
+
# obtain a refresh token. A refresh token allows your application to
|
25
|
+
# obtain new access tokens.
|
26
|
+
# @note Save refresh tokens in secure long-term storage and continue to
|
27
|
+
# use them as long as they remain valid. Limits apply to the number of
|
28
|
+
# refresh tokens that are issued per client-user combination, and per
|
29
|
+
# user across all clients, and these limits are different. If your
|
30
|
+
# application requests enough refresh tokens to go over one of the
|
31
|
+
# limits, older refresh tokens stop working.
|
32
|
+
# There is currently a 25-token limit per Google user account.
|
33
|
+
# If a user account has 25 valid tokens, the next authentication request
|
34
|
+
# succeeds, but quietly invalidates the oldest outstanding token without
|
35
|
+
# any user-visible warning.
|
36
|
+
#
|
37
|
+
# @return [String] the OAuth2 Google refresh token.
|
38
|
+
attr_reader :refresh_token
|
39
|
+
|
40
|
+
# Access tokens have limited lifetimes. If your application needs access
|
41
|
+
# to a Google API beyond the lifetime of a single access token, it can
|
42
|
+
# obtain a refresh token. A refresh token allows your application to
|
43
|
+
# obtain new access tokens.
|
44
|
+
#
|
45
|
+
# @return [Time] the time when access token no longer works.
|
46
|
+
attr_reader :expires_at
|
7
47
|
|
8
48
|
def initialize(data = {})
|
9
49
|
@access_token = data['access_token']
|
@@ -11,6 +51,7 @@ module Yt
|
|
11
51
|
@expires_at = expiration_date data.slice('expires_at', 'expires_in')
|
12
52
|
end
|
13
53
|
|
54
|
+
# @return [Boolean] whether the access token has expired.
|
14
55
|
def expired?
|
15
56
|
@expires_at && @expires_at.past?
|
16
57
|
end
|
data/lib/yt/models/base.rb
CHANGED
@@ -2,8 +2,9 @@ require 'yt/actions/delete'
|
|
2
2
|
require 'yt/actions/update'
|
3
3
|
require 'yt/errors/request_error'
|
4
4
|
|
5
|
+
require 'active_support' # does not load anything by default but is required
|
5
6
|
require 'active_support/core_ext/module/delegation' # for delegate
|
6
|
-
require 'active_support/core_ext/string/inflections' # for camelize
|
7
|
+
require 'active_support/core_ext/string/inflections' # for camelize/constantize
|
7
8
|
|
8
9
|
module Yt
|
9
10
|
module Models
|
@@ -13,29 +14,36 @@ module Yt
|
|
13
14
|
|
14
15
|
# @private
|
15
16
|
def self.has_many(attributes)
|
16
|
-
attributes = attributes.to_s
|
17
17
|
require "yt/collections/#{attributes}"
|
18
|
-
|
19
|
-
collection = "Yt::Collections::#{
|
20
|
-
|
21
|
-
define_method attributes do
|
22
|
-
ivar = instance_variable_get "@#{attributes}"
|
23
|
-
instance_variable_set "@#{attributes}", ivar || collection.of(self)
|
24
|
-
end
|
18
|
+
collection_name = attributes.to_s.sub(/.*\./, '').camelize.pluralize
|
19
|
+
collection = "Yt::Collections::#{collection_name}".constantize
|
20
|
+
define_memoized_method(attributes) { collection.of self }
|
25
21
|
end
|
26
22
|
|
27
23
|
# @private
|
28
24
|
def self.has_one(attribute)
|
29
25
|
attributes = attribute.to_s.pluralize
|
30
26
|
has_many attributes
|
27
|
+
define_memoized_method(attribute) { send(attributes).first! }
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
# A wrapper around Ruby’s +define_method+ that, in addition to adding an
|
33
|
+
# instance method called +name+, adds an instance variable called +@name+
|
34
|
+
# that stores the result of +name+ the first time is invoked, and returns
|
35
|
+
# it every other time. Especially useful if invoking +name+ takes a long
|
36
|
+
# time.
|
37
|
+
def self.define_memoized_method(name, &method)
|
38
|
+
define_method name do
|
39
|
+
ivar = instance_variable_get "@#{name}"
|
40
|
+
instance_variable_set "@#{name}", ivar || instance_eval(&method)
|
35
41
|
end
|
36
42
|
end
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
46
|
+
# By including Models in the main namespace, models can be initialized with
|
47
|
+
# the shorter notation Yt::Video.new, rather than Yt::Models::Video.new.
|
40
48
|
include Models
|
41
49
|
end
|
data/lib/yt/models/channel.rb
CHANGED
@@ -10,15 +10,15 @@ module Yt
|
|
10
10
|
include Associations::Earnings
|
11
11
|
include Associations::Views
|
12
12
|
|
13
|
-
# @!attribute subscriptions
|
13
|
+
# @!attribute [r] subscriptions
|
14
14
|
# @return [Yt::Collections::Subscriptions] the channel’s subscriptions.
|
15
15
|
has_many :subscriptions
|
16
16
|
|
17
|
-
# @!attribute videos
|
17
|
+
# @!attribute [r] videos
|
18
18
|
# @return [Yt::Collections::Videos] the channel’s videos.
|
19
19
|
has_many :videos
|
20
20
|
|
21
|
-
# @!attribute playlists
|
21
|
+
# @!attribute [r] playlists
|
22
22
|
# @return [Yt::Collections::Playlists] the channel’s playlists.
|
23
23
|
has_many :playlists
|
24
24
|
|
@@ -7,7 +7,7 @@ module Yt
|
|
7
7
|
# @see https://developers.google.com/youtube/analytics/v1/content_owner_reports
|
8
8
|
class ContentOwner < Account
|
9
9
|
|
10
|
-
# @!attribute partnered_channels
|
10
|
+
# @!attribute [r] partnered_channels
|
11
11
|
# @return [Yt::Collection::PartneredChannels] the channels managed by the CMS account.
|
12
12
|
has_many :partnered_channels
|
13
13
|
|
@@ -2,61 +2,48 @@ require 'yt/models/url'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# @example
|
9
|
-
# description = Yt::Description.new 'Fullscreen provides a suite of end-to-end YouTube tools and services to many of the world’s leading brands and media companies.'
|
10
|
-
# description.to_s.slice(0,19) # => 'Fullscreen provides'
|
11
|
-
# description.length # => 127
|
5
|
+
# Encapsulates information about the description of a resource, for
|
6
|
+
# instance a channel.
|
7
|
+
# @see https://developers.google.com/youtube/v3/docs/channels#resource
|
12
8
|
#
|
9
|
+
# The value has a maximum length of 1000 characters.
|
13
10
|
class Description < String
|
14
|
-
#
|
15
|
-
#
|
11
|
+
# @return [Boolean] whether the description includes a link to a video
|
16
12
|
# @example
|
17
|
-
# description = Yt::Description.new 'Link to video: youtube.com/watch?v=MESycYJytkU'
|
13
|
+
# description = Yt::Models::Description.new 'Link to video: youtube.com/watch?v=MESycYJytkU'
|
18
14
|
# description.has_link_to_video? #=> true
|
19
15
|
#
|
20
|
-
# @
|
16
|
+
# @todo add an option to match the link to a specific video
|
21
17
|
def has_link_to_video?
|
22
|
-
# TODO: might take as an option WHICH video to link to
|
23
|
-
# in order to check if it's my own video
|
24
18
|
links.any?{|link| link.kind == :video}
|
25
19
|
end
|
26
20
|
|
27
|
-
#
|
28
|
-
#
|
21
|
+
# @return [Boolean] whether the description includes a link to a channel
|
29
22
|
# @example
|
30
23
|
# description = Yt::Description.new 'Link to channel: youtube.com/fullscreen'
|
31
24
|
# description.has_link_to_channel? #=> true
|
32
25
|
#
|
33
|
-
# @
|
34
|
-
def has_link_to_channel?(options = {})
|
35
|
-
# TODO: might take as an option WHICH channel to link to
|
36
|
-
# in order to check if it's my own channel
|
26
|
+
# @todo add an option to match the link to a specific channel
|
27
|
+
def has_link_to_channel?(options = {})
|
37
28
|
links.any?{|link| link.kind == :channel}
|
38
29
|
end
|
39
30
|
|
40
|
-
#
|
41
|
-
#
|
31
|
+
# @return [Boolean] whether the description includes a link to subscribe
|
42
32
|
# @example
|
43
33
|
# description = Yt::Description.new 'Link to subscribe: youtube.com/subscription_center?add_user=fullscreen'
|
44
34
|
# description.has_link_to_subscribe? #=> true
|
45
35
|
#
|
46
|
-
# @
|
47
|
-
def has_link_to_subscribe?(options = {})
|
48
|
-
# TODO: might take as an option WHICH channel to subscribe to
|
49
|
-
# in order to check if it's my own channel
|
36
|
+
# @todo add an option to match the link to subscribe to a specific channel
|
37
|
+
def has_link_to_subscribe?(options = {})
|
50
38
|
links.any?{|link| link.kind == :subscription}
|
51
39
|
end
|
52
40
|
|
53
|
-
#
|
54
|
-
#
|
41
|
+
# @return [Boolean] whether the description includes a link to a playlist
|
55
42
|
# @example
|
56
43
|
# description = Yt::Description.new 'Link to playlist: youtube.com/playlist?list=LLxO1tY8h1AhOz0T4ENwmpow'
|
57
44
|
# description.has_link_to_playlist? #=> true
|
58
45
|
#
|
59
|
-
# @
|
46
|
+
# @todo add an option to match the link to a specific playlist
|
60
47
|
def has_link_to_playlist?
|
61
48
|
links.any?{|link| link.kind == :playlist}
|
62
49
|
end
|
@@ -2,28 +2,29 @@ require 'yt/models/base'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Encapsulates information about the content of a resource, for
|
6
|
+
# instance a video.
|
7
|
+
# @see https://developers.google.com/youtube/v3/docs/videos#resource
|
5
8
|
class DetailsSet < Base
|
6
9
|
|
7
10
|
def initialize(options = {})
|
8
11
|
@data = options[:data]
|
9
12
|
end
|
10
13
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# @return [Integer] Duration in seconds of the YouTube video
|
14
|
+
# @return [Integer] the duration of the resource (in seconds).
|
14
15
|
def duration
|
15
16
|
@duration = to_seconds @data.fetch('duration', 0)
|
16
17
|
end
|
17
|
-
# also available: dimension, definition, caption, licensed_content?
|
18
18
|
|
19
19
|
private
|
20
20
|
|
21
|
-
# The
|
22
|
-
# in
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
21
|
+
# The duration of the resource as reported by YouTube. The value is an
|
22
|
+
# ISO 8601 duration in the format PT#M#S, in which the letters PT
|
23
|
+
# indicate that the value specifies a period of time, and the letters M
|
24
|
+
# and S refer to length in minutes and seconds, respectively. The #
|
25
|
+
# characters preceding the M and S letters are both integers that specify
|
26
|
+
# the number of minutes (or seconds) of the video. For example, a value
|
27
|
+
# of PT15M51S indicates that the video is 15 minutes and 51 seconds long.
|
27
28
|
def to_seconds(iso8601_duration)
|
28
29
|
match = iso8601_duration.match %r{^PT(?:|(?<hours>\d*?)H)(?:|(?<min>\d*?)M)(?:|(?<sec>\d*?)S)$}
|
29
30
|
hours = (match[:hours] || '0').to_i
|
data/lib/yt/models/id.rb
CHANGED
data/lib/yt/models/playlist.rb
CHANGED
@@ -6,10 +6,17 @@ module Yt
|
|
6
6
|
# @see https://developers.google.com/youtube/v3/docs/playlists
|
7
7
|
class Playlist < Resource
|
8
8
|
|
9
|
-
# @!attribute playlist_items
|
9
|
+
# @!attribute [r] playlist_items
|
10
10
|
# @return [Yt::Collections::PlaylistItems] the playlist’s items.
|
11
11
|
has_many :playlist_items
|
12
12
|
|
13
|
+
# Deletes the playlist.
|
14
|
+
#
|
15
|
+
# This method requires {Resource#auth auth} to return an authenticated
|
16
|
+
# instance of {Yt::Account} with permissions to delete the playlist.
|
17
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
18
|
+
# return an account with permissions to delete the playlist.
|
19
|
+
# @return [Boolean] whether the playlist does not exist anymore.
|
13
20
|
def delete
|
14
21
|
do_delete {@id = nil}
|
15
22
|
!exists?
|
@@ -37,6 +44,14 @@ module Yt
|
|
37
44
|
!@id.nil?
|
38
45
|
end
|
39
46
|
|
47
|
+
# Adds a video to the playlist
|
48
|
+
# Does not raise an error if the video cannot be added (e.g., unknown).
|
49
|
+
#
|
50
|
+
# This method requires {Resource#auth auth} to return an authenticated
|
51
|
+
# instance of {Yt::Account} with permissions to update the playlist.
|
52
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
53
|
+
# return an account with permissions to update the playlist.
|
54
|
+
# @return [Yt::PlaylistItem] the item added to the playlist.
|
40
55
|
def add_video(video_id)
|
41
56
|
playlist_items.insert video_params(video_id), ignore_errors: true
|
42
57
|
end
|
@@ -59,6 +74,8 @@ module Yt
|
|
59
74
|
|
60
75
|
private
|
61
76
|
|
77
|
+
# @return [Hash] the parameters to submit to YouTube to delete a playlist.
|
78
|
+
# @see https://developers.google.com/youtube/v3/docs/playlists/delete
|
62
79
|
def delete_params
|
63
80
|
super.tap do |params|
|
64
81
|
params[:path] = '/youtube/v3/playlists'
|
@@ -66,6 +83,8 @@ module Yt
|
|
66
83
|
end
|
67
84
|
end
|
68
85
|
|
86
|
+
# @return [Hash] the parameters to submit to YouTube to update a playlist.
|
87
|
+
# @see https://developers.google.com/youtube/v3/docs/playlists/update
|
69
88
|
def update_params
|
70
89
|
super.tap do |params|
|
71
90
|
params[:path] = '/youtube/v3/playlists'
|
@@ -35,6 +35,8 @@ module Yt
|
|
35
35
|
|
36
36
|
private
|
37
37
|
|
38
|
+
# @return [Hash] the parameters to submit to YouTube to delete a playlist item.
|
39
|
+
# @see https://developers.google.com/youtube/v3/docs/playlistItems/delete
|
38
40
|
def delete_params
|
39
41
|
super.tap do |params|
|
40
42
|
params[:path] = '/youtube/v3/playlistItems'
|
data/lib/yt/models/rating.rb
CHANGED
data/lib/yt/models/request.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'net/http' # for Net::HTTP.start
|
2
2
|
require 'uri' # for URI.json
|
3
3
|
require 'json' # for JSON.parse
|
4
|
+
require 'active_support' # does not load anything by default but is required
|
4
5
|
require 'active_support/core_ext' # for Hash.from_xml, Hash.to_param
|
5
6
|
|
6
7
|
require 'yt/config'
|
data/lib/yt/models/snippet.rb
CHANGED
@@ -2,28 +2,25 @@ require 'yt/models/description'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Encapsulates information about the snippet of a resource, for
|
6
|
+
# instance a channel.
|
7
|
+
# @see https://developers.google.com/youtube/v3/docs/channels#resource
|
5
8
|
class Snippet
|
6
9
|
def initialize(options = {})
|
7
10
|
@data = options[:data]
|
8
11
|
end
|
9
12
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# @return [String] Title of the YouTube resource
|
13
|
+
# @return [String] the resource’s title.
|
13
14
|
def title
|
14
15
|
@title ||= @data.fetch 'title', ''
|
15
16
|
end
|
16
17
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# @return [Yt::Description] A Yt::Description object for the YouTube resource
|
18
|
+
# @return [Yt::Models::Description] the resource’s description.
|
20
19
|
def description
|
21
20
|
@description ||= Description.new @data.fetch('description', '')
|
22
21
|
end
|
23
22
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# @return [Time or nil] The publication date for the YouTube resource
|
23
|
+
# @return [Time or nil] the date and time that the resource was created.
|
27
24
|
def published_at
|
28
25
|
@published_at ||= Time.parse @data['publishedAt']
|
29
26
|
end
|
@@ -38,9 +35,13 @@ module Yt
|
|
38
35
|
@tags ||= @data.fetch 'tags', []
|
39
36
|
end
|
40
37
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
38
|
+
# @return [String] a thumbnail image associated with the resource.
|
39
|
+
# @param [Symbol or String] size The size of the thumbnail to retrieve.
|
40
|
+
# Valid values are: default, medium, high.
|
41
|
+
# For a resource that refers to a video, default equals 120x90px,
|
42
|
+
# medium equals 320x180px, high equals 480x360px.
|
43
|
+
# For a resource that refers to a channel, default equals 88x88px,
|
44
|
+
# medium equals 240x240px, high equals 800x800px.
|
44
45
|
def thumbnail_url(size = :default)
|
45
46
|
@thumbnails ||= @data.fetch 'thumbnails', {}
|
46
47
|
@thumbnails.fetch(size.to_s, {})['url']
|
data/lib/yt/models/status.rb
CHANGED
@@ -1,25 +1,30 @@
|
|
1
1
|
module Yt
|
2
2
|
module Models
|
3
|
+
# Encapsulates information about the privacy status of a resource, for
|
4
|
+
# instance a channel.
|
5
|
+
# @see https://developers.google.com/youtube/v3/docs/channels#resource
|
3
6
|
class Status
|
4
7
|
def initialize(options = {})
|
5
8
|
@data = options[:data]
|
6
9
|
end
|
7
10
|
|
8
|
-
# @return [Boolean]
|
11
|
+
# @return [Boolean] whether the resource is public
|
9
12
|
def public?
|
10
13
|
privacy_status == 'public'
|
11
14
|
end
|
12
15
|
|
13
|
-
# @return [Boolean]
|
16
|
+
# @return [Boolean] whether the resource is private
|
14
17
|
def private?
|
15
18
|
privacy_status == 'private'
|
16
19
|
end
|
17
20
|
|
18
|
-
# @return [Boolean]
|
21
|
+
# @return [Boolean] whether the resource is unlisted
|
19
22
|
def unlisted?
|
20
23
|
privacy_status == 'unlisted'
|
21
24
|
end
|
22
25
|
|
26
|
+
# @return [String] the privacy status of the channel.
|
27
|
+
# Valid values are: private, public, unlisted
|
23
28
|
def privacy_status
|
24
29
|
@privacy_status ||= @data['privacyStatus']
|
25
30
|
end
|
@@ -29,6 +29,8 @@ module Yt
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
+
# @return [Hash] the parameters to submit to YouTube to delete a subscription.
|
33
|
+
# @see https://developers.google.com/youtube/v3/docs/subscriptions/delete
|
32
34
|
def delete_params
|
33
35
|
super.tap do |params|
|
34
36
|
params[:path] = '/youtube/v3/subscriptions'
|
data/lib/yt/models/video.rb
CHANGED
@@ -5,16 +5,16 @@ module Yt
|
|
5
5
|
# Provides methods to interact with YouTube videos.
|
6
6
|
# @see https://developers.google.com/youtube/v3/docs/videos
|
7
7
|
class Video < Resource
|
8
|
-
# @!attribute details_set
|
8
|
+
# @!attribute [r] details_set
|
9
9
|
# @return [Yt::Models::DetailsSet] the video’s content details.
|
10
10
|
has_one :details_set
|
11
11
|
delegate :duration, to: :details_set
|
12
12
|
|
13
|
-
# @!attribute rating
|
13
|
+
# @!attribute [r] rating
|
14
14
|
# @return [Yt::Models::Rating] the video’s rating.
|
15
15
|
has_one :rating
|
16
16
|
|
17
|
-
# @!attribute annotations
|
17
|
+
# @!attribute [r] annotations
|
18
18
|
# @return [Yt::Collections::Annotations] the video’s annotations.
|
19
19
|
has_many :annotations
|
20
20
|
|
data/lib/yt/version.rb
CHANGED
@@ -4,10 +4,17 @@ require 'yt/models/video'
|
|
4
4
|
describe Yt::Video, :device_app do
|
5
5
|
subject(:video) { Yt::Video.new id: id, auth: $account }
|
6
6
|
|
7
|
-
context 'given
|
7
|
+
context 'given a public video with annotations' do
|
8
8
|
let(:id) { 'MESycYJytkU' }
|
9
9
|
|
10
10
|
it { expect(video.annotations).to be_a Yt::Collections::Annotations }
|
11
11
|
it { expect(video.annotations.first).to be_a Yt::Annotation }
|
12
12
|
end
|
13
|
+
|
14
|
+
context 'given a private video' do
|
15
|
+
let(:id) { 'JzDEc54FVTc' }
|
16
|
+
|
17
|
+
it { expect(video.annotations).to be_a Yt::Collections::Annotations }
|
18
|
+
it { expect(video.annotations.count).to be_zero }
|
19
|
+
end
|
13
20
|
end
|
@@ -24,6 +24,18 @@ describe Yt::Annotation do
|
|
24
24
|
it { expect(annotation.below? 5).to be true }
|
25
25
|
end
|
26
26
|
|
27
|
+
context 'given an annotation without a single position' do
|
28
|
+
let(:xml) { %Q{
|
29
|
+
<segment>
|
30
|
+
<movingRegion type="rect">
|
31
|
+
<rectRegion d="0" h="17.7779998779" t="0:05.000" w="25.0" x="7.117000103" y="5.07000017166"/>
|
32
|
+
</movingRegion>
|
33
|
+
</segment>
|
34
|
+
} }
|
35
|
+
it { expect(annotation.above? 50).to be true }
|
36
|
+
it { expect(annotation.below? 50).to be_falsey }
|
37
|
+
end
|
38
|
+
|
27
39
|
context 'given an annotation without explicit location' do
|
28
40
|
let(:xml) { '<segment></segment>' }
|
29
41
|
it { expect(annotation.above? 50).to be_falsey }
|
@@ -75,6 +87,11 @@ describe Yt::Annotation do
|
|
75
87
|
let(:xml) { '<action type="openUrl"><url link_class="3"/></action>' }
|
76
88
|
it { expect(annotation).not_to have_link_to_playlist }
|
77
89
|
end
|
90
|
+
|
91
|
+
context 'given an annotation without text' do
|
92
|
+
let(:xml) { '<TEXT />' }
|
93
|
+
it { expect(annotation).not_to have_link_to_playlist }
|
94
|
+
end
|
78
95
|
end
|
79
96
|
|
80
97
|
describe '#has_link_to_same_window?' do
|
@@ -127,5 +144,18 @@ describe Yt::Annotation do
|
|
127
144
|
it { expect(annotation.starts_after? 0).to be_nil }
|
128
145
|
it { expect(annotation.starts_before? 0).to be_nil }
|
129
146
|
end
|
147
|
+
|
148
|
+
context 'given an annotation with "never" as the timestamp' do
|
149
|
+
let(:xml) { %Q{
|
150
|
+
<segment>
|
151
|
+
<movingRegion type="rect">
|
152
|
+
<rectRegion h="6.0" t="never" w="25.0" x="7.0" y="5.0"/>
|
153
|
+
<rectRegion h="6.0" t="never" w="25.0" x="7.0" y="5.0"/>
|
154
|
+
</movingRegion>
|
155
|
+
</segment>
|
156
|
+
} }
|
157
|
+
it { expect(annotation.starts_after? 0).to be_nil }
|
158
|
+
it { expect(annotation.starts_before? 0).to be_nil }
|
159
|
+
end
|
130
160
|
end
|
131
161
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claudio Baccigalupo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-06-
|
11
|
+
date: 2014-06-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|