yt 0.7.3 → 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/Gemfile.lock +2 -2
  4. data/HISTORY.md +2 -0
  5. data/README.md +9 -7
  6. data/lib/yt/collections/reports.rb +2 -6
  7. data/lib/yt/models/account.rb +3 -2
  8. data/lib/yt/models/base.rb +3 -33
  9. data/lib/yt/models/channel.rb +24 -4
  10. data/lib/yt/modules/associations.rb +47 -0
  11. data/lib/yt/{associations/authentications.rb → modules/authentication.rb} +2 -2
  12. data/lib/yt/modules/reports.rb +65 -0
  13. data/lib/yt/version.rb +1 -1
  14. data/spec/{associations/device_auth → requests/as_account}/account_spec.rb +0 -0
  15. data/spec/{associations/device_auth → requests/as_account}/authentications_spec.rb +2 -2
  16. data/spec/{associations/device_auth → requests/as_account}/channel_spec.rb +0 -1
  17. data/spec/{associations/device_auth → requests/as_account}/playlist_item_spec.rb +0 -0
  18. data/spec/{associations/device_auth → requests/as_account}/playlist_spec.rb +0 -0
  19. data/spec/{associations/device_auth → requests/as_account}/resource_spec.rb +0 -0
  20. data/spec/{associations/device_auth → requests/as_account}/video_spec.rb +0 -0
  21. data/spec/requests/as_content_owner/channel_spec.rb +245 -0
  22. data/spec/{associations/device_auth → requests/as_content_owner}/content_owner_spec.rb +0 -0
  23. data/spec/{associations/server_auth → requests/as_server_app}/channel_spec.rb +0 -0
  24. data/spec/{associations/server_auth → requests/as_server_app}/playlist_item_spec.rb +0 -0
  25. data/spec/{associations/server_auth → requests/as_server_app}/playlist_spec.rb +0 -0
  26. data/spec/{associations/server_auth → requests/as_server_app}/resource_spec.rb +0 -0
  27. data/spec/{associations/server_auth → requests/as_server_app}/video_spec.rb +0 -0
  28. data/spec/{associations/no_auth → requests/unauthenticated}/video_spec.rb +2 -2
  29. metadata +34 -40
  30. data/lib/yt/associations/earnings.rb +0 -44
  31. data/lib/yt/associations/views.rb +0 -44
  32. data/lib/yt/collections/earnings.rb +0 -14
  33. data/lib/yt/collections/views.rb +0 -18
  34. data/spec/associations/device_auth/earnings_spec.rb +0 -67
  35. data/spec/associations/device_auth/views_spec.rb +0 -52
  36. data/spec/collections/earnings_spec.rb +0 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 280089714e1281d849dcb959a4de6e8bed69b76f
4
- data.tar.gz: 8d2482b21d1ba9827c9a8172aab4475c36348c51
3
+ metadata.gz: 3db26d07a5ba0fed297bba2c49bf6d3124620f48
4
+ data.tar.gz: 9fdda77579c877c307fbd36880744ca89a925b29
5
5
  SHA512:
6
- metadata.gz: 14db211427304d5eb03b6d62f50190eb50f614e8e0c9c68d6d7ff26c19ae88378f8feb4502cfaafb2e0ee15e0be0c7aa1d7aa01ff3fc211481b59b4257261a2b
7
- data.tar.gz: 811037e6baf6cfc47c73cb3efd4a64ed5cb1b7c0f1aa0dfffa06ec4ee73106dbb7f26fe92b1b040b3ff5a72026c2076f5d05a3b5d2b4b7143fb22d8e652385a8
6
+ metadata.gz: ee58c92e64c053f44f05634012be1804841db856118917c9a02539a47267bedc1c3e4b2f662988fac7c33fd1688db71fe45f1aabed3773da78e0a8dfe3710d0a
7
+ data.tar.gz: 3fa6e55ee78ec12f9e9a6810f8a7260fd2d62313758324cb984e2f3655a784af60504614796d18f0107e9ad64ffa221df202283fb64537bcf1630e8d549a6746
data/.yardopts CHANGED
@@ -1 +1,3 @@
1
1
  --no-private
2
+ lib/yt/modules/reports.rb
3
+ lib/**/*.rb
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- yt (0.7.3)
4
+ yt (0.7.4)
5
5
  activesupport
6
6
 
7
7
  GEM
@@ -24,7 +24,7 @@ GEM
24
24
  i18n (0.6.9)
25
25
  json (1.8.1)
26
26
  mime-types (2.3)
27
- minitest (5.3.4)
27
+ minitest (5.3.5)
28
28
  multi_json (1.10.1)
29
29
  rake (10.3.2)
30
30
  rest-client (1.6.7)
data/HISTORY.md CHANGED
@@ -10,6 +10,8 @@ v0.7 - 2014/06/18
10
10
  * More status methods for PlaylistItem (privacy_status, public?, private?, unlisted?)
11
11
  * Add video.update to update title, description, tags and categoryId of a video
12
12
  * Sort channel.videos by most recent first
13
+ * Extract Reports (earnings, views) into module with macro `has_report`
14
+ * New channel reports: comments, likes, dislikes, shares and impressions
13
15
 
14
16
  v0.6 - 2014/06/05
15
17
  -----------------
data/README.md CHANGED
@@ -86,7 +86,7 @@ Use [Yt::Channel](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Chan
86
86
  * access the playlists of a channel
87
87
  * subscribe to and unsubscribe from a channel
88
88
  * create and delete playlists from a channel
89
- * retrieve the estimated daily earnings of a channel
89
+ * retrieve the daily earnings, views, comments, likes, dislikes, shares and impressions of a channel
90
90
 
91
91
  ```ruby
92
92
  channel = Yt::Channel.new id: 'UCxO1tY8h1AhOz0T4ENwmpow'
@@ -131,10 +131,12 @@ content_owner = Yt::ContentOwner.new owner_name: 'CMSname', access_token: 'ya29.
131
131
  channel = Yt::Channel.new id: 'UCxO1tY8h1AhOz0T4ENwmpow', auth: content_owner
132
132
 
133
133
  channel.earnings_on 5.days.ago #=> 12.23
134
- channel.earnings until: 2.days.ago #=> {Wed, 28 May 2014 => 1.34, Thu, 29 May 2014 => 0.47}
135
-
136
- channel.views_on 5.days.ago #=> 44
137
- channel.views since: 3.days.ago #=> {Wed, 28 May 2014 => 12, Thu, 29 May 2014 => 3}
134
+ channel.views since: 7.days.ago #=> {Wed, 28 May 2014 => 12.0, Thu, 29 May 2014 => 3.0, …}
135
+ channel.comments until: 2.days.ago #=> {Wed, 28 May 2014 => 9.0, Thu, 29 May 2014 => 4.0, …}
136
+ channel.ch.likes from: 8.days.ago #=> {Tue, 27 May 2014 => 7.0, Wed, 28 May 2014 => 0.0, …}
137
+ channel.ch.dislikes to: 2.days.ago #=> {Tue, 27 May 2014 => 0.0, Wed, 28 May 2014 => 1.0, …}
138
+ channel.shares since: 7.days.ago, until: 7.days.ago #=> {Wed, 28 May 2014 => 3.0}
139
+ channel.impressions_on 5.days.ago #=> 157.0
138
140
  ```
139
141
 
140
142
  *The methods above require to be authenticated as the channel’s content owner (see below).*
@@ -431,7 +433,7 @@ To install on your system, run
431
433
 
432
434
  To use inside a bundled Ruby project, add this line to the Gemfile:
433
435
 
434
- gem 'yt', '~> 0.7.3'
436
+ gem 'yt', '~> 0.7.4'
435
437
 
436
438
  Since the gem follows [Semantic Versioning](http://semver.org),
437
439
  indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
@@ -459,7 +461,7 @@ How to test
459
461
  Yt comes with two different sets of tests:
460
462
 
461
463
  1. tests in `spec/models`, `spec/collections` and `spec/errors` **do not hit** the YouTube API
462
- 1. tests in `spec/associations` **hit** the YouTube API and require authentication
464
+ 1. tests in `spec/requests` **hit** the YouTube API and require authentication
463
465
 
464
466
  The reason why some tests actually hit the YouTube API is because they are
465
467
  meant to really integrate Yt with YouTube. YouTube API is not exactly
@@ -3,6 +3,7 @@ require 'yt/collections/base'
3
3
  module Yt
4
4
  module Collections
5
5
  class Reports < Base
6
+ attr_writer :metric
6
7
 
7
8
  def within(days_range)
8
9
  @days_range = days_range
@@ -24,7 +25,7 @@ module Yt
24
25
  params['filters'] = "channel==#{@parent.id}"
25
26
  params['start-date'] = @days_range.begin
26
27
  params['end-date'] = @days_range.end
27
- params['metrics'] = metrics
28
+ params['metrics'] = @metric
28
29
  params['dimensions'] = :day
29
30
  end
30
31
  end
@@ -40,11 +41,6 @@ module Yt
40
41
  data.last
41
42
  end
42
43
 
43
- # To be overriden by superclasses
44
- # def metrics
45
- # ''
46
- # end
47
-
48
44
  def items_key
49
45
  'rows'
50
46
  end
@@ -1,12 +1,13 @@
1
1
  require 'yt/models/base'
2
- require 'yt/associations/authentications'
2
+ require 'yt/modules/authentication'
3
3
 
4
4
  module Yt
5
5
  module Models
6
6
  # Provides methods to interact with YouTube accounts.
7
7
  # @see https://developers.google.com/youtube/v3/guides/authentication
8
8
  class Account < Base
9
- include Associations::Authentications
9
+ # Includes methods to authenticate with YouTube API.
10
+ include Modules::Authentication
10
11
 
11
12
  # @!attribute [r] channel
12
13
  # @return [Yt::Models::Channel] the account’s channel.
@@ -1,45 +1,15 @@
1
1
  require 'yt/actions/delete'
2
2
  require 'yt/actions/update'
3
+ require 'yt/modules/associations'
3
4
  require 'yt/errors/request_error'
4
5
 
5
- require 'active_support' # does not load anything by default but is required
6
- require 'active_support/core_ext/module/delegation' # for delegate
7
- require 'active_support/core_ext/string/inflections' # for camelize/constantize
8
-
9
6
  module Yt
10
7
  module Models
11
8
  class Base
9
+ extend Modules::Associations
10
+
12
11
  include Actions::Delete
13
12
  include Actions::Update
14
-
15
- # @private
16
- def self.has_many(attributes)
17
- require "yt/collections/#{attributes}"
18
- collection_name = attributes.to_s.sub(/.*\./, '').camelize.pluralize
19
- collection = "Yt::Collections::#{collection_name}".constantize
20
- define_memoized_method(attributes) { collection.of self }
21
- end
22
-
23
- # @private
24
- def self.has_one(attribute)
25
- attributes = attribute.to_s.pluralize
26
- has_many attributes
27
- define_memoized_method(attribute) { send(attributes).first! }
28
- end
29
-
30
- private
31
-
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)
41
- end
42
- end
43
13
  end
44
14
  end
45
15
 
@@ -1,14 +1,13 @@
1
1
  require 'yt/models/resource'
2
- require 'yt/associations/earnings'
3
- require 'yt/associations/views'
2
+ require 'yt/modules/reports'
4
3
 
5
4
  module Yt
6
5
  module Models
7
6
  # A channel resource contains information about a YouTube channel.
8
7
  # @see https://developers.google.com/youtube/v3/docs/channels
9
8
  class Channel < Resource
10
- include Associations::Earnings
11
- include Associations::Views
9
+ # Includes the +:has_report+ method to access YouTube Analytics reports.
10
+ extend Modules::Reports
12
11
 
13
12
  # @!attribute [r] subscriptions
14
13
  # @return [Yt::Collections::Subscriptions] the channel’s subscriptions.
@@ -22,6 +21,27 @@ module Yt
22
21
  # @return [Yt::Collections::Playlists] the channel’s playlists.
23
22
  has_many :playlists
24
23
 
24
+ # @macro has_report
25
+ has_report :earnings
26
+
27
+ # @macro has_report
28
+ has_report :views
29
+
30
+ # @macro has_report
31
+ has_report :comments
32
+
33
+ # @macro has_report
34
+ has_report :likes
35
+
36
+ # @macro has_report
37
+ has_report :dislikes
38
+
39
+ # @macro has_report
40
+ has_report :shares
41
+
42
+ # @macro has_report
43
+ has_report :impressions
44
+
25
45
  # @!attribute [r] statistics_set
26
46
  # @return [Yt::Models::StatisticsSet] the statistics for the video.
27
47
  has_one :statistics_set
@@ -0,0 +1,47 @@
1
+ require 'active_support' # does not load anything by default but is required
2
+ require 'active_support/core_ext/module/delegation' # for delegate
3
+ require 'active_support/core_ext/string/inflections' # for camelize/constantize
4
+
5
+ module Yt
6
+ module Modules
7
+ # Associations are a set of macro-like class methods to express
8
+ # relationship between YouTube resources like "Channel has many Videos" or
9
+ # "Account has one Id". They are inspired by ActiveRecord::Associations.
10
+ module Associations
11
+ # @example Adds the +videos+ method to the Channel resource.
12
+ # class Channel < Resource
13
+ # has_many :videos
14
+ # end
15
+ def has_many(attributes)
16
+ require "yt/collections/#{attributes}"
17
+ collection_name = attributes.to_s.sub(/.*\./, '').camelize.pluralize
18
+ collection = "Yt::Collections::#{collection_name}".constantize
19
+ define_memoized_method(attributes) { collection.of self }
20
+ end
21
+
22
+ # @example Adds the +status+ method to the Video resource.
23
+ # class Video < Resource
24
+ # has_one :status
25
+ # end
26
+ def has_one(attribute)
27
+ attributes = attribute.to_s.pluralize
28
+ has_many attributes
29
+ define_memoized_method(attribute) { send(attributes).first! }
30
+ end
31
+
32
+ private
33
+
34
+ # A wrapper around Ruby’s +define_method+ that, in addition to adding an
35
+ # instance method called +name+, adds an instance variable called +@name+
36
+ # that stores the result of +name+ the first time is invoked, and returns
37
+ # it every other time. Especially useful if invoking +name+ takes a long
38
+ # time.
39
+ def define_memoized_method(name, &method)
40
+ define_method name do
41
+ ivar = instance_variable_get "@#{name}"
42
+ instance_variable_set "@#{name}", ivar || instance_eval(&method)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -4,12 +4,12 @@ require 'yt/errors/no_items'
4
4
  require 'yt/errors/unauthorized'
5
5
 
6
6
  module Yt
7
- module Associations
7
+ module Modules
8
8
  # Provides authentication methods to YouTube resources, which
9
9
  # allows to access to content detail set-specific methods like `access_token`.
10
10
  #
11
11
  # YouTube resources with authentication are: {Yt::Models::Account accounts}.
12
- module Authentications
12
+ module Authentication
13
13
  delegate :access_token, :refresh_token, :expires_at, to: :authentication
14
14
 
15
15
  def initialize(options = {})
@@ -0,0 +1,65 @@
1
+ require 'yt/collections/reports'
2
+
3
+ module Yt
4
+ module Modules
5
+ # Provides methods to to access the analytics reports of a resource.
6
+ #
7
+ # YouTube resources with reports are: {Yt::Models::Channel channels}.
8
+ module Reports
9
+ # @!macro has_report
10
+ # @!method $1_on(date)
11
+ # @return [Float] the $1 for a single day.
12
+ # @param [#to_date] date The single day to get the $1 for.
13
+ # @!method $1(options = {})
14
+ # @return [Hash<Date, Float>] the $1 for a range of a days.
15
+ # @param [Hash] options the range of days to get the $1 for.
16
+ # @option options [#to_date] :since The first day of the range.
17
+ # Also aliased as *:from*.
18
+ # @option options [#to_date] :until The last day of the range.
19
+ # Also aliased as *:to*.
20
+
21
+ # Defines two public instance methods to access the reports of a
22
+ # resource for a specific metric.
23
+ # @param [Symbol] metric the metric to access the reports of.
24
+ # @example Adds +earnings+ and +earnings_on+ on a Channel resource.
25
+ # class Channel < Resource
26
+ # has_report :earnings
27
+ # end
28
+ def has_report(metric)
29
+ define_method "#{metric}_on" do |date|
30
+ send(metric, from: date, to: date).values.first
31
+ end
32
+
33
+ define_method metric do |options = {}|
34
+ from = options[:since] || options[:from] || 5.days.ago
35
+ to = options[:until] || options[:to] || 1.day.ago
36
+ range = Range.new *[from, to].map(&:to_date)
37
+
38
+ ivar = instance_variable_get "@#{metric}"
39
+ instance_variable_set "@#{metric}", ivar || {}
40
+
41
+ Hash[*range.flat_map do |date|
42
+ [date, instance_variable_get("@#{metric}")[date] ||= send("range_#{metric}", range)[date]]
43
+ end]
44
+ end
45
+
46
+ define_method "range_#{metric}" do |date_range|
47
+ ivar = instance_variable_get "@range_#{metric}"
48
+ instance_variable_set "@range_#{metric}", ivar || {}
49
+ instance_variable_get("@range_#{metric}")[date_range] ||= send("all_#{metric}").within date_range
50
+ end
51
+ private "range_#{metric}"
52
+
53
+ define_method "all_#{metric}" do
54
+ # @note Asking for the "earnings" metric of a day in which a channel
55
+ # made 0 USD returns the wrong "nil". But adding to the request the
56
+ # "estimatedMinutesWatched" metric returns the correct value 0.
57
+ query = metric
58
+ query = "estimatedMinutesWatched,#{metric}" if metric == :earnings
59
+ Collections::Reports.of(self).tap{|reports| reports.metric = query}
60
+ end
61
+ private "all_#{metric}"
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.7.3'
2
+ VERSION = '0.7.4'
3
3
  end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
- require 'yt/associations/authentications'
2
+ require 'yt/models/account'
3
3
 
4
- describe Yt::Associations::Authentications, :device_app do
4
+ describe Yt::Account, :device_app do
5
5
  subject(:account) { Yt::Account.new attrs }
6
6
 
7
7
  describe '#refresh' do
@@ -1,5 +1,4 @@
1
1
  # encoding: UTF-8
2
-
3
2
  require 'spec_helper'
4
3
  require 'yt/models/channel'
5
4
 
@@ -0,0 +1,245 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'yt/models/channel'
4
+
5
+ describe Yt::Channel, :partner do
6
+ subject(:channel) { Yt::Channel.new id: id, auth: $content_owner }
7
+
8
+ context 'given a partnered channel', :partner do
9
+ context 'managed by the authenticated Content Owner' do
10
+ let(:id) { ENV['YT_TEST_PARTNER_CHANNEL_ID'] }
11
+
12
+ describe 'earnings can be retrieved for a specific day' do
13
+ context 'in which the channel made any money' do
14
+ let(:earnings) { channel.earnings_on 5.days.ago}
15
+ it { expect(earnings).to be_a Float }
16
+ end
17
+
18
+ # NOTE: This test sounds redundant, but it’s actually a reflection of
19
+ # another irrational behavior of YouTube API. In short, if you ask for
20
+ # the "earnings" metric of a day in which a channel made 0 USD, then
21
+ # the API returns "nil". But if you also for the "earnings" metric AND
22
+ # the "estimatedMinutesWatched" metric, then the API returns the
23
+ # correct value of "0", while still returning nil for those days in
24
+ # which the earnings have not been estimated yet.
25
+ context 'in which the channel did not make any money' do
26
+ let(:zero_date) { ENV['YT_TEST_PARTNER_CHANNEL_NO_EARNINGS_DATE'] }
27
+ let(:earnings) { channel.earnings_on zero_date}
28
+ it { expect(earnings).to eq 0 }
29
+ end
30
+
31
+ context 'in the future' do
32
+ let(:earnings) { channel.earnings_on 5.days.from_now}
33
+ it { expect(earnings).to be_nil }
34
+ end
35
+ end
36
+
37
+ describe 'earnings can be retrieved for a range of days' do
38
+ let(:date) { 4.days.ago }
39
+
40
+ specify 'with a given start (:since option)' do
41
+ expect(channel.earnings(since: date).keys.min).to eq date.to_date
42
+ end
43
+
44
+ specify 'with a given end (:until option)' do
45
+ expect(channel.earnings(until: date).keys.max).to eq date.to_date
46
+ end
47
+
48
+ specify 'with a given start (:from option)' do
49
+ expect(channel.earnings(from: date).keys.min).to eq date.to_date
50
+ end
51
+
52
+ specify 'with a given end (:to option)' do
53
+ expect(channel.earnings(to: date).keys.max).to eq date.to_date
54
+ end
55
+ end
56
+
57
+ describe 'views can be retrieved for a specific day' do
58
+ context 'in which the channel was partnered' do
59
+ let(:views) { channel.views_on 5.days.ago}
60
+ it { expect(views).to be_a Float }
61
+ end
62
+
63
+ context 'in which the channel was not partnered' do
64
+ let(:views) { channel.views_on 20.years.ago}
65
+ it { expect(views).to be_nil }
66
+ end
67
+ end
68
+
69
+ describe 'views can be retrieved for a range of days' do
70
+ let(:date) { 4.days.ago }
71
+
72
+ specify 'with a given start (:since option)' do
73
+ expect(channel.views(since: date).keys.min).to eq date.to_date
74
+ end
75
+
76
+ specify 'with a given end (:until option)' do
77
+ expect(channel.views(until: date).keys.max).to eq date.to_date
78
+ end
79
+
80
+ specify 'with a given start (:from option)' do
81
+ expect(channel.views(from: date).keys.min).to eq date.to_date
82
+ end
83
+
84
+ specify 'with a given end (:to option)' do
85
+ expect(channel.views(to: date).keys.max).to eq date.to_date
86
+ end
87
+ end
88
+
89
+ describe 'comments can be retrieved for a specific day' do
90
+ context 'in which the channel was partnered' do
91
+ let(:comments) { channel.comments_on 5.days.ago}
92
+ it { expect(comments).to be_a Float }
93
+ end
94
+
95
+ context 'in which the channel was not partnered' do
96
+ let(:comments) { channel.comments_on 20.years.ago}
97
+ it { expect(comments).to be_nil }
98
+ end
99
+ end
100
+
101
+ describe 'comments can be retrieved for a range of days' do
102
+ let(:date) { 4.days.ago }
103
+
104
+ specify 'with a given start (:since option)' do
105
+ expect(channel.comments(since: date).keys.min).to eq date.to_date
106
+ end
107
+
108
+ specify 'with a given end (:until option)' do
109
+ expect(channel.comments(until: date).keys.max).to eq date.to_date
110
+ end
111
+
112
+ specify 'with a given start (:from option)' do
113
+ expect(channel.comments(from: date).keys.min).to eq date.to_date
114
+ end
115
+
116
+ specify 'with a given end (:to option)' do
117
+ expect(channel.comments(to: date).keys.max).to eq date.to_date
118
+ end
119
+ end
120
+
121
+ describe 'likes can be retrieved for a specific day' do
122
+ context 'in which the channel was partnered' do
123
+ let(:likes) { channel.likes_on 5.days.ago}
124
+ it { expect(likes).to be_a Float }
125
+ end
126
+
127
+ context 'in which the channel was not partnered' do
128
+ let(:likes) { channel.likes_on 20.years.ago}
129
+ it { expect(likes).to be_nil }
130
+ end
131
+ end
132
+
133
+ describe 'likes can be retrieved for a range of days' do
134
+ let(:date) { 4.days.ago }
135
+
136
+ specify 'with a given start (:since option)' do
137
+ expect(channel.likes(since: date).keys.min).to eq date.to_date
138
+ end
139
+
140
+ specify 'with a given end (:until option)' do
141
+ expect(channel.likes(until: date).keys.max).to eq date.to_date
142
+ end
143
+
144
+ specify 'with a given start (:from option)' do
145
+ expect(channel.likes(from: date).keys.min).to eq date.to_date
146
+ end
147
+
148
+ specify 'with a given end (:to option)' do
149
+ expect(channel.likes(to: date).keys.max).to eq date.to_date
150
+ end
151
+ end
152
+
153
+ describe 'dislikes can be retrieved for a specific day' do
154
+ context 'in which the channel was partnered' do
155
+ let(:dislikes) { channel.dislikes_on 5.days.ago}
156
+ it { expect(dislikes).to be_a Float }
157
+ end
158
+
159
+ context 'in which the channel was not partnered' do
160
+ let(:dislikes) { channel.dislikes_on 20.years.ago}
161
+ it { expect(dislikes).to be_nil }
162
+ end
163
+ end
164
+
165
+ describe 'dislikes can be retrieved for a range of days' do
166
+ let(:date) { 4.days.ago }
167
+
168
+ specify 'with a given start (:since option)' do
169
+ expect(channel.dislikes(since: date).keys.min).to eq date.to_date
170
+ end
171
+
172
+ specify 'with a given end (:until option)' do
173
+ expect(channel.dislikes(until: date).keys.max).to eq date.to_date
174
+ end
175
+
176
+ specify 'with a given start (:from option)' do
177
+ expect(channel.dislikes(from: date).keys.min).to eq date.to_date
178
+ end
179
+
180
+ specify 'with a given end (:to option)' do
181
+ expect(channel.dislikes(to: date).keys.max).to eq date.to_date
182
+ end
183
+ end
184
+
185
+ describe 'shares can be retrieved for a range of days' do
186
+ let(:date) { 4.days.ago }
187
+
188
+ specify 'with a given start (:since option)' do
189
+ expect(channel.shares(since: date).keys.min).to eq date.to_date
190
+ end
191
+
192
+ specify 'with a given end (:until option)' do
193
+ expect(channel.shares(until: date).keys.max).to eq date.to_date
194
+ end
195
+
196
+ specify 'with a given start (:from option)' do
197
+ expect(channel.shares(from: date).keys.min).to eq date.to_date
198
+ end
199
+
200
+ specify 'with a given end (:to option)' do
201
+ expect(channel.shares(to: date).keys.max).to eq date.to_date
202
+ end
203
+ end
204
+
205
+ describe 'impressions can be retrieved for a specific day' do
206
+ context 'in which the channel was partnered' do
207
+ let(:impressions) { channel.impressions_on 5.days.ago}
208
+ it { expect(impressions).to be_a Float }
209
+ end
210
+
211
+ context 'in which the channel was not partnered' do
212
+ let(:impressions) { channel.impressions_on 20.years.ago}
213
+ it { expect(impressions).to be_nil }
214
+ end
215
+ end
216
+
217
+ describe 'impressions can be retrieved for a range of days' do
218
+ let(:date) { 4.days.ago }
219
+
220
+ specify 'with a given start (:since option)' do
221
+ expect(channel.impressions(since: date).keys.min).to eq date.to_date
222
+ end
223
+
224
+ specify 'with a given end (:until option)' do
225
+ expect(channel.impressions(until: date).keys.max).to eq date.to_date
226
+ end
227
+
228
+ specify 'with a given start (:from option)' do
229
+ expect(channel.impressions(from: date).keys.min).to eq date.to_date
230
+ end
231
+
232
+ specify 'with a given end (:to option)' do
233
+ expect(channel.impressions(to: date).keys.max).to eq date.to_date
234
+ end
235
+ end
236
+ end
237
+
238
+ context 'not managed by the authenticated Content Owner' do
239
+ let(:id) { 'UCBR8-60-B28hp2BmDPdntcQ' }
240
+
241
+ it { expect{channel.earnings}.to raise_error Yt::Errors::Forbidden }
242
+ it { expect{channel.views}.to raise_error Yt::Errors::Forbidden }
243
+ end
244
+ end
245
+ end
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
  require 'yt/models/video'
3
3
 
4
- describe Yt::Video, :device_app do
5
- subject(:video) { Yt::Video.new id: id, auth: $account }
4
+ describe Yt::Video do
5
+ subject(:video) { Yt::Video.new id: id }
6
6
 
7
7
  context 'given a public video with annotations' do
8
8
  let(:id) { 'MESycYJytkU' }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
@@ -122,15 +122,11 @@ files:
122
122
  - lib/yt/actions/insert.rb
123
123
  - lib/yt/actions/list.rb
124
124
  - lib/yt/actions/update.rb
125
- - lib/yt/associations/authentications.rb
126
- - lib/yt/associations/earnings.rb
127
- - lib/yt/associations/views.rb
128
125
  - lib/yt/collections/annotations.rb
129
126
  - lib/yt/collections/authentications.rb
130
127
  - lib/yt/collections/base.rb
131
128
  - lib/yt/collections/channels.rb
132
129
  - lib/yt/collections/content_details.rb
133
- - lib/yt/collections/earnings.rb
134
130
  - lib/yt/collections/ids.rb
135
131
  - lib/yt/collections/partnered_channels.rb
136
132
  - lib/yt/collections/playlist_items.rb
@@ -143,7 +139,6 @@ files:
143
139
  - lib/yt/collections/subscriptions.rb
144
140
  - lib/yt/collections/user_infos.rb
145
141
  - lib/yt/collections/videos.rb
146
- - lib/yt/collections/views.rb
147
142
  - lib/yt/config.rb
148
143
  - lib/yt/errors/forbidden.rb
149
144
  - lib/yt/errors/no_items.rb
@@ -172,27 +167,13 @@ files:
172
167
  - lib/yt/models/url.rb
173
168
  - lib/yt/models/user_info.rb
174
169
  - lib/yt/models/video.rb
170
+ - lib/yt/modules/associations.rb
171
+ - lib/yt/modules/authentication.rb
172
+ - lib/yt/modules/reports.rb
175
173
  - lib/yt/version.rb
176
- - spec/associations/device_auth/account_spec.rb
177
- - spec/associations/device_auth/authentications_spec.rb
178
- - spec/associations/device_auth/channel_spec.rb
179
- - spec/associations/device_auth/content_owner_spec.rb
180
- - spec/associations/device_auth/earnings_spec.rb
181
- - spec/associations/device_auth/playlist_item_spec.rb
182
- - spec/associations/device_auth/playlist_spec.rb
183
- - spec/associations/device_auth/resource_spec.rb
184
- - spec/associations/device_auth/video_spec.rb
185
- - spec/associations/device_auth/views_spec.rb
186
- - spec/associations/no_auth/video_spec.rb
187
- - spec/associations/server_auth/channel_spec.rb
188
- - spec/associations/server_auth/playlist_item_spec.rb
189
- - spec/associations/server_auth/playlist_spec.rb
190
- - spec/associations/server_auth/resource_spec.rb
191
- - spec/associations/server_auth/video_spec.rb
192
174
  - spec/collections/annotations_spec.rb
193
175
  - spec/collections/channels_spec.rb
194
176
  - spec/collections/content_details_spec.rb
195
- - spec/collections/earnings_spec.rb
196
177
  - spec/collections/playlist_items_spec.rb
197
178
  - spec/collections/playlists_spec.rb
198
179
  - spec/collections/ratings_spec.rb
@@ -221,6 +202,21 @@ files:
221
202
  - spec/models/url_spec.rb
222
203
  - spec/models/user_info_spec.rb
223
204
  - spec/models/video_spec.rb
205
+ - spec/requests/as_account/account_spec.rb
206
+ - spec/requests/as_account/authentications_spec.rb
207
+ - spec/requests/as_account/channel_spec.rb
208
+ - spec/requests/as_account/playlist_item_spec.rb
209
+ - spec/requests/as_account/playlist_spec.rb
210
+ - spec/requests/as_account/resource_spec.rb
211
+ - spec/requests/as_account/video_spec.rb
212
+ - spec/requests/as_content_owner/channel_spec.rb
213
+ - spec/requests/as_content_owner/content_owner_spec.rb
214
+ - spec/requests/as_server_app/channel_spec.rb
215
+ - spec/requests/as_server_app/playlist_item_spec.rb
216
+ - spec/requests/as_server_app/playlist_spec.rb
217
+ - spec/requests/as_server_app/resource_spec.rb
218
+ - spec/requests/as_server_app/video_spec.rb
219
+ - spec/requests/unauthenticated/video_spec.rb
224
220
  - spec/spec_helper.rb
225
221
  - spec/support/fail_matcher.rb
226
222
  - spec/support/global_hooks.rb
@@ -251,26 +247,9 @@ specification_version: 4
251
247
  summary: Yt makes it easy to interact with Youtube V3 API by providing a modular,
252
248
  intuitive and tested Ruby-style API.
253
249
  test_files:
254
- - spec/associations/device_auth/account_spec.rb
255
- - spec/associations/device_auth/authentications_spec.rb
256
- - spec/associations/device_auth/channel_spec.rb
257
- - spec/associations/device_auth/content_owner_spec.rb
258
- - spec/associations/device_auth/earnings_spec.rb
259
- - spec/associations/device_auth/playlist_item_spec.rb
260
- - spec/associations/device_auth/playlist_spec.rb
261
- - spec/associations/device_auth/resource_spec.rb
262
- - spec/associations/device_auth/video_spec.rb
263
- - spec/associations/device_auth/views_spec.rb
264
- - spec/associations/no_auth/video_spec.rb
265
- - spec/associations/server_auth/channel_spec.rb
266
- - spec/associations/server_auth/playlist_item_spec.rb
267
- - spec/associations/server_auth/playlist_spec.rb
268
- - spec/associations/server_auth/resource_spec.rb
269
- - spec/associations/server_auth/video_spec.rb
270
250
  - spec/collections/annotations_spec.rb
271
251
  - spec/collections/channels_spec.rb
272
252
  - spec/collections/content_details_spec.rb
273
- - spec/collections/earnings_spec.rb
274
253
  - spec/collections/playlist_items_spec.rb
275
254
  - spec/collections/playlists_spec.rb
276
255
  - spec/collections/ratings_spec.rb
@@ -299,6 +278,21 @@ test_files:
299
278
  - spec/models/url_spec.rb
300
279
  - spec/models/user_info_spec.rb
301
280
  - spec/models/video_spec.rb
281
+ - spec/requests/as_account/account_spec.rb
282
+ - spec/requests/as_account/authentications_spec.rb
283
+ - spec/requests/as_account/channel_spec.rb
284
+ - spec/requests/as_account/playlist_item_spec.rb
285
+ - spec/requests/as_account/playlist_spec.rb
286
+ - spec/requests/as_account/resource_spec.rb
287
+ - spec/requests/as_account/video_spec.rb
288
+ - spec/requests/as_content_owner/channel_spec.rb
289
+ - spec/requests/as_content_owner/content_owner_spec.rb
290
+ - spec/requests/as_server_app/channel_spec.rb
291
+ - spec/requests/as_server_app/playlist_item_spec.rb
292
+ - spec/requests/as_server_app/playlist_spec.rb
293
+ - spec/requests/as_server_app/resource_spec.rb
294
+ - spec/requests/as_server_app/video_spec.rb
295
+ - spec/requests/unauthenticated/video_spec.rb
302
296
  - spec/spec_helper.rb
303
297
  - spec/support/fail_matcher.rb
304
298
  - spec/support/global_hooks.rb
@@ -1,44 +0,0 @@
1
- require 'yt/collections/earnings'
2
-
3
- module Yt
4
- module Associations
5
- # Provides methods which allows to access the earnings generated by a resource.
6
- #
7
- # YouTube resources with earning are: {Yt::Models::Channel channels}.
8
- module Earnings
9
- # @return [Float] the estimated earnings for one specific day in USD.
10
- # @param [Date or Time or DateTime or String] date The date to obtain
11
- # the estimated earnings for. If +String+, must be Date-parseable.
12
- def earnings_on(date)
13
- earnings(from: date, to: date).values.first
14
- end
15
-
16
- # @return [Hash] the estimated earnings for a range of days. Each +key+ is a +Date+
17
- # and each +value+ is a +Float+, representing estimated earnings in USD.
18
- # @param [Hash] options the range of days to get the earnings for.
19
- # @option options [Date or Time or DateTime or String] :since The start
20
- # of the days range. If +String+, must be Date-parseable. Also aliased as *:from*.
21
- # @option options [Date or Time or DateTime or String] :until The end
22
- # of the days range. If +String+, must be Date-parseable. Also aliased as *:to*.
23
- def earnings(options = {})
24
- from = options[:since] || options[:from] || 6.days.ago
25
- to = options[:until] || options[:to] || 2.days.ago
26
- range = Range.new *[from, to].map(&:to_date)
27
-
28
- Hash[*range.flat_map do |date|
29
- [date, (@earnings ||= {})[date] ||= range_earnings(range)[date]]
30
- end]
31
- end
32
-
33
- private
34
-
35
- def range_earnings(date_range)
36
- (@range_earnings ||= {})[date_range] ||= all_earnings.within date_range
37
- end
38
-
39
- def all_earnings
40
- Collections::Earnings.of self
41
- end
42
- end
43
- end
44
- end
@@ -1,44 +0,0 @@
1
- require 'yt/collections/views'
2
-
3
- module Yt
4
- module Associations
5
- # Provides methods which allows to access the view count of a resource.
6
- #
7
- # YouTube resources with view counts are: {Yt::Models::Channel channels}.
8
- module Views
9
- # @return [Integer, nil] the view count for one specific day.
10
- # @param [Date or Time or DateTime or String] date The date to obtain
11
- # the views for. If +String+, must be Date-parseable.
12
- def views_on(date)
13
- views(from: date, to: date).values.first
14
- end
15
-
16
- # @return [Hash] the view counts for a range of days. Each +key+ is a +Date+
17
- # and each +value+ is an +Integer+ or +nil+, representing the number of views.
18
- # @param [Hash] options the range of days to get the view for.
19
- # @option options [Date or Time or DateTime or String] :since The start
20
- # of the days range. If +String+, must be Date-parseable. Also aliased as *:from*.
21
- # @option options [Date or Time or DateTime or String] :until The end
22
- # of the days range. If +String+, must be Date-parseable. Also aliased as *:to*.
23
- def views(options = {})
24
- from = options[:since] || options[:from] || 5.days.ago
25
- to = options[:until] || options[:to] || 1.day.ago
26
- range = Range.new *[from, to].map(&:to_date)
27
-
28
- Hash[*range.flat_map do |date|
29
- [date, (@views ||= {})[date] ||= range_views(range)[date]]
30
- end]
31
- end
32
-
33
- private
34
-
35
- def range_views(date_range)
36
- (@range_views ||= {})[date_range] ||= all_views.within date_range
37
- end
38
-
39
- def all_views
40
- Collections::Views.of self
41
- end
42
- end
43
- end
44
- end
@@ -1,14 +0,0 @@
1
- require 'yt/collections/reports'
2
-
3
- module Yt
4
- module Collections
5
- class Earnings < Reports
6
-
7
- private
8
-
9
- def metrics
10
- [:estimatedMinutesWatched, :earnings].join ','
11
- end
12
- end
13
- end
14
- end
@@ -1,18 +0,0 @@
1
- require 'yt/collections/reports'
2
-
3
- module Yt
4
- module Collections
5
- class Views < Reports
6
-
7
- private
8
-
9
- def metrics
10
- :views
11
- end
12
-
13
- def value_of(data)
14
- data.last.to_i if data.last
15
- end
16
- end
17
- end
18
- end
@@ -1,67 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'spec_helper'
4
-
5
- describe Yt::Associations::Earnings, :partner do
6
- context 'given a partnered channel' do
7
- let(:channel) { Yt::Channel.new id: channel_id, auth: $content_owner }
8
-
9
- context 'managed by the authenticated Content Owner' do
10
- let(:channel_id) { ENV['YT_TEST_PARTNER_CHANNEL_ID'] }
11
-
12
- describe '#earnings_on' do
13
- context 'and a date in which the channel made any money' do
14
- let(:earnings) { channel.earnings_on 5.days.ago}
15
- it { expect(earnings).to be_a Float }
16
- end
17
-
18
- # NOTE: This test sounds redundant, but it’s actually a reflection of
19
- # another irrational behavior of YouTube API. In short, if you ask for
20
- # the "earnings" metric of a day in which a channel made 0 USD, then
21
- # the API returns "nil". But if you also for the "earnings" metric AND
22
- # the "estimatedMinutesWatched" metric, then the API returns the
23
- # correct value of "0", while still returning nil for those days in
24
- # which the earnings have not been estimated yet.
25
- context 'and a date in which the channel did not make any money' do
26
- let(:zero_date) { ENV['YT_TEST_PARTNER_CHANNEL_NO_EARNINGS_DATE'] }
27
- let(:earnings) { channel.earnings_on zero_date}
28
- it { expect(earnings).to eq 0 }
29
- end
30
-
31
- context 'and a date for which earnings have not yet been estimated' do
32
- let(:earnings) { channel.earnings_on 5.days.from_now}
33
- it { expect(earnings).to be_nil }
34
- end
35
- end
36
-
37
- describe '#earnings' do
38
- let(:date) { 4.days.ago }
39
-
40
- context 'given a :since option' do
41
- let(:earnings) { channel.earnings since: date}
42
- it { expect(earnings.keys.min).to eq date.to_date }
43
- end
44
-
45
- context 'given a :from option' do
46
- let(:earnings) { channel.earnings from: date}
47
- it { expect(earnings.keys.min).to eq date.to_date }
48
- end
49
-
50
- context 'given a :until option' do
51
- let(:earnings) { channel.earnings until: date}
52
- it { expect(earnings.keys.max).to eq date.to_date }
53
- end
54
-
55
- context 'given a :to option' do
56
- let(:earnings) { channel.earnings to: date}
57
- it { expect(earnings.keys.max).to eq date.to_date }
58
- end
59
- end
60
- end
61
-
62
- context 'not managed by the authenticated Content Owner' do
63
- let(:channel_id) { 'UCBR8-60-B28hp2BmDPdntcQ' }
64
- it { expect{channel.earnings}.to raise_error Yt::Errors::Forbidden }
65
- end
66
- end
67
- end
@@ -1,52 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Yt::Associations::Views, :partner do
4
- context 'given a partnered channel' do
5
- let(:channel) { Yt::Channel.new id: channel_id, auth: $content_owner }
6
-
7
- context 'managed by the authenticated Content Owner' do
8
- let(:channel_id) { ENV['YT_TEST_PARTNER_CHANNEL_ID'] }
9
-
10
- describe '#views_on' do
11
- context 'and a date in which the channel was partnered' do
12
- let(:views) { channel.views_on 5.days.ago}
13
- it { expect(views).to be_an Integer }
14
- end
15
-
16
- context 'and a date in which the channel was not partnered' do
17
- let(:views) { channel.views_on 20.years.ago}
18
- it { expect(views).to be_nil }
19
- end
20
- end
21
-
22
- describe '#views' do
23
- let(:date) { 4.days.ago }
24
-
25
- context 'given a :since option' do
26
- let(:views) { channel.views since: date}
27
- it { expect(views.keys.min).to eq date.to_date }
28
- end
29
-
30
- context 'given a :from option' do
31
- let(:views) { channel.views from: date}
32
- it { expect(views.keys.min).to eq date.to_date }
33
- end
34
-
35
- context 'given a :until option' do
36
- let(:views) { channel.views until: date}
37
- it { expect(views.keys.max).to eq date.to_date }
38
- end
39
-
40
- context 'given a :to option' do
41
- let(:views) { channel.views to: date}
42
- it { expect(views.keys.max).to eq date.to_date }
43
- end
44
- end
45
- end
46
-
47
- context 'not managed by the authenticated Content Owner' do
48
- let(:channel_id) { 'UCBR8-60-B28hp2BmDPdntcQ' }
49
- it { expect{channel.views}.to raise_error Yt::Errors::Forbidden }
50
- end
51
- end
52
- end
@@ -1,18 +0,0 @@
1
- require 'spec_helper'
2
- require 'yt/collections/earnings'
3
-
4
- describe Yt::Collections::Earnings do
5
- subject(:collection) { Yt::Collections::Earnings.new parent: channel }
6
- let(:channel) { Yt::Channel.new id: 'UCxO1tY8h1AhOz0T4ENwmpow' }
7
- let(:date) { 1.day.ago.to_date }
8
- let(:dollars) { 10 }
9
- before { expect(collection).to behave }
10
-
11
- describe '#within' do
12
- context 'given YouTube responds with a list of earnings' do
13
- let(:behave) { receive(:flat_map).and_return [date, dollars] }
14
-
15
- it { expect(collection.within(date..date)[date]).to eq dollars }
16
- end
17
- end
18
- end