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 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