yt 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0608745d313bbcec172603a1854abfffdee3f074
4
- data.tar.gz: 1f53091e0e0810f74c00a63f82584404fb640044
3
+ metadata.gz: ec366e6612cd8a61ec3067f58c819426b4da21ad
4
+ data.tar.gz: 3708e2efffd1b660afe370630d2f9b86e23f9e59
5
5
  SHA512:
6
- metadata.gz: 3d79894c6d8bb6ce42e1e0ab7ee49c05f723a68eb41c2e1f32f5278979fb7e73d902af587648fb76b8cc5d18012d37220e04f84b1d463f714399a80cf46ce509
7
- data.tar.gz: c393cf4018880ec464394d6c55087b8a65a99bcbf0466da48574c37927dc4232441851bf5b8f18033d361159e022dc6f584dbd68022b53849894ecd8849bcd09
6
+ metadata.gz: 33b816e4a002b24218b08b602c1faf999c854e04fa2edd3a3c84127ee0860463fe68ef8a07577d18830443497347ce55f968320503e5f27706b9e558be3cd884
7
+ data.tar.gz: 7e802cf425854455fd936424312f1343ce82a7bd35032b373cfb17df6b2c811525668013e91e2d7b7c3999725cf93d211e042981ff8dbbe55d58e7a3fa0eace5
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- yt (0.6.3)
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.3)
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.0)
36
+ rspec-core (3.0.1)
37
37
  rspec-support (~> 3.0.0)
38
- rspec-expectations (3.0.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.3'
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
@@ -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
@@ -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
 
@@ -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
- # is passed to the Annotation initializer.
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.fetch 'TEXT', ''
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/youtube/v3/guides/authentication
3
+ # Provides methods to authenticate with YouTube (and Google) API.
4
+ # @see https://developers.google.com/accounts/docs/OAuth2
5
5
  class Authentication
6
- attr_reader :access_token, :refresh_token, :expires_at
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
@@ -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
- mod = attributes.sub(/.*\./, '').camelize
19
- collection = "Yt::Collections::#{mod.pluralize}".constantize
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
- define_method attribute do
33
- ivar = instance_variable_get "@#{attribute}"
34
- instance_variable_set "@#{attribute}", ivar || send(attributes).first!
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
@@ -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
- # Provides read-only access to the description of a YouTube resource.
6
- # Resources with descriptions are: videos and channels.
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
- # Returns whether the description includes a YouTube video URL
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
- # @return [Boolean] Whether the description includes a link to a video
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
- # Returns whether the description includes a YouTube channel URL
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
- # @return [Boolean] Whether the description includes a link to a channel
34
- def has_link_to_channel?(options = {}) # TODO: which channel
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
- # Returns whether the description includes a YouTube subscription URL
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
- # @return [Boolean] Whether the description includes a link to subscribe
47
- def has_link_to_subscribe?(options = {}) # TODO: which channel
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
- # Returns whether the description includes a YouTube playlist URL
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
- # @return [Boolean] Whether the description includes a link to a playlist
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
- # Return the duration of the YouTube video.
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 length of the video. The tag value is an ISO 8601 duration in the format PT#M#S,
22
- # in which the letters PT indicate that the value specifies a period of time, and
23
- # the letters M and S refer to length in minutes and seconds, respectively. The #
24
- # characters preceding the M and S letters are both integers that specify the number
25
- # of minutes (or seconds) of the video. For example, a value of PT15M51S indicates
26
- # that the video is 15 minutes and 51 seconds long.
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
@@ -1,5 +1,7 @@
1
1
  module Yt
2
2
  module Models
3
+ # Encapsulates information about the ID that YouTube uses to uniquely
4
+ # identify a resource.
3
5
  class Id < String
4
6
  end
5
7
  end
@@ -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'
@@ -22,6 +22,8 @@ module Yt
22
22
 
23
23
  private
24
24
 
25
+ # @return [Hash] the parameters to submit to YouTube to update a rating.
26
+ # @see https://developers.google.com/youtube/v3/docs/videos/rate
25
27
  def update_params
26
28
  super.tap do |params|
27
29
  params[:method] = :post
@@ -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'
@@ -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
- # Return the title of the YouTube resource.
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
- # Return the description of a YouTube resource.
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
- # Return the publication date of a YouTube resource.
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
- # Return the URL of a thumbnail image of the YouTube resource.
42
- #
43
- # @return [String] A URL.
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']
@@ -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] Is the resource public?
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] Is the resource private?
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] Is the resource unlisted?
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'
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.6.3'
2
+ VERSION = '0.6.4'
3
3
  end
@@ -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 an existing video with annotations' do
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.3
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-12 00:00:00.000000000 Z
11
+ date: 2014-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport