yt 0.6.2 → 0.6.3
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 +9 -9
- data/HISTORY.md +1 -0
- data/README.md +12 -10
- data/lib/yt/collections/annotations.rb +10 -0
- data/lib/yt/collections/channels.rb +7 -0
- data/lib/yt/collections/earnings.rb +4 -28
- data/lib/yt/collections/partnered_channels.rb +5 -0
- data/lib/yt/collections/reports.rb +52 -0
- data/lib/yt/collections/videos.rb +8 -0
- data/lib/yt/collections/views.rb +6 -26
- data/lib/yt/config.rb +20 -19
- data/lib/yt/models/account.rb +19 -3
- data/lib/yt/models/annotation.rb +25 -36
- data/lib/yt/models/authentication.rb +2 -0
- data/lib/yt/models/base.rb +3 -4
- data/lib/yt/models/channel.rb +52 -0
- data/lib/yt/models/configuration.rb +34 -13
- data/lib/yt/models/content_owner.rb +8 -1
- data/lib/yt/models/description.rb +11 -16
- data/lib/yt/models/playlist.rb +5 -0
- data/lib/yt/models/playlist_item.rb +11 -1
- data/lib/yt/models/rating.rb +5 -0
- data/lib/yt/models/request.rb +17 -10
- data/lib/yt/models/resource.rb +7 -2
- data/lib/yt/models/subscription.rb +3 -1
- data/lib/yt/models/user_info.rb +15 -13
- data/lib/yt/models/video.rb +40 -1
- data/lib/yt/version.rb +1 -1
- data/spec/models/request_spec.rb +71 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0608745d313bbcec172603a1854abfffdee3f074
|
4
|
+
data.tar.gz: 1f53091e0e0810f74c00a63f82584404fb640044
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d79894c6d8bb6ce42e1e0ab7ee49c05f723a68eb41c2e1f32f5278979fb7e73d902af587648fb76b8cc5d18012d37220e04f84b1d463f714399a80cf46ce509
|
7
|
+
data.tar.gz: c393cf4018880ec464394d6c55087b8a65a99bcbf0466da48574c37927dc4232441851bf5b8f18033d361159e022dc6f584dbd68022b53849894ecd8849bcd09
|
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.3)
|
5
5
|
activesupport
|
6
6
|
|
7
7
|
GEM
|
@@ -23,10 +23,10 @@ GEM
|
|
23
23
|
docile (1.1.3)
|
24
24
|
i18n (0.6.9)
|
25
25
|
json (1.8.1)
|
26
|
-
mime-types (2.
|
27
|
-
minitest (5.3.
|
28
|
-
multi_json (1.10.
|
29
|
-
rake (10.3.
|
26
|
+
mime-types (2.3)
|
27
|
+
minitest (5.3.4)
|
28
|
+
multi_json (1.10.1)
|
29
|
+
rake (10.3.2)
|
30
30
|
rest-client (1.6.7)
|
31
31
|
mime-types (>= 1.16)
|
32
32
|
rspec (3.0.0)
|
@@ -38,7 +38,7 @@ GEM
|
|
38
38
|
rspec-expectations (3.0.0)
|
39
39
|
diff-lcs (>= 1.2.0, < 2.0)
|
40
40
|
rspec-support (~> 3.0.0)
|
41
|
-
rspec-mocks (3.0.
|
41
|
+
rspec-mocks (3.0.1)
|
42
42
|
rspec-support (~> 3.0.0)
|
43
43
|
rspec-support (3.0.0)
|
44
44
|
simplecov (0.8.2)
|
@@ -49,9 +49,9 @@ GEM
|
|
49
49
|
term-ansicolor (1.3.0)
|
50
50
|
tins (~> 1.0)
|
51
51
|
thor (0.19.1)
|
52
|
-
thread_safe (0.3.
|
53
|
-
tins (1.
|
54
|
-
tzinfo (1.1
|
52
|
+
thread_safe (0.3.4)
|
53
|
+
tins (1.3.0)
|
54
|
+
tzinfo (1.2.1)
|
55
55
|
thread_safe (~> 0.1)
|
56
56
|
yard (0.8.7.4)
|
57
57
|
|
data/HISTORY.md
CHANGED
@@ -5,6 +5,7 @@ v0.6 - 2014/06/05
|
|
5
5
|
* [breaking change] Account#videos shows *all* videos owned by account (public and private)
|
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
|
+
* Retry request once when YouTube times out
|
8
9
|
|
9
10
|
v0.5 - 2014/05/16
|
10
11
|
-----------------
|
data/README.md
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
Yt
|
2
|
-
|
1
|
+
Yt - a Ruby client for the YouTube API
|
2
|
+
======================================================
|
3
3
|
|
4
|
-
Yt helps you write apps that need to interact with
|
4
|
+
Yt helps you write apps that need to interact with YouTube.
|
5
5
|
|
6
|
-
[
|
7
|
-
|
8
|
-
[.
|
7
|
+
|
8
|
+
[](https://travis-ci.org/Fullscreen/yt)
|
9
|
+
[](https://coveralls.io/r/Fullscreen/yt)
|
10
|
+
[](https://gemnasium.com/Fullscreen/yt)
|
11
|
+
[](https://codeclimate.com/github/Fullscreen/yt)
|
12
|
+
[](http://rubydoc.info/github/Fullscreen/yt/master/frames)
|
13
|
+
[](http://rubygems.org/gems/yt)
|
12
14
|
|
13
15
|
After [registering your app](#configuring-your-app), you can run commands like:
|
14
16
|
|
@@ -330,7 +332,7 @@ To install on your system, run
|
|
330
332
|
|
331
333
|
To use inside a bundled Ruby project, add this line to the Gemfile:
|
332
334
|
|
333
|
-
gem 'yt', '~> 0.6.
|
335
|
+
gem 'yt', '~> 0.6.3'
|
334
336
|
|
335
337
|
Since the gem follows [Semantic Versioning](http://semver.org),
|
336
338
|
indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
|
@@ -3,14 +3,21 @@ require 'yt/models/annotation'
|
|
3
3
|
|
4
4
|
module Yt
|
5
5
|
module Collections
|
6
|
+
# Provides methods to interact with a collection of YouTube annotations.
|
7
|
+
# Resources with annotations are: {Yt::Models::Video videos}.
|
6
8
|
class Annotations < Base
|
7
9
|
|
8
10
|
private
|
9
11
|
|
12
|
+
# @return [Yt::Models::Annotation] a new annotation initialized with one
|
13
|
+
# of the items returned by asking YouTube for a list of annotations.
|
10
14
|
def new_item(data)
|
11
15
|
Yt::Annotation.new data: data
|
12
16
|
end
|
13
17
|
|
18
|
+
# @return [Hash] the parameters to submit to YouTube to list annotations.
|
19
|
+
# @note YouTube does not provide an API endpoint to get annotations for
|
20
|
+
# a video, so we use an "old-style" URL that YouTube still maintains.
|
14
21
|
def list_params
|
15
22
|
super.tap do |params|
|
16
23
|
params[:format] = :xml
|
@@ -21,6 +28,9 @@ module Yt
|
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
31
|
+
# @private
|
32
|
+
# @note Annotations overwrites +next_page+ since the list of annotations
|
33
|
+
# is not paginated API-style, but in its own custom way.
|
24
34
|
def next_page
|
25
35
|
request = Yt::Request.new list_params
|
26
36
|
response = request.run
|
@@ -3,14 +3,21 @@ require 'yt/models/channel'
|
|
3
3
|
|
4
4
|
module Yt
|
5
5
|
module Collections
|
6
|
+
# Provides methods to interact with a collection of YouTube channels.
|
7
|
+
# Resources with channels are: {Yt::Models::Account accounts}.
|
6
8
|
class Channels < Base
|
7
9
|
|
8
10
|
private
|
9
11
|
|
12
|
+
# @return [Yt::Models::Channel] a new channel initialized with one of
|
13
|
+
# the items returned by asking YouTube for a list of channels.
|
14
|
+
# @see https://developers.google.com/youtube/v3/docs/channels#resource
|
10
15
|
def new_item(data)
|
11
16
|
Yt::Channel.new id: data['id'], snippet: data['snippet'], auth: @auth
|
12
17
|
end
|
13
18
|
|
19
|
+
# @return [Hash] the parameters to submit to YouTube to list channels.
|
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] = {maxResults: 50, part: 'snippet', mine: true}
|
@@ -1,37 +1,13 @@
|
|
1
|
-
require 'yt/collections/
|
1
|
+
require 'yt/collections/reports'
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Collections
|
5
|
-
class Earnings <
|
6
|
-
|
7
|
-
def within(days_range)
|
8
|
-
@days_range = days_range
|
9
|
-
Hash[*flat_map{|daily_earning| daily_earning}]
|
10
|
-
end
|
5
|
+
class Earnings < Reports
|
11
6
|
|
12
7
|
private
|
13
8
|
|
14
|
-
def
|
15
|
-
|
16
|
-
[Date.iso8601(data.first), data.last]
|
17
|
-
end
|
18
|
-
|
19
|
-
def list_params
|
20
|
-
super.tap do |params|
|
21
|
-
params[:path] = '/youtube/analytics/v1/reports'
|
22
|
-
params[:params] = {}.tap do |params|
|
23
|
-
params['ids'] = "contentOwner==#{@auth.owner_name}"
|
24
|
-
params['filters'] = "channel==#{@parent.id}"
|
25
|
-
params['start-date'] = @days_range.begin
|
26
|
-
params['end-date'] = @days_range.end
|
27
|
-
params['metrics'] = [:estimatedMinutesWatched, :earnings].join ','
|
28
|
-
params['dimensions'] = :day
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def items_key
|
34
|
-
'rows'
|
9
|
+
def metrics
|
10
|
+
[:estimatedMinutesWatched, :earnings].join ','
|
35
11
|
end
|
36
12
|
end
|
37
13
|
end
|
@@ -6,6 +6,8 @@ module Yt
|
|
6
6
|
|
7
7
|
private
|
8
8
|
|
9
|
+
# @return [Hash] the parameters to submit to YouTube to list partnered channels.
|
10
|
+
# @see https://developers.google.com/youtube/v3/docs/channels/list
|
9
11
|
def list_params
|
10
12
|
super.tap do |params|
|
11
13
|
params[:params].delete :mine
|
@@ -14,6 +16,9 @@ module Yt
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
19
|
+
# @private
|
20
|
+
# @note Partnered Channels overwrites +list_resources+ since the endpoint
|
21
|
+
# to hit is 'channels', not 'partnered_channels'.
|
17
22
|
def list_resources
|
18
23
|
self.class.superclass
|
19
24
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Collections
|
5
|
+
class Reports < Base
|
6
|
+
|
7
|
+
def within(days_range)
|
8
|
+
@days_range = days_range
|
9
|
+
Hash[*flat_map{|daily_value| daily_value}]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def new_item(data)
|
15
|
+
[day_of(data), value_of(data)]
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see https://developers.google.com/youtube/analytics/v1/content_owner_reports
|
19
|
+
def list_params
|
20
|
+
super.tap do |params|
|
21
|
+
params[:path] = '/youtube/analytics/v1/reports'
|
22
|
+
params[:params] = {}.tap do |params|
|
23
|
+
params['ids'] = "contentOwner==#{@auth.owner_name}"
|
24
|
+
params['filters'] = "channel==#{@parent.id}"
|
25
|
+
params['start-date'] = @days_range.begin
|
26
|
+
params['end-date'] = @days_range.end
|
27
|
+
params['metrics'] = metrics
|
28
|
+
params['dimensions'] = :day
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def day_of(data)
|
34
|
+
# NOTE: could use column headers to be more precise
|
35
|
+
Date.iso8601 data.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def value_of(data)
|
39
|
+
# NOTE: could use column headers to be more precise
|
40
|
+
data.last
|
41
|
+
end
|
42
|
+
|
43
|
+
def metrics
|
44
|
+
''
|
45
|
+
end
|
46
|
+
|
47
|
+
def items_key
|
48
|
+
'rows'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -3,14 +3,22 @@ require 'yt/models/video'
|
|
3
3
|
|
4
4
|
module Yt
|
5
5
|
module Collections
|
6
|
+
# Provides methods to interact with a collection of YouTube videos.
|
7
|
+
# Resources with videos are: {Yt::Models::Channel channels} and
|
8
|
+
# {Yt::Models::Account accounts}.
|
6
9
|
class Videos < Base
|
7
10
|
|
8
11
|
private
|
9
12
|
|
13
|
+
# @return [Yt::Models::Video] a new video initialized with one of
|
14
|
+
# the items returned by asking YouTube for a list of videos.
|
15
|
+
# @see https://developers.google.com/youtube/v3/docs/videos#resource
|
10
16
|
def new_item(data)
|
11
17
|
Yt::Video.new id: data['id']['videoId'], snippet: data['snippet'], auth: @auth
|
12
18
|
end
|
13
19
|
|
20
|
+
# @return [Hash] the parameters to submit to YouTube to list videos.
|
21
|
+
# @see https://developers.google.com/youtube/v3/docs/search/list
|
14
22
|
def list_params
|
15
23
|
super.tap do |params|
|
16
24
|
params[:params] = @parent.videos_params.merge videos_params
|
data/lib/yt/collections/views.rb
CHANGED
@@ -1,37 +1,17 @@
|
|
1
|
-
require 'yt/collections/
|
1
|
+
require 'yt/collections/reports'
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Collections
|
5
|
-
class Views <
|
6
|
-
|
7
|
-
def within(days_range)
|
8
|
-
@days_range = days_range
|
9
|
-
Hash[*flat_map{|daily_view| daily_view}]
|
10
|
-
end
|
5
|
+
class Views < Reports
|
11
6
|
|
12
7
|
private
|
13
8
|
|
14
|
-
def
|
15
|
-
|
16
|
-
[Date.iso8601(data.first), (data.last.to_i if data.last)]
|
17
|
-
end
|
18
|
-
|
19
|
-
def list_params
|
20
|
-
super.tap do |params|
|
21
|
-
params[:path] = '/youtube/analytics/v1/reports'
|
22
|
-
params[:params] = {}.tap do |params|
|
23
|
-
params['ids'] = "contentOwner==#{@auth.owner_name}"
|
24
|
-
params['filters'] = "channel==#{@parent.id}"
|
25
|
-
params['start-date'] = @days_range.begin
|
26
|
-
params['end-date'] = @days_range.end
|
27
|
-
params['metrics'] = :views
|
28
|
-
params['dimensions'] = :day
|
29
|
-
end
|
30
|
-
end
|
9
|
+
def metrics
|
10
|
+
:views
|
31
11
|
end
|
32
12
|
|
33
|
-
def
|
34
|
-
|
13
|
+
def value_of(data)
|
14
|
+
data.last.to_i if data.last
|
35
15
|
end
|
36
16
|
end
|
37
17
|
end
|
data/lib/yt/config.rb
CHANGED
@@ -1,53 +1,54 @@
|
|
1
1
|
require 'yt/models/configuration'
|
2
2
|
|
3
3
|
module Yt
|
4
|
-
# Provides methods to read and write
|
4
|
+
# Provides methods to read and write global configuration settings.
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# highest precedence).
|
9
|
-
#
|
10
|
-
# @note Config is the only module auto-loaded in the Yt module,
|
11
|
-
# in order to have a syntax as easy as Yt.configure
|
12
|
-
#
|
13
|
-
# @example A server-to-server YouTube client app
|
6
|
+
# A typical usage is to set the API keys retrieved from the
|
7
|
+
# {http://console.developers.google.com Google Developers Console}.
|
14
8
|
#
|
9
|
+
# @example Set the API key for a server-only YouTube app:
|
15
10
|
# Yt.configure do |config|
|
16
11
|
# config.api_key = 'ABCDEFGHIJ1234567890'
|
17
12
|
# end
|
18
13
|
#
|
19
|
-
# @example
|
20
|
-
#
|
14
|
+
# @example Set the API client id/secret for a web-client YouTube app:
|
21
15
|
# Yt.configure do |config|
|
22
16
|
# config.client_id = 'ABCDEFGHIJ1234567890'
|
23
17
|
# config.client_secret = 'ABCDEFGHIJ1234567890'
|
24
18
|
# end
|
25
19
|
#
|
20
|
+
# Note that Yt.configure has precedence over values through with
|
21
|
+
# environment variables (see {Yt::Models::Configuration}).
|
22
|
+
#
|
26
23
|
module Config
|
27
|
-
# Yields the global configuration to
|
28
|
-
# @yield [Yt::Configuration] global configuration
|
24
|
+
# Yields the global configuration to the given block.
|
29
25
|
#
|
30
26
|
# @example
|
31
27
|
# Yt.configure do |config|
|
32
28
|
# config.api_key = 'ABCDEFGHIJ1234567890'
|
33
29
|
# end
|
34
|
-
#
|
30
|
+
#
|
31
|
+
# @yield [Yt::Models::Configuration] The global configuration.
|
35
32
|
def configure
|
36
33
|
yield configuration if block_given?
|
37
34
|
end
|
38
35
|
|
39
|
-
# Returns the global
|
40
|
-
#
|
41
|
-
#
|
36
|
+
# Returns the global {Yt::Models::Configuration} object.
|
37
|
+
#
|
38
|
+
# While this method _can_ be used to read and write configuration settings,
|
39
|
+
# it is easier to use {Yt::Config#configure} Yt.configure}.
|
42
40
|
#
|
43
41
|
# @example
|
44
42
|
# Yt.configuration.api_key = 'ABCDEFGHIJ1234567890'
|
45
|
-
#
|
46
|
-
# @
|
43
|
+
#
|
44
|
+
# @return [Yt::Models::Configuration] The global configuration.
|
47
45
|
def configuration
|
48
46
|
@configuration ||= Yt::Configuration.new
|
49
47
|
end
|
50
48
|
end
|
51
49
|
|
50
|
+
# @note Config is the only module auto-loaded in the Yt module,
|
51
|
+
# in order to have a syntax as easy as Yt.configure
|
52
|
+
|
52
53
|
extend Config
|
53
54
|
end
|
data/lib/yt/models/account.rb
CHANGED
@@ -3,14 +3,30 @@ require 'yt/associations/authentications'
|
|
3
3
|
|
4
4
|
module Yt
|
5
5
|
module Models
|
6
|
-
# Provides methods to
|
6
|
+
# Provides methods to interact with YouTube accounts.
|
7
|
+
# @see https://developers.google.com/youtube/v3/guides/authentication
|
7
8
|
class Account < Base
|
8
9
|
include Associations::Authentications
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
# @!attribute channel
|
12
|
+
# @return [Yt::Models::Channel] the account’s channel.
|
13
|
+
has_one :channel
|
14
|
+
delegate :playlists, :create_playlist, :delete_playlists, to: :channel
|
15
|
+
|
16
|
+
# @!attribute user_info
|
17
|
+
# @return [Yt::Models::UserInfo] the account’s profile information.
|
18
|
+
has_one :user_info
|
19
|
+
delegate :id, :email, :has_verified_email?, :gender, :name,
|
20
|
+
:given_name, :family_name, :profile_url, :avatar_url,
|
21
|
+
:locale, :hd, to: :user_info
|
22
|
+
|
23
|
+
# @!attribute videos
|
24
|
+
# @return [Yt::Collections::Videos] the videos owned by the account.
|
12
25
|
has_many :videos
|
13
26
|
|
27
|
+
# @private
|
28
|
+
# Tells `has_many :videos` that account.videos should return all the
|
29
|
+
# videos *owned by* the account (public, private, unlisted).
|
14
30
|
def videos_params
|
15
31
|
{forMine: true}
|
16
32
|
end
|
data/lib/yt/models/annotation.rb
CHANGED
@@ -1,82 +1,71 @@
|
|
1
1
|
module Yt
|
2
2
|
module Models
|
3
|
-
# Provides methods to
|
3
|
+
# Provides methods to interact with YouTube annotations.
|
4
|
+
# @see https://www.youtube.com/yt/playbook/annotations.html
|
4
5
|
class Annotation
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# @note
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# @param [String] xml_data The YouTube XML representation of an annotation
|
6
|
+
# @param [Hash] options the options to initialize an Annotation.
|
7
|
+
# @option options [String] :data The XML representation of an annotation
|
8
|
+
# @note YouTube API V3 does not provide access to video annotations,
|
9
|
+
# therefore the XML endpoint is used to retrieve them and its response
|
10
|
+
# is passed to the Annotation initializer.
|
12
11
|
def initialize(options = {})
|
13
12
|
@data = options[:data]
|
14
13
|
end
|
15
14
|
|
16
|
-
#
|
17
|
-
#
|
15
|
+
# @return [Boolean] whether the text box surrounding the annotation is
|
16
|
+
# completely in the top y% of the video frame.
|
18
17
|
# @param [Integer] y Vertical position in the Youtube video (0 to 100)
|
19
|
-
#
|
20
|
-
# @return [Boolean] Whether the box remains above y
|
21
18
|
def above?(y)
|
22
19
|
top && top < y
|
23
20
|
end
|
24
21
|
|
25
|
-
#
|
26
|
-
#
|
22
|
+
# @return [Boolean] whether the text box surrounding the annotation is
|
23
|
+
# completely in the bottom y% of the video frame.
|
27
24
|
# @param [Integer] y Vertical position in the Youtube video (0 to 100)
|
28
|
-
#
|
29
|
-
# @return [Boolean] Whether the box remains below y
|
30
25
|
def below?(y)
|
31
26
|
bottom && bottom > y
|
32
27
|
end
|
33
28
|
|
34
|
-
#
|
35
|
-
# Should a branding watermark also counts, because it links to the channel?
|
36
|
-
#
|
37
|
-
# @return [Boolean] Whether there is a link to subscribe in the annotation
|
29
|
+
# @return [Boolean] whether the annotation includes a link to subscribe.
|
38
30
|
def has_link_to_subscribe?(options = {}) # TODO: options for which videos
|
39
31
|
link_class == '5'
|
40
32
|
end
|
41
33
|
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# @return [Boolean] Whether there is a link to a video in the annotation
|
34
|
+
# @return [Boolean] whether the annotation includes a link to a video,
|
35
|
+
# either directly in the text, or as an "Invideo featured video".
|
46
36
|
def has_link_to_video?(options = {}) # TODO: options for which videos
|
47
37
|
link_class == '1' || type == 'promotion'
|
48
38
|
end
|
49
39
|
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
# @return [Boolean] Whether there is a link to a playlist in the annotation
|
40
|
+
# @return [Boolean] whether the annotation includes a link to a playlist,
|
41
|
+
# or to a video embedded in a playlist.
|
54
42
|
def has_link_to_playlist?
|
55
43
|
link_class == '2' || text.include?('&list=')
|
56
44
|
end
|
57
45
|
|
58
|
-
#
|
59
|
-
#
|
60
|
-
# @return [Boolean] Whether the link opens in the same window
|
46
|
+
# @return [Boolean] whether the annotation includes a link that will
|
47
|
+
# open in the current browser window.
|
61
48
|
def has_link_to_same_window?
|
62
49
|
link_target == 'current'
|
63
50
|
end
|
64
51
|
|
65
|
-
#
|
66
|
-
#
|
67
|
-
# @return [Boolean] Whether the annotation comes from InVideo Programming
|
52
|
+
# @return [Boolean] whether the annotation is an "InVideo Programming".
|
68
53
|
def has_invideo_programming?
|
69
54
|
type == 'promotion' || type == 'branding'
|
70
55
|
end
|
71
56
|
|
72
|
-
# @
|
57
|
+
# @param [Numeric] seconds the number of seconds
|
58
|
+
# @return [Boolean] whether the annotation starts after the number of
|
59
|
+
# seconds indicated.
|
73
60
|
# @note This is broken for invideo programming, because they do not
|
74
61
|
# have the timestamp in the region, but in the "data" field
|
75
62
|
def starts_after?(seconds)
|
76
63
|
timestamps.first > seconds if timestamps.any?
|
77
64
|
end
|
78
65
|
|
79
|
-
# @
|
66
|
+
# @param [Numeric] seconds the number of seconds
|
67
|
+
# @return [Boolean] whether the annotation starts before the number of
|
68
|
+
# seconds indicated.
|
80
69
|
# @note This is broken for invideo programming, because they do not
|
81
70
|
# have the timestamp in the region, but in the "data" field
|
82
71
|
def starts_before?(seconds)
|
data/lib/yt/models/base.rb
CHANGED
@@ -5,13 +5,13 @@ require 'yt/errors/request_error'
|
|
5
5
|
require 'active_support/core_ext/module/delegation' # for delegate
|
6
6
|
require 'active_support/core_ext/string/inflections' # for camelize
|
7
7
|
|
8
|
-
|
9
8
|
module Yt
|
10
9
|
module Models
|
11
10
|
class Base
|
12
11
|
include Actions::Delete
|
13
12
|
include Actions::Update
|
14
13
|
|
14
|
+
# @private
|
15
15
|
def self.has_many(attributes)
|
16
16
|
attributes = attributes.to_s
|
17
17
|
require "yt/collections/#{attributes}"
|
@@ -24,9 +24,8 @@ module Yt
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
# @private
|
28
|
+
def self.has_one(attribute)
|
30
29
|
attributes = attribute.to_s.pluralize
|
31
30
|
has_many attributes
|
32
31
|
|
data/lib/yt/models/channel.rb
CHANGED
@@ -4,30 +4,79 @@ require 'yt/associations/views'
|
|
4
4
|
|
5
5
|
module Yt
|
6
6
|
module Models
|
7
|
+
# Provides methods to interact with YouTube channels.
|
8
|
+
# @see https://developers.google.com/youtube/v3/docs/channels
|
7
9
|
class Channel < Resource
|
8
10
|
include Associations::Earnings
|
9
11
|
include Associations::Views
|
10
12
|
|
13
|
+
# @!attribute subscriptions
|
14
|
+
# @return [Yt::Collections::Subscriptions] the channel’s subscriptions.
|
11
15
|
has_many :subscriptions
|
16
|
+
|
17
|
+
# @!attribute videos
|
18
|
+
# @return [Yt::Collections::Videos] the channel’s videos.
|
12
19
|
has_many :videos
|
20
|
+
|
21
|
+
# @!attribute playlists
|
22
|
+
# @return [Yt::Collections::Playlists] the channel’s playlists.
|
13
23
|
has_many :playlists
|
14
24
|
|
25
|
+
# Returns whether the authenticated account is subscribed to the channel.
|
26
|
+
#
|
27
|
+
# This method requires {Resource#auth auth} to return an
|
28
|
+
# authenticated instance of {Yt::Account}.
|
29
|
+
# @return [Boolean] whether the account is subscribed to the channel.
|
30
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
31
|
+
# return an authenticated account.
|
15
32
|
def subscribed?
|
16
33
|
subscriptions.any?{|s| s.exists?}
|
17
34
|
end
|
18
35
|
|
36
|
+
# Subscribes the authenticated account to the channel.
|
37
|
+
# Does not raise an error if the account was already subscribed.
|
38
|
+
#
|
39
|
+
# This method requires {Resource#auth auth} to return an
|
40
|
+
# authenticated instance of {Yt::Account}.
|
41
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
42
|
+
# return an authenticated account.
|
19
43
|
def subscribe
|
20
44
|
subscriptions.insert ignore_errors: true
|
21
45
|
end
|
22
46
|
|
47
|
+
# Subscribes the authenticated account to the channel.
|
48
|
+
# Raises an error if the account was already subscribed.
|
49
|
+
#
|
50
|
+
# This method requires {Resource#auth auth} to return an
|
51
|
+
# authenticated instance of {Yt::Account}.
|
52
|
+
# @raise [Yt::Errors::RequestError] if {Resource#auth auth} was already
|
53
|
+
# subscribed to the channel.
|
54
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
55
|
+
# return an authenticated account.
|
23
56
|
def subscribe!
|
24
57
|
subscriptions.insert
|
25
58
|
end
|
26
59
|
|
60
|
+
# Unsubscribes the authenticated account from the channel.
|
61
|
+
# Does not raise an error if the account was not subscribed.
|
62
|
+
#
|
63
|
+
# This method requires {Resource#auth auth} to return an
|
64
|
+
# authenticated instance of {Yt::Account}.
|
65
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
66
|
+
# return an authenticated account.
|
27
67
|
def unsubscribe
|
28
68
|
subscriptions.delete_all({}, ignore_errors: true)
|
29
69
|
end
|
30
70
|
|
71
|
+
# Unsubscribes the authenticated account from the channel.
|
72
|
+
# Raises an error if the account was not subscribed.
|
73
|
+
#
|
74
|
+
# This method requires {Resource#auth auth} to return an
|
75
|
+
# authenticated instance of {Yt::Account}.
|
76
|
+
# @raise [Yt::Errors::RequestError] if {Resource#auth auth} was not
|
77
|
+
# subscribed to the channel.
|
78
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
79
|
+
# return an authenticated account.
|
31
80
|
def unsubscribe!
|
32
81
|
subscriptions.delete_all
|
33
82
|
end
|
@@ -40,6 +89,9 @@ module Yt
|
|
40
89
|
playlists.delete_all attrs
|
41
90
|
end
|
42
91
|
|
92
|
+
# @private
|
93
|
+
# Tells `has_many :videos` that channel.videos should return all the
|
94
|
+
# videos publicly available on the channel.
|
43
95
|
def videos_params
|
44
96
|
{channelId: id}
|
45
97
|
end
|
@@ -1,27 +1,48 @@
|
|
1
1
|
module Yt
|
2
2
|
module Models
|
3
|
-
#
|
3
|
+
# Provides an object to store global configuration settings.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# @example A server-to-server YouTube client app
|
10
|
-
#
|
11
|
-
# Yt.configure do |config|
|
12
|
-
# config.api_key = 'ABCDEFGHIJ1234567890'
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# @example A web YouTube client app
|
5
|
+
# This class is typically not used directly, but by calling
|
6
|
+
# {Yt::Config#configure Yt.configure}, which creates and updates a single
|
7
|
+
# instance of {Yt::Models::Configuration}.
|
16
8
|
#
|
9
|
+
# @example Set the API client id/secret for a web-client YouTube app:
|
17
10
|
# Yt.configure do |config|
|
18
11
|
# config.client_id = 'ABCDEFGHIJ1234567890'
|
19
12
|
# config.client_secret = 'ABCDEFGHIJ1234567890'
|
20
13
|
# end
|
21
14
|
#
|
15
|
+
# @see Yt::Config for more examples.
|
16
|
+
#
|
17
|
+
# An alternative way to set global configuration settings is by storing
|
18
|
+
# them in the following environment variables:
|
19
|
+
#
|
20
|
+
# * +YT_CLIENT_ID+ to store the Client ID for web/device apps
|
21
|
+
# * +YT_CLIENT_SECRET+ to store the Client Secret for web/device apps
|
22
|
+
# * +YT_API_KEY+ to store the API key for server/browser apps
|
23
|
+
#
|
24
|
+
# In case both methods are used together,
|
25
|
+
# {Yt::Config#configure Yt.configure} takes precedence.
|
26
|
+
#
|
27
|
+
# @example Set the API client id/secret for a web-client YouTube app:
|
28
|
+
# ENV['YT_CLIENT_ID'] = 'ABCDEFGHIJ1234567890'
|
29
|
+
# ENV['YT_CLIENT_SECRET'] = 'ABCDEFGHIJ1234567890'
|
30
|
+
#
|
22
31
|
class Configuration
|
23
|
-
|
32
|
+
# @return [String] the Client ID for web/device YouTube applications.
|
33
|
+
# @see https://console.developers.google.com Google Developers Console
|
34
|
+
attr_accessor :client_id
|
35
|
+
|
36
|
+
# @return [String] the Client Secret for web/device YouTube applications.
|
37
|
+
# @see https://console.developers.google.com Google Developers Console
|
38
|
+
attr_accessor :client_secret
|
39
|
+
|
40
|
+
# @return [String] the API key for server/browser YouTube applications.
|
41
|
+
# @see https://console.developers.google.com Google Developers Console
|
42
|
+
attr_accessor :api_key
|
24
43
|
|
44
|
+
# Initialize the global configuration settings, using the values of
|
45
|
+
# the specified following environment variables by default.
|
25
46
|
def initialize
|
26
47
|
@client_id = ENV['YT_CLIENT_ID']
|
27
48
|
@client_secret = ENV['YT_CLIENT_SECRET']
|
@@ -2,9 +2,16 @@ require 'yt/models/account'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
-
# Provides methods to
|
5
|
+
# Provides methods to interact with YouTube CMS accounts.
|
6
|
+
# @see https://cms.youtube.com
|
7
|
+
# @see https://developers.google.com/youtube/analytics/v1/content_owner_reports
|
6
8
|
class ContentOwner < Account
|
9
|
+
|
10
|
+
# @!attribute partnered_channels
|
11
|
+
# @return [Yt::Collection::PartneredChannels] the channels managed by the CMS account.
|
7
12
|
has_many :partnered_channels
|
13
|
+
|
14
|
+
# @return [String] the name of the CMS account.
|
8
15
|
attr_reader :owner_name
|
9
16
|
|
10
17
|
def initialize(options = {})
|
@@ -6,18 +6,16 @@ module Yt
|
|
6
6
|
# Resources with descriptions are: videos and channels.
|
7
7
|
#
|
8
8
|
# @example
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# description.length # => 127
|
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
|
13
12
|
#
|
14
13
|
class Description < String
|
15
14
|
# Returns whether the description includes a YouTube video URL
|
16
15
|
#
|
17
16
|
# @example
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# description.has_link_to_video? #=> true
|
17
|
+
# description = Yt::Description.new 'Link to video: youtube.com/watch?v=MESycYJytkU'
|
18
|
+
# description.has_link_to_video? #=> true
|
21
19
|
#
|
22
20
|
# @return [Boolean] Whether the description includes a link to a video
|
23
21
|
def has_link_to_video?
|
@@ -29,9 +27,8 @@ module Yt
|
|
29
27
|
# Returns whether the description includes a YouTube channel URL
|
30
28
|
#
|
31
29
|
# @example
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# description.has_link_to_channel? #=> true
|
30
|
+
# description = Yt::Description.new 'Link to channel: youtube.com/fullscreen'
|
31
|
+
# description.has_link_to_channel? #=> true
|
35
32
|
#
|
36
33
|
# @return [Boolean] Whether the description includes a link to a channel
|
37
34
|
def has_link_to_channel?(options = {}) # TODO: which channel
|
@@ -43,9 +40,8 @@ module Yt
|
|
43
40
|
# Returns whether the description includes a YouTube subscription URL
|
44
41
|
#
|
45
42
|
# @example
|
46
|
-
#
|
47
|
-
#
|
48
|
-
# description.has_link_to_subscribe? #=> true
|
43
|
+
# description = Yt::Description.new 'Link to subscribe: youtube.com/subscription_center?add_user=fullscreen'
|
44
|
+
# description.has_link_to_subscribe? #=> true
|
49
45
|
#
|
50
46
|
# @return [Boolean] Whether the description includes a link to subscribe
|
51
47
|
def has_link_to_subscribe?(options = {}) # TODO: which channel
|
@@ -57,9 +53,8 @@ module Yt
|
|
57
53
|
# Returns whether the description includes a YouTube playlist URL
|
58
54
|
#
|
59
55
|
# @example
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# description.has_link_to_playlist? #=> true
|
56
|
+
# description = Yt::Description.new 'Link to playlist: youtube.com/playlist?list=LLxO1tY8h1AhOz0T4ENwmpow'
|
57
|
+
# description.has_link_to_playlist? #=> true
|
63
58
|
#
|
64
59
|
# @return [Boolean] Whether the description includes a link to a playlist
|
65
60
|
def has_link_to_playlist?
|
data/lib/yt/models/playlist.rb
CHANGED
@@ -2,7 +2,12 @@ require 'yt/models/resource'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Provides methods to interact with YouTube playlists.
|
6
|
+
# @see https://developers.google.com/youtube/v3/docs/playlists
|
5
7
|
class Playlist < Resource
|
8
|
+
|
9
|
+
# @!attribute playlist_items
|
10
|
+
# @return [Yt::Collections::PlaylistItems] the playlist’s items.
|
6
11
|
has_many :playlist_items
|
7
12
|
|
8
13
|
def delete
|
@@ -2,8 +2,18 @@ require 'yt/models/base'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Provides methods to interact with YouTube playlist items.
|
6
|
+
# @see https://developers.google.com/youtube/v3/docs/playlistItems
|
5
7
|
class PlaylistItem < Base
|
6
|
-
|
8
|
+
# @return [String] the ID that uniquely identify a YouTube playlist item.
|
9
|
+
attr_reader :id
|
10
|
+
|
11
|
+
# @return [String] the order in which the item appears in the playlist.
|
12
|
+
# The value uses a zero-based index, so the first item has a position
|
13
|
+
# of 0, the second item has a position of 1, and so forth.
|
14
|
+
attr_reader :position
|
15
|
+
|
16
|
+
attr_reader :video
|
7
17
|
|
8
18
|
def initialize(options = {})
|
9
19
|
@id = options[:id]
|
data/lib/yt/models/rating.rb
CHANGED
@@ -2,7 +2,12 @@ require 'yt/models/base'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Provides methods to modify the rating of a video on YouTube.
|
6
|
+
# @see https://developers.google.com/youtube/v3/docs/videos/rate
|
7
|
+
# @see https://developers.google.com/youtube/v3/docs/videos/getRating
|
5
8
|
class Rating < Base
|
9
|
+
# @return [Symbol or nil] the rating of a video (if present).
|
10
|
+
# Valid values are: :dislike, :like, :none, :unspecified
|
6
11
|
attr_reader :rating
|
7
12
|
|
8
13
|
def initialize(options = {})
|
data/lib/yt/models/request.rb
CHANGED
@@ -29,7 +29,7 @@ module Yt
|
|
29
29
|
if response.is_a? @expected_response
|
30
30
|
response.tap{|response| response.body = parse_format response.body}
|
31
31
|
else
|
32
|
-
run_again? ? run : raise(
|
32
|
+
run_again? ? run : raise(response_error, request_error_message)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -39,6 +39,8 @@ module Yt
|
|
39
39
|
@response ||= Net::HTTP.start(*net_http_options) do |http|
|
40
40
|
http.request http_request
|
41
41
|
end
|
42
|
+
rescue OpenSSL::SSL::SSLError, Errno::ETIMEDOUT, Errno::ENETUNREACH => e
|
43
|
+
@response ||= e
|
42
44
|
end
|
43
45
|
|
44
46
|
def http_request
|
@@ -105,7 +107,7 @@ module Yt
|
|
105
107
|
# random error that can be fixed by waiting for some seconds and running
|
106
108
|
# the exact same query, or the access token needs to be refreshed.
|
107
109
|
def run_again?
|
108
|
-
|
110
|
+
refresh_token_and_retry? || server_error? && sleep_and_retry?
|
109
111
|
end
|
110
112
|
|
111
113
|
# Once in a while, YouTube responds with 500, or 503, or 400 Error and
|
@@ -113,17 +115,20 @@ module Yt
|
|
113
115
|
# In all these cases, running the same query after some seconds fixes
|
114
116
|
# the issue. This it not documented by YouTube and hardly testable, but
|
115
117
|
# trying again is a workaround that works and hardly causes any damage.
|
116
|
-
def
|
118
|
+
def sleep_and_retry?(max_retries = 1)
|
117
119
|
@retries_so_far ||= -1
|
118
120
|
@retries_so_far += 1
|
119
|
-
if (@retries_so_far < max_retries)
|
121
|
+
if (@retries_so_far < max_retries)
|
120
122
|
@response = @http_request = @uri = nil
|
121
123
|
sleep 3
|
122
124
|
end
|
123
125
|
end
|
124
126
|
|
125
|
-
def
|
127
|
+
def server_error?
|
126
128
|
case response
|
129
|
+
when OpenSSL::SSL::SSLError then true
|
130
|
+
when Errno::ETIMEDOUT then true
|
131
|
+
when Errno::ENETUNREACH then true
|
127
132
|
when Net::HTTPServerError then true
|
128
133
|
when Net::HTTPBadRequest then response.body =~ /did not conform/
|
129
134
|
else false
|
@@ -133,26 +138,28 @@ module Yt
|
|
133
138
|
# If a request authorized with an access token returns 401, then the
|
134
139
|
# access token might have expired. If a refresh token is also present,
|
135
140
|
# try to run the request one more time with a refreshed access token.
|
136
|
-
def
|
141
|
+
def refresh_token_and_retry?
|
137
142
|
if response.is_a? Net::HTTPUnauthorized
|
138
143
|
@response = @http_request = @uri = nil
|
139
144
|
@auth.refresh
|
140
145
|
end if @auth.respond_to? :refresh
|
141
146
|
end
|
142
147
|
|
143
|
-
def
|
144
|
-
|
145
|
-
|
148
|
+
def response_error
|
149
|
+
if server_error?
|
150
|
+
Errors::ServerError
|
151
|
+
else case response
|
146
152
|
when Net::HTTPUnauthorized then Errors::Unauthorized
|
147
153
|
when Net::HTTPForbidden then Errors::Forbidden
|
148
154
|
else Errors::RequestError
|
155
|
+
end
|
149
156
|
end
|
150
157
|
end
|
151
158
|
|
152
159
|
def request_error_message
|
153
160
|
{}.tap do |message|
|
154
161
|
message[:request_curl] = as_curl
|
155
|
-
message[:response_body] = JSON(response.body) rescue response.
|
162
|
+
message[:response_body] = JSON(response.body) rescue response.inspect
|
156
163
|
end.to_json
|
157
164
|
end
|
158
165
|
|
data/lib/yt/models/resource.rb
CHANGED
@@ -6,8 +6,13 @@ module Yt
|
|
6
6
|
class Resource < Base
|
7
7
|
attr_reader :auth
|
8
8
|
has_one :id
|
9
|
-
|
10
|
-
has_one :
|
9
|
+
|
10
|
+
has_one :snippet
|
11
|
+
delegate :title, :description, :thumbnail_url, :published_at,
|
12
|
+
:tags, to: :snippet
|
13
|
+
|
14
|
+
has_one :status
|
15
|
+
delegate :privacy_status, :public?, :private?, :unlisted?, to: :status
|
11
16
|
|
12
17
|
def initialize(options = {})
|
13
18
|
@url = URL.new(options[:url]) if options[:url]
|
@@ -2,8 +2,10 @@ require 'yt/models/base'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Provides methods to interact with YouTube subscriptions.
|
6
|
+
# @see https://developers.google.com/youtube/v3/docs/subscriptions
|
5
7
|
class Subscription < Base
|
6
|
-
|
8
|
+
# @return [String] the ID that uniquely identify a YouTube subscription.
|
7
9
|
attr_reader :id
|
8
10
|
|
9
11
|
def initialize(options = {})
|
data/lib/yt/models/user_info.rb
CHANGED
@@ -2,64 +2,66 @@ require 'yt/models/base'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Provides methods to retrieve an account’s user profile.
|
6
|
+
# @see https://developers.google.com/+/api/latest/people/getOpenIdConnect
|
5
7
|
class UserInfo < Base
|
6
8
|
def initialize(options = {})
|
7
9
|
@data = options[:data]
|
8
10
|
end
|
9
11
|
|
10
|
-
# @return [String]
|
12
|
+
# @return [String] the user’s ID.
|
11
13
|
def id
|
12
14
|
@id ||= @data.fetch 'id', ''
|
13
15
|
end
|
14
16
|
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# @return [String] Email of the YouTube account
|
17
|
+
# @return [String] the user’s email address.
|
18
18
|
def email
|
19
19
|
@email ||= @data.fetch 'email', ''
|
20
20
|
end
|
21
21
|
|
22
|
-
# @return [Boolean]
|
22
|
+
# @return [Boolean] whether the email address is verified.
|
23
23
|
def has_verified_email?
|
24
24
|
@verified_email ||= @data.fetch 'verified_email', false
|
25
25
|
end
|
26
26
|
|
27
|
-
# @return [String] name
|
27
|
+
# @return [String] the user's full name.
|
28
28
|
def name
|
29
29
|
@name ||= @data.fetch 'name', ''
|
30
30
|
end
|
31
31
|
|
32
|
-
# @return [String]
|
32
|
+
# @return [String] the user’s given (first) name.
|
33
33
|
def given_name
|
34
34
|
@given_name ||= @data.fetch 'given_name', ''
|
35
35
|
end
|
36
36
|
|
37
|
-
# @return [String]
|
37
|
+
# @return [String] the user’s family (last) name.
|
38
38
|
def family_name
|
39
39
|
@family_name ||= @data.fetch 'family_name', ''
|
40
40
|
end
|
41
41
|
|
42
|
-
# @return [String]
|
42
|
+
# @return [String] the URL of the user’s profile page.
|
43
43
|
def profile_url
|
44
44
|
@profile_url ||= @data.fetch 'link', ''
|
45
45
|
end
|
46
46
|
|
47
|
-
# @return [String]
|
47
|
+
# @return [String] the URL of the user’s profile picture.
|
48
48
|
def avatar_url
|
49
49
|
@avatar_url ||= @data.fetch 'picture', ''
|
50
50
|
end
|
51
51
|
|
52
|
-
# @return [String] gender
|
52
|
+
# @return [String] the person’s gender. Possible values include, but
|
53
|
+
# are not limited to, "male", "female", "other".
|
53
54
|
def gender
|
54
55
|
@gender ||= @data.fetch 'gender', ''
|
55
56
|
end
|
56
57
|
|
57
|
-
# @return [String] locale
|
58
|
+
# @return [String] the user’s preferred locale.
|
58
59
|
def locale
|
59
60
|
@locale ||= @data.fetch 'locale', ''
|
60
61
|
end
|
61
62
|
|
62
|
-
# @return [String]
|
63
|
+
# @return [String] the hosted domain name for the user’s Google Apps
|
64
|
+
# account. For instance, example.com.
|
63
65
|
def hd
|
64
66
|
@hd ||= @data.fetch 'hd', ''
|
65
67
|
end
|
data/lib/yt/models/video.rb
CHANGED
@@ -2,25 +2,64 @@ require 'yt/models/resource'
|
|
2
2
|
|
3
3
|
module Yt
|
4
4
|
module Models
|
5
|
+
# Provides methods to interact with YouTube videos.
|
6
|
+
# @see https://developers.google.com/youtube/v3/docs/videos
|
5
7
|
class Video < Resource
|
6
|
-
|
8
|
+
# @!attribute details_set
|
9
|
+
# @return [Yt::Models::DetailsSet] the video’s content details.
|
10
|
+
has_one :details_set
|
11
|
+
delegate :duration, to: :details_set
|
12
|
+
|
13
|
+
# @!attribute rating
|
14
|
+
# @return [Yt::Models::Rating] the video’s rating.
|
7
15
|
has_one :rating
|
16
|
+
|
17
|
+
# @!attribute annotations
|
18
|
+
# @return [Yt::Collections::Annotations] the video’s annotations.
|
8
19
|
has_many :annotations
|
9
20
|
|
21
|
+
# Returns whether the authenticated account likes the video.
|
22
|
+
#
|
23
|
+
# This method requires {Resource#auth auth} to return an
|
24
|
+
# authenticated instance of {Yt::Account}.
|
25
|
+
# @return [Boolean] whether the account likes the video.
|
26
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
27
|
+
# return an authenticated account.
|
10
28
|
def liked?
|
11
29
|
rating.rating == :like
|
12
30
|
end
|
13
31
|
|
32
|
+
# Likes the video on behalf of the authenticated account.
|
33
|
+
#
|
34
|
+
# This method requires {Resource#auth auth} to return an
|
35
|
+
# authenticated instance of {Yt::Account}.
|
36
|
+
# @return [Boolean] whether the account likes the video.
|
37
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
38
|
+
# return an authenticated account.
|
14
39
|
def like
|
15
40
|
rating.update :like
|
16
41
|
liked?
|
17
42
|
end
|
18
43
|
|
44
|
+
# Dislikes the video on behalf of the authenticated account.
|
45
|
+
#
|
46
|
+
# This method requires {Resource#auth auth} to return an
|
47
|
+
# authenticated instance of {Yt::Account}.
|
48
|
+
# @return [Boolean] whether the account does not like the video.
|
49
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
50
|
+
# return an authenticated account.
|
19
51
|
def dislike
|
20
52
|
rating.update :dislike
|
21
53
|
!liked?
|
22
54
|
end
|
23
55
|
|
56
|
+
# Resets the rating of the video on behalf of the authenticated account.
|
57
|
+
#
|
58
|
+
# This method requires {Resource#auth auth} to return an
|
59
|
+
# authenticated instance of {Yt::Account}.
|
60
|
+
# @return [Boolean] whether the account does not like the video.
|
61
|
+
# @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
|
62
|
+
# return an authenticated account.
|
24
63
|
def unlike
|
25
64
|
rating.update :none
|
26
65
|
!liked?
|
data/lib/yt/version.rb
CHANGED
data/spec/models/request_spec.rb
CHANGED
@@ -6,11 +6,12 @@ describe Yt::Request do
|
|
6
6
|
subject(:request) { Yt::Request.new host: 'example.com' }
|
7
7
|
let(:response) { response_class.new nil, nil, nil }
|
8
8
|
let(:response_body) { }
|
9
|
-
before { allow(response).to receive(:body).and_return response_body }
|
10
|
-
before { expect(Net::HTTP).to receive(:start).once.and_return response }
|
11
9
|
|
12
10
|
describe '#run' do
|
13
11
|
context 'given a request that returns' do
|
12
|
+
before { allow(response).to receive(:body).and_return response_body }
|
13
|
+
before { expect(Net::HTTP).to receive(:start).once.and_return response }
|
14
|
+
|
14
15
|
context 'a success code 2XX' do
|
15
16
|
let(:response_class) { Net::HTTPOK }
|
16
17
|
|
@@ -70,5 +71,73 @@ describe Yt::Request do
|
|
70
71
|
it { expect{request.run}.to fail }
|
71
72
|
end
|
72
73
|
end
|
74
|
+
|
75
|
+
# TODO: clean up the following tests, removing repetitions
|
76
|
+
context 'given a request that raises' do
|
77
|
+
before { expect(Net::HTTP).to receive(:start).once.and_raise http_error }
|
78
|
+
|
79
|
+
# NOTE: This test is just a reflection of YouTube irrational behavior of
|
80
|
+
# being unavailable once in a while, and therefore causing Net::HTTP to
|
81
|
+
# fail, although retrying after some seconds works.
|
82
|
+
context 'an OpenSSL::SSL::SSLError' do
|
83
|
+
let(:http_error) { OpenSSL::SSL::SSLError.new }
|
84
|
+
|
85
|
+
context 'every time' do
|
86
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_raise http_error }
|
87
|
+
|
88
|
+
it { expect{request.run}.to fail }
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'but works the second time' do
|
92
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_return retry_response }
|
93
|
+
before { allow(retry_response).to receive(:body) }
|
94
|
+
let(:retry_response) { Net::HTTPOK.new nil, nil, nil }
|
95
|
+
|
96
|
+
it { expect{request.run}.not_to fail }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# NOTE: This test is just a reflection of YouTube irrational behavior of
|
101
|
+
# being unavailable once in a while, and therefore causing Net::HTTP to
|
102
|
+
# fail, although retrying after some seconds works.
|
103
|
+
context 'an Errno::ETIMEDOUT' do
|
104
|
+
let(:http_error) { Errno::ETIMEDOUT.new }
|
105
|
+
|
106
|
+
context 'every time' do
|
107
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_raise http_error }
|
108
|
+
|
109
|
+
it { expect{request.run}.to fail }
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'but works the second time' do
|
113
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_return retry_response }
|
114
|
+
before { allow(retry_response).to receive(:body) }
|
115
|
+
let(:retry_response) { Net::HTTPOK.new nil, nil, nil }
|
116
|
+
|
117
|
+
it { expect{request.run}.not_to fail }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# NOTE: This test is just a reflection of YouTube irrational behavior of
|
122
|
+
# being unavailable once in a while, and therefore causing Net::HTTP to
|
123
|
+
# fail, although retrying after some seconds works.
|
124
|
+
context 'an Errno::ENETUNREACH' do
|
125
|
+
let(:http_error) { Errno::ENETUNREACH.new }
|
126
|
+
|
127
|
+
context 'every time' do
|
128
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_raise http_error }
|
129
|
+
|
130
|
+
it { expect{request.run}.to fail }
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'but works the second time' do
|
134
|
+
before { expect(Net::HTTP).to receive(:start).at_least(:once).and_return retry_response }
|
135
|
+
before { allow(retry_response).to receive(:body) }
|
136
|
+
let(:retry_response) { Net::HTTPOK.new nil, nil, nil }
|
137
|
+
|
138
|
+
it { expect{request.run}.not_to fail }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
73
142
|
end
|
74
143
|
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.3
|
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-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- lib/yt/collections/playlist_items.rb
|
137
137
|
- lib/yt/collections/playlists.rb
|
138
138
|
- lib/yt/collections/ratings.rb
|
139
|
+
- lib/yt/collections/reports.rb
|
139
140
|
- lib/yt/collections/snippets.rb
|
140
141
|
- lib/yt/collections/statuses.rb
|
141
142
|
- lib/yt/collections/subscriptions.rb
|