yt 0.14.7 → 0.15.0

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: e26f7e87270dc0509123f5ea88302a280e54da66
4
- data.tar.gz: 73f82aca406690352fb266e2129bc4288be40f4a
3
+ metadata.gz: 54209e7c3dedc665ad4d6b0d93bcf6c6d9d6f546
4
+ data.tar.gz: 6e09959e44cbf2aad7bcf29df8dfa25f6cc903c6
5
5
  SHA512:
6
- metadata.gz: 7c113de8d650081766da2ba86cbc85e2e038e02cf2e8f358ab76feabeacae8ca2fc5d29447e68608a0a31d2466177fe44722b4bfe2d25a40cb1004e7edfa2a50
7
- data.tar.gz: 6600285bcf6beb7fb56d38bdb555cba51e745a0237c2a21059f9b3905faeec6487866b1deb3b028751b67a4d31fb2a27684c9f9bd63ea2f5eb12bdbdd7538c3b
6
+ metadata.gz: 3a83c419303aa9980ecc4769c711798742aa0a3a77202ef04e18300ba32d414994f9e2b3fa6dd5042489e5b9caaca75966c4bdcfe1a3e1a995f641d176f5a89f
7
+ data.tar.gz: 5f31bea6b6e1d473a42f73f2971efd7567220d0283c645b488ea6f35870be5e460352346996469179408f11fc9190ce61e17ce2ac435499ae89e732ead1e65b0
data/CHANGELOG.md CHANGED
@@ -6,6 +6,25 @@ For more information about changelogs, check
6
6
  [Keep a Changelog](http://keepachangelog.com) and
7
7
  [Vandamme](http://tech-angels.github.io/vandamme).
8
8
 
9
+ ## 0.15.0 - 2015-04-19
10
+
11
+ **How to upgrade**
12
+
13
+ If your code never calls the `viewer_percentage(gender: [:female|:male])` method
14
+ on a Channel or Video object, then you are good to go.
15
+
16
+ If it does, then replace your calls to `viewer_percentage(gender: :female)`
17
+ with `viewer_percentage(by: gender)[:female]`, and do the same for `:male`.
18
+
19
+ Note that the _plural_ `viewer_percentages` method still works but it’s
20
+ deprecated: you should use `viewer_percentage` instead.
21
+
22
+ * [ENHANCEMENT] Remove `:gender` option in `viewer_percentage` in favor of a more generic `:by`
23
+ * [FEATURE] New `by: :gender` option for reports, to return viewer percentage by gender
24
+ * [FEATURE] New `by: :age_group` option for reports, to return viewer percentage by age group
25
+ * [ENHANCEMENT] The viewer percentage report now accepts start/end date options (like any other report)
26
+ * [DEPRECATION] Deprecate `viewer_percentages` in favor of `viewer_percentage`.
27
+
9
28
  ## 0.14.7 - 2015-04-17
10
29
 
11
30
  * [FEATURE] New `by: :device_type` option for reports, to return views and estimated watched minutes (channels) by device
data/README.md CHANGED
@@ -41,7 +41,7 @@ To install on your system, run
41
41
 
42
42
  To use inside a bundled Ruby project, add this line to the Gemfile:
43
43
 
44
- gem 'yt', '~> 0.14.7'
44
+ gem 'yt', '~> 0.15.0'
45
45
 
46
46
  Since the gem follows [Semantic Versioning](http://semver.org),
47
47
  indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
@@ -156,7 +156,7 @@ Use [Yt::Channel](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Chan
156
156
  * retrieve the views and estimated minutes watched by video
157
157
  * retrieve the views and estimated minutes watched by playlist
158
158
  * retrieve the views and estimated minutes watched by device type
159
- * retrieve the viewer percentage of a channel by gender and age group
159
+ * retrieve the viewer percentage of a channel by gender, by age group or by both
160
160
 
161
161
  ```ruby
162
162
  # Channels can be initialized with ID or URL
@@ -215,8 +215,7 @@ channel.favorites_removed from: '2014-08-30', to: '2014-08-31' #=> {Sat, 30 Aug
215
215
  channel.estimated_minutes_watched #=> {Sun, 22 Feb 2015=>2433258.0, Mon, 23 Feb 2015=>2634360.0, …}
216
216
  channel.average_view_duration #=> {Sun, 22 Feb 2015=>329.0, Mon, 23 Feb 2015=>326.0, …}
217
217
  channel.average_view_percentage # {Sun, 22 Feb 2015=>38.858253094977265, Mon, 23 Feb 2015=>37.40014235438217, …}
218
- channel.viewer_percentages #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
219
- channel.viewer_percentage(gender: :male) #=> 49.12
218
+ channel.viewer_percentage #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
220
219
 
221
220
  channel.views since: 7.days.ago, by: :traffic_source #=> {advertising: 10.0, related_video: 20.0, promoted: 5.0, subscriber: 1.0, channel: 3.0, other: 7.0}
222
221
  channel.estimated_minutes_watched since: 7.days.ago, by: :traffic_source #=> {annotation: 10.0, external_app: 20.0, external_url: 5.0, embedded: 1.0, search: 3.0}
@@ -258,8 +257,8 @@ channel.subscribers_lost from: '2014-08-30', to: '2014-08-31' #=> {Sat, 30 Aug 2
258
257
  channel.estimated_minutes_watched #=> {Sun, 22 Feb 2015=>2433258.0, Mon, 23 Feb 2015=>2634360.0, …}
259
258
  channel.average_view_duration #=> {Sun, 22 Feb 2015=>329.0, Mon, 23 Feb 2015=>326.0, …}
260
259
  channel.average_view_percentage # {Sun, 22 Feb 2015=>38.858253094977265, Mon, 23 Feb 2015=>37.40014235438217, …}
261
- channel.viewer_percentages #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
262
- channel.viewer_percentage(gender: :female) #=> 49.12
260
+ channel.viewer_percentage since: 7.days.ago #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
261
+
263
262
  channel.views since: 7.days.ago, by: :traffic_source #=> {advertising: 10.0, related_video: 20.0, promoted: 5.0, subscriber: 1.0, channel: 3.0, other: 7.0}
264
263
  channel.estimated_minutes_watched since: 7.days.ago, by: :traffic_source #=> {annotation: 10.0, external_app: 20.0, external_url: 5.0, embedded: 1.0, search: 3.0}
265
264
  channel.views since: 7.days.ago, by: :playback_location #=> {embedded: 34.0, watch: 467.0, channel: 462.0, other: 3.0}
@@ -275,6 +274,8 @@ channel.estimated_minutes_watched since: 7.days.ago, by: :playlist #=> {#<Yt::Mo
275
274
  channel.views since: 7.days.ago, by: :device_type #=> {mobile: 144473.0, unknown_platform: 840.0, game_console: 4940.0, desktop: 102889.0, tv: 4134.0, tablet: 50189.0}
276
275
  channel.estimated_minutes_watched since: 7.days.ago, by: :device_type #=> {mobile: 144473.0, unknown_platform: 840.0, game_console: 4940.0, desktop: 102889.0, tv: 4134.0, tablet: 50189.0}
277
276
  channel.monetized_playbacks_on 5.days.ago #=> 123.0
277
+ channel.viewer_percentage since: 7.days.ago, by: :gender #=> {female: 49.12, male: 50.88}
278
+ channel.viewer_percentage since: 7.days.ago, by: :age_group #=> {'18-24' => 12.12, '25-34' => 16.16,…}
278
279
 
279
280
  channel.content_owner #=> 'CMSname'
280
281
  channel.linked_at #=> Wed, 28 May 2014
@@ -299,7 +300,7 @@ Use [Yt::Video](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Video)
299
300
  * retrieve the views of a video by embedded player location
300
301
  * retrieve the views of a video by related video
301
302
  * retrieve the views of a video by device type
302
- * retrieve the viewer percentage of a video by gender and age group
303
+ * retrieve the viewer percentage of a video by gender, by age group or by both
303
304
  * retrieve data about live-streaming videos
304
305
  * retrieve the advertising options of a video
305
306
 
@@ -396,14 +397,15 @@ video.favorites_removed from: '2014-08-30', to: '2014-08-31' #=> {Sat, 30 Aug 20
396
397
  video.average_view_duration #=> {Sun, 22 Feb 2015=>329.0, Mon, 23 Feb 2015=>326.0, …}
397
398
  video.average_view_percentage # {Sun, 22 Feb 2015=>38.858253094977265, Mon, 23 Feb 2015=>37.40014235438217, …}
398
399
  video.estimated_minutes_watched #=> {Sun, 22 Feb 2015=>2433258.0, Mon, 23 Feb 2015=>2634360.0, …}
399
- video.viewer_percentages #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
400
- video.viewer_percentage(gender: :female) #=> 49.12
400
+ video.viewer_percentage #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
401
401
 
402
402
  video.views since: 7.days.ago, by: :traffic_source #=> {advertising: 10.0, related_video: 20.0, promoted: 5.0, subscriber: 1.0, channel: 3.0, other: 7.0}
403
403
  video.views since: 7.days.ago, by: :playback_location #=> {:embedded=>6.0, :watch=>11.0}
404
404
  video.views since: 7.days.ago, by: :embedded_player_location #=> {"fullscreen.net"=>45.0, "linkedin.com"=>5.0, "mashable.com"=>1.0, "unknown"=>1.0}
405
405
  video.views since: 7.days.ago, by: :related_video #=> {#<Yt::Models::Video @id=...>: 10.0, #<Yt::Models::Video @id=...>: 20.0, …}
406
406
  video.views since: 7.days.ago, by: :device_type #=> {mobile: 144473.0, unknown_platform: 840.0, game_console: 4940.0, desktop: 102889.0, tv: 4134.0, tablet: 50189.0}
407
+ video.viewer_percentage since: 7.days.ago, by: :gender #=> {female: 49.12, male: 50.88}
408
+ video.viewer_percentage since: 7.days.ago, by: :age_group #=> {'18-24' => 12.12, '25-34' => 16.16,…}
407
409
 
408
410
  video.delete #=> true
409
411
  ```
@@ -429,12 +431,15 @@ video.average_view_percentage # {Sun, 22 Feb 2015=>38.858253094977265, Mon, 23 F
429
431
  video.estimated_minutes_watched #=> {Sun, 22 Feb 2015=>2433258.0, Mon, 23 Feb 2015=>2634360.0, …}
430
432
  video.impressions_on 5.days.ago #=> 157.0
431
433
  video.monetized_playbacks_on 5.days.ago #=> 123.0
432
-
433
- video.viewer_percentages #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
434
- video.viewer_percentage(gender: :female) #=> 49.12
434
+ video.viewer_percentage #=> {female: {'18-24' => 12.12, '25-34' => 16.16,…}…}
435
435
 
436
436
  video.views since: 7.days.ago, by: :traffic_source #=> {advertising: 10.0, related_video: 20.0, promoted: 5.0, subscriber: 1.0, channel: 3.0, other: 7.0}
437
437
  video.views since: 7.days.ago, by: :playback_location #=> {:embedded=>6.0, :watch=>11.0}
438
+ video.views since: 7.days.ago, by: :embedded_player_location #=> {"fullscreen.net"=>45.0, "linkedin.com"=>5.0, "mashable.com"=>1.0, "unknown"=>1.0}
439
+ video.views since: 7.days.ago, by: :related_video #=> {#<Yt::Models::Video @id=...>: 10.0, #<Yt::Models::Video @id=...>: 20.0, …}
440
+ video.views since: 7.days.ago, by: :device_type #=> {mobile: 144473.0, unknown_platform: 840.0, game_console: 4940.0, desktop: 102889.0, tv: 4134.0, tablet: 50189.0}
441
+ video.viewer_percentage since: 7.days.ago, by: :gender #=> {female: 49.12, male: 50.88}
442
+ video.viewer_percentage since: 7.days.ago, by: :age_group #=> {'18-24' => 12.12, '25-34' => 16.16,…}
438
443
 
439
444
  video.ad_formats #=> ["standard_instream", "trueview_instream"]
440
445
  ```
@@ -43,10 +43,10 @@ module Yt
43
43
 
44
44
  def define_metric_method(metric)
45
45
  define_method metric do |options = {}|
46
- from = options[:since] || options[:from] || 5.days.ago
47
- to = options[:until] || options[:to] || 1.day.ago
46
+ from = options[:since] || options[:from] || (metric == :viewer_percentage ? 3.months.ago : 5.days.ago)
47
+ to = options[:until] || options[:to] || (metric == :viewer_percentage ? Date.today : 1.day.ago)
48
48
  range = Range.new *[from, to].map(&:to_date)
49
- dimension = options[:by] || :day
49
+ dimension = options[:by] || (metric == :viewer_percentage ? :gender_age_group : :day)
50
50
 
51
51
  ivar = instance_variable_get "@#{metric}_#{dimension}"
52
52
  instance_variable_set "@#{metric}_#{dimension}", ivar || {}
@@ -4,8 +4,10 @@ module Yt
4
4
  #
5
5
  # YouTube resources with viewer percentage reports are:
6
6
  # {Yt::Models::Channel channels} and {Yt::Models::Channel videos}.
7
+ # @deprecated Use {#viewer_percentage} instead.
7
8
  module HasViewerPercentages
8
9
  # @!macro has_viewer_percentages
10
+ # @!deprecated Use {#viewer_percentage} instead.
9
11
  # @!method viewer_percentages
10
12
  # @return [Hash<Symbol,Hash<String,Float>>] the viewer percentages.
11
13
  # The first-level hash identifies the genres (:female, :male).
@@ -20,7 +22,7 @@ module Yt
20
22
  # @example Return the % of male viewers of a video
21
23
  # channel.viewer_percentage(gender: :male) #=> 52.02
22
24
 
23
- # Defines two public instance methods to access the viewer percentages of
25
+ # Defines one public instance methods to access the viewer percentages of
24
26
  # a resource for a specific metric.
25
27
  # @example Adds +viewer_percentages+ and +viewer_percentage+ on Channel.
26
28
  # class Channel < Resource
@@ -30,23 +32,15 @@ module Yt
30
32
  require 'yt/collections/viewer_percentages'
31
33
 
32
34
  define_viewer_percentages_method
33
- define_viewer_percentage_method
34
35
  end
35
36
 
36
37
  private
37
38
 
38
39
  def define_viewer_percentages_method
39
- # @todo: add options like start and end date
40
40
  define_method :viewer_percentages do
41
41
  @viewer_percentages ||= Collections::ViewerPercentages.of(self)[id]
42
42
  end
43
43
  end
44
-
45
- def define_viewer_percentage_method
46
- define_method :viewer_percentage do |filters = {}|
47
- viewer_percentages[filters[:gender]].values.sum.round(2)
48
- end
49
- end
50
44
  end
51
45
  end
52
46
  end
@@ -13,6 +13,12 @@ module Yt
13
13
 
14
14
  private
15
15
 
16
+ def attributes_for_new_item(data)
17
+ snippet = data.fetch 'snippet', {}
18
+ data['snippet'].reverse_merge! complete: snippet.key?('position')
19
+ super(data)
20
+ end
21
+
16
22
  # @return [Hash] the parameters to submit to YouTube to list playlist items.
17
23
  # @see https://developers.google.com/youtube/v3/docs/playlistItems/list
18
24
  def list_params
@@ -11,6 +11,9 @@ module Yt
11
11
  hash[:video] = {name: 'video', parse: ->(video_id) { Yt::Video.new id: video_id, auth: @auth } }
12
12
  hash[:playlist] = {name: 'playlist', parse: ->(playlist_id) { Yt::Playlist.new id: playlist_id, auth: @auth } }
13
13
  hash[:device_type] = {name: 'deviceType', parse: ->(type) { type.downcase.to_sym } }
14
+ hash[:gender_age_group] = {name: 'gender,ageGroup', parse: ->(gender) { gender.downcase.to_sym }}
15
+ hash[:gender] = {name: 'gender', parse: ->(gender) { gender.downcase.to_sym } }
16
+ hash[:age_group] = {name: 'ageGroup', parse: ->(age_group) { age_group[3..-1] } }
14
17
  end
15
18
 
16
19
  # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#Traffic_Source_Dimensions
@@ -46,7 +49,13 @@ module Yt
46
49
  def within(days_range, dimension, try_again = true)
47
50
  @days_range = days_range
48
51
  @dimension = dimension
49
- Hash[*flat_map{|daily_value| daily_value}]
52
+ if dimension == :gender_age_group # array of array
53
+ Hash.new{|h,k| h[k] = Hash.new 0.0}.tap do |hash|
54
+ each{|gender, age_group, value| hash[gender][age_group[3..-1]] = value}
55
+ end
56
+ else
57
+ Hash[*flat_map{|value| [value.first, value.last]}]
58
+ end
50
59
  # NOTE: Once in a while, YouTube responds with 400 Error and the message
51
60
  # "Invalid query. Query did not conform to the expectations."; in this
52
61
  # case running the same query after one second fixes the issue. This is
@@ -60,7 +69,7 @@ module Yt
60
69
  private
61
70
 
62
71
  def new_item(data)
63
- [instance_exec(data.first, &DIMENSIONS[@dimension][:parse]), data.last]
72
+ [instance_exec(data.first, &DIMENSIONS[@dimension][:parse]), *data[1..-1]]
64
73
  end
65
74
 
66
75
  # @see https://developers.google.com/youtube/analytics/v1/content_owner_reports
@@ -17,7 +17,7 @@ module Yt
17
17
 
18
18
  def attributes_for_new_item(data)
19
19
  id = use_list_endpoint? ? data['id'] : data['id']['videoId']
20
- snippet = data['snippet'].reverse_merge includes_tags: false if data['snippet']
20
+ snippet = data['snippet'].reverse_merge complete: false if data['snippet']
21
21
  {}.tap do |attributes|
22
22
  attributes[:id] = id
23
23
  attributes[:snippet] = snippet
@@ -39,7 +39,7 @@ module Yt
39
39
  video = videos.find{|v| v.id == item['id']['videoId']}
40
40
  parts.each do |part|
41
41
  item[part] = case part
42
- when 'snippet' then video.snippet.data.merge includes_tags: true
42
+ when 'snippet' then video.snippet.data.merge complete: true
43
43
  when 'status' then video.status.data
44
44
  when 'statistics' then video.statistics_set.data
45
45
  when 'contentDetails' then video.content_detail.data
@@ -2,6 +2,7 @@ require 'yt/collections/base'
2
2
 
3
3
  module Yt
4
4
  module Collections
5
+ # @deprecated Use {Yt::Collections::Reports} instead.
5
6
  class ViewerPercentages < Base
6
7
  delegate :[], to: :all
7
8
 
@@ -58,6 +58,10 @@ module Yt
58
58
  # @macro has_report
59
59
  has_report :monetized_playbacks
60
60
 
61
+ # @macro has_report
62
+ has_report :viewer_percentage
63
+
64
+ # @deprecated Use {#has_report :viewer_percentage}.
61
65
  # @macro has_viewer_percentages
62
66
  has_viewer_percentages
63
67
 
@@ -5,7 +5,7 @@ module Yt
5
5
  # Provides methods to interact with YouTube playlist items.
6
6
  # @see https://developers.google.com/youtube/v3/docs/playlistItems
7
7
  class PlaylistItem < Resource
8
- delegate :channel_id, :channel_title, :playlist_id, :position, :video_id,
8
+ delegate :channel_id, :channel_title, :playlist_id, :video_id,
9
9
  :video, to: :snippet
10
10
 
11
11
  def delete(options = {})
@@ -17,6 +17,20 @@ module Yt
17
17
  !@id.nil?
18
18
  end
19
19
 
20
+ # Returns the position of the item in the playlist.
21
+ # Since YouTube API does not return the position on PlaylistItem#create,
22
+ # the memoized @snippet is erased if the video was instantiated like that,
23
+ # so that the full snippet (with position) is loaded, rather than the
24
+ # partial one.
25
+ # @see https://developers.google.com/youtube/v3/docs/playlistItems
26
+ # @return [Integer] the order in which the item appears in a playlist.
27
+ def position
28
+ unless snippet.position || snippet.complete? || @auth.nil?
29
+ @snippet = nil
30
+ end
31
+ snippet.position
32
+ end
33
+
20
34
  private
21
35
 
22
36
  def resource_id
@@ -146,16 +146,17 @@ module Yt
146
146
  @video ||= Video.new id: video_id, auth: @auth if video_id
147
147
  end
148
148
 
149
- # Returns whether YouTube API includes tags in this snippet.
150
- # YouTube API only returns tags on Videos#list, not on Videos#search.
149
+ # Returns whether YouTube API includes all attributes in this snippet.
150
+ # For instance, YouTube API only returns tags on Videos#list, not on
151
+ # Videos#search. And returns position on PlaylistItems#list, not on
152
+ # PlaylistItems#insert.
151
153
  # This method is used to guarantee that, when a video was instantiated
152
- # by calling account.videos or channel.videos, then video.tags is not
153
- # simply returned as empty, but an additional call to Videos#list is
154
- # made to retrieve the correct tags.
154
+ # by one of the methods above, an additional call to is made to retrieve
155
+ # the full snippet in case an attribute is missing.
155
156
  # @see https://developers.google.com/youtube/v3/docs/videos
156
- # @return [Boolean] whether YouTube API includes tags in this snippet.
157
- def includes_tags
158
- @includes_tags ||= data.fetch :includes_tags, true
157
+ # @return [Boolean] whether YouTube API includes the complete snippet.
158
+ def complete?
159
+ @complete ||= data.fetch :complete, true
159
160
  end
160
161
 
161
162
  private
@@ -89,6 +89,10 @@ module Yt
89
89
  # @macro has_report
90
90
  has_report :monetized_playbacks
91
91
 
92
+ # @macro has_report
93
+ has_report :viewer_percentage
94
+
95
+ # @deprecated Use {#has_report :viewer_percentage}.
92
96
  # @macro has_viewer_percentages
93
97
  has_viewer_percentages
94
98
 
@@ -124,7 +128,7 @@ module Yt
124
128
  # @return [Array<Yt::Models::Tag>] the list of keyword tags associated
125
129
  # with the video.
126
130
  def tags
127
- unless snippet.tags.any? || snippet.includes_tags || @auth.nil?
131
+ unless snippet.tags.any? || snippet.complete? || @auth.nil?
128
132
  @snippet = nil
129
133
  end
130
134
  snippet.tags
data/lib/yt/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.14.7'
2
+ VERSION = '0.15.0'
3
3
  end
@@ -9,7 +9,7 @@ describe Yt::Collections::Reports do
9
9
  let(:msg) { {response_body: {error: {errors: [error]}}}.to_json }
10
10
 
11
11
  describe '#within' do
12
- let(:result) { reports.within Range.new(5.days.ago, 4.days.ago) }
12
+ let(:result) { reports.within Range.new(5.days.ago, 4.days.ago), :day }
13
13
  context 'given the request raises error 400 with "Invalid Query" message' do
14
14
  let(:reason) { 'badRequest' }
15
15
  let(:message) { 'Invalid query. Query did not conform to the expectations' }
@@ -22,7 +22,7 @@ describe Yt::Collections::Reports do
22
22
  end
23
23
 
24
24
  context 'but returns a success code 2XX the second time' do
25
- before { try_again.and_return [1,2] }
25
+ before { try_again.and_return [[1,2]] }
26
26
  it { expect{result}.not_to fail }
27
27
  end
28
28
  end
@@ -51,12 +51,12 @@ describe Yt::Account, :device_app do
51
51
  end
52
52
 
53
53
  describe '.includes(:snippet)' do
54
- let(:video) { $account.videos.includes(:snippet).first }
54
+ let(:video) { $account.videos.first }
55
55
 
56
56
  specify 'eager-loads the *full* snippet of each video' do
57
57
  expect(video.instance_variable_defined? :@snippet).to be true
58
58
  expect(video.channel_title).to be
59
- expect(video.snippet.includes_tags).to be true
59
+ expect(video.snippet).to be_complete
60
60
  end
61
61
  end
62
62
 
@@ -109,10 +109,13 @@ describe Yt::Account, :device_app do
109
109
  it { expect{account.authentication}.to raise_error Yt::Errors::MissingAuth }
110
110
  end
111
111
 
112
- context 'and an invalid device code' do
113
- before { attrs[:device_code] = '--not-a-valid-device-code--' }
114
- it { expect{account.authentication}.to raise_error Yt::Errors::MissingAuth }
115
- end
112
+ # NOTE: This test is commented out because of YouTube irrational behavior
113
+ # of using to return "MissingAuth" when passing a wrong device code, and
114
+ # now randomly returning `{"error"=>"internal_failure"}` instead.
115
+ # context 'and an invalid device code' do
116
+ # before { attrs[:device_code] = '--not-a-valid-device-code--' }
117
+ # it { expect{account.authentication}.to raise_error Yt::Errors::MissingAuth }
118
+ # end
116
119
  end
117
120
 
118
121
  context 'given no token or code' do
@@ -26,7 +26,7 @@ describe Yt::Channel, :device_app do
26
26
 
27
27
  specify 'returns the videos in the channel without their tags' do
28
28
  expect(video).to be_a Yt::Video
29
- expect(video.snippet.includes_tags).to be false
29
+ expect(video.snippet).not_to be_complete
30
30
  end
31
31
 
32
32
  describe '.where(id: *anything*)' do
@@ -175,6 +175,7 @@ describe Yt::Channel, :device_app do
175
175
  expect{channel.estimated_minutes_watched}.not_to raise_error
176
176
  expect{channel.average_view_duration}.not_to raise_error
177
177
  expect{channel.average_view_percentage}.not_to raise_error
178
+ expect{channel.viewer_percentage}.not_to raise_error
178
179
  expect{channel.earnings}.to raise_error Yt::Errors::Unauthorized
179
180
  expect{channel.impressions}.to raise_error Yt::Errors::Unauthorized
180
181
  expect{channel.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
@@ -195,9 +196,9 @@ describe Yt::Channel, :device_app do
195
196
  expect{channel.impressions_on 3.days.ago}.to raise_error Yt::Errors::Unauthorized
196
197
  end
197
198
 
199
+ # @deprecated, use channel.viewer_percentage instead
198
200
  it 'returns valid reports for channel-related demographics' do
199
201
  expect{channel.viewer_percentages}.not_to raise_error
200
- expect{channel.viewer_percentage}.not_to raise_error
201
202
  end
202
203
 
203
204
  it 'cannot give information about its content owner' do
@@ -5,7 +5,7 @@ describe Yt::PlaylistItem, :device_app do
5
5
  subject(:item) { Yt::PlaylistItem.new id: id, auth: $account }
6
6
 
7
7
  context 'given an existing playlist item' do
8
- let(:id) { 'PLjW_GNR5Ir0GWEP_oveGBNjTvKkYyZfsna1TZDCBP-Z8' }
8
+ let(:id) { 'PLjW_GNR5Ir0GMlbJzA-aW0UV8TchJFb8p3uzrLNcZKPY' }
9
9
 
10
10
  it 'returns valid metadata' do
11
11
  expect(item.title).to be_a String
@@ -7,7 +7,7 @@ describe Yt::Playlist, :device_app do
7
7
  subject(:playlist) { Yt::Playlist.new id: id, auth: $account }
8
8
 
9
9
  context 'given an existing playlist' do
10
- let(:id) { 'PLSWYkYzOrPMRCK6j0UgryI8E0NHhoVdRc' }
10
+ let(:id) { 'PLSWYkYzOrPMT9pJG5St5G0WDalhRzGkU4' }
11
11
 
12
12
  it 'returns valid metadata' do
13
13
  expect(playlist.title).to be_a String
@@ -21,6 +21,8 @@ describe Yt::Playlist, :device_app do
21
21
  end
22
22
 
23
23
  it { expect(playlist.playlist_items.first).to be_a Yt::PlaylistItem }
24
+ it { expect(playlist.playlist_items.first.snippet).to be_complete }
25
+ it { expect(playlist.playlist_items.first.position).not_to be_nil }
24
26
  end
25
27
 
26
28
  context 'given an unknown playlist' do
@@ -31,7 +33,7 @@ describe Yt::Playlist, :device_app do
31
33
  end
32
34
 
33
35
  context 'given someone else’s playlist' do
34
- let(:id) { 'PLSWYkYzOrPMRCK6j0UgryI8E0NHhoVdRc' }
36
+ let(:id) { 'PLSWYkYzOrPMT9pJG5St5G0WDalhRzGkU4' }
35
37
  let(:video_id) { 'MESycYJytkU' }
36
38
 
37
39
  it { expect{playlist.delete}.to fail.with 'forbidden' }
@@ -137,6 +139,20 @@ describe Yt::Playlist, :device_app do
137
139
  it { expect(playlist.add_video(video_id, position: 0).position).to be 0 }
138
140
  end
139
141
 
142
+ # NOTE: This test sounds redundant, but it’s actually a reflection of
143
+ # another irrational behavior of YouTube API. In short, if you add a new
144
+ # video to a playlist, the returned item does not have the "position"
145
+ # information. You need an extra call to get it. When YouTube fixes this
146
+ # behavior, this test (and related code) will go away.
147
+ describe 'adding the video' do
148
+ let(:item) { playlist.add_video video_id }
149
+
150
+ specify 'returns an item without its position' do
151
+ expect(item.snippet).not_to be_complete
152
+ expect(item.position).not_to be_nil # after reloading
153
+ end
154
+ end
155
+
140
156
  describe 'can be removed' do
141
157
  before { playlist.add_video video_id }
142
158
 
@@ -292,6 +292,7 @@ describe Yt::Video, :device_app do
292
292
  expect{video.estimated_minutes_watched}.not_to raise_error
293
293
  expect{video.average_view_duration}.not_to raise_error
294
294
  expect{video.average_view_percentage}.not_to raise_error
295
+ expect{video.viewer_percentage}.not_to raise_error
295
296
  expect{video.earnings}.to raise_error Yt::Errors::Unauthorized
296
297
  expect{video.impressions}.to raise_error Yt::Errors::Unauthorized
297
298
  expect{video.monetized_playbacks}.to raise_error Yt::Errors::Unauthorized
@@ -313,9 +314,9 @@ describe Yt::Video, :device_app do
313
314
  expect{video.impressions_on 3.days.ago}.to raise_error Yt::Errors::Unauthorized
314
315
  end
315
316
 
317
+ # @deprecated, use video.viewer_percentage instead
316
318
  it 'returns valid reports for video-related demographics' do
317
319
  expect{video.viewer_percentages}.not_to raise_error
318
- expect{video.viewer_percentage}.not_to raise_error
319
320
  end
320
321
  end
321
322
 
@@ -311,7 +311,7 @@ describe Yt::Channel, :partner do
311
311
 
312
312
  describe 'shares can be retrieved for a specific day' do
313
313
  context 'in which the channel was partnered' do
314
- let(:shares) { channel.shares_on 5.days.ago}
314
+ let(:shares) { channel.shares_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
315
315
  it { expect(shares).to be_a Float }
316
316
  end
317
317
 
@@ -845,6 +845,58 @@ describe Yt::Channel, :partner do
845
845
  end
846
846
  end
847
847
 
848
+ describe 'viewer percentage can be retrieved for a range of days' do
849
+ let(:viewer_percentage) { channel.viewer_percentage since: 1.year.ago, until: 10.days.ago}
850
+ it { expect(viewer_percentage).to be_a Hash }
851
+ end
852
+
853
+ describe 'viewer_percentage can be grouped by gender and age group' do
854
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
855
+ let(:keys) { range.values }
856
+
857
+ specify 'without a :by option (default)' do
858
+ viewer_percentage = channel.viewer_percentage range
859
+ expect(viewer_percentage.keys).to match_array [:female, :male]
860
+ expect(viewer_percentage[:female].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
861
+ expect(viewer_percentage[:female].values).to all(be_instance_of Float)
862
+ expect(viewer_percentage[:male].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
863
+ expect(viewer_percentage[:male].values).to all(be_instance_of Float)
864
+ end
865
+
866
+ specify 'with the :by option set to :gender_age_group' do
867
+ viewer_percentage = channel.viewer_percentage range.merge by: :gender_age_group
868
+ expect(viewer_percentage.keys).to match_array [:female, :male]
869
+ expect(viewer_percentage[:female].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
870
+ expect(viewer_percentage[:female].values).to all(be_instance_of Float)
871
+ expect(viewer_percentage[:male].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
872
+ expect(viewer_percentage[:male].values).to all(be_instance_of Float)
873
+ end
874
+ end
875
+
876
+ describe 'viewer_percentage can be grouped by gender' do
877
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
878
+ let(:keys) { range.values }
879
+
880
+ specify 'with the :by option set to :gender' do
881
+ viewer_percentage = channel.viewer_percentage range.merge by: :gender
882
+ expect(viewer_percentage.keys).to match_array [:female, :male]
883
+ expect(viewer_percentage[:female]).to be_a Float
884
+ expect(viewer_percentage[:male]).to be_a Float
885
+ end
886
+ end
887
+
888
+ describe 'viewer_percentage can be grouped by age group' do
889
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
890
+ let(:keys) { range.values }
891
+
892
+ specify 'with the :by option set to :age_group' do
893
+ viewer_percentage = channel.viewer_percentage range.merge by: :age_group
894
+ expect(viewer_percentage.keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
895
+ expect(viewer_percentage.values).to all(be_instance_of Float)
896
+ end
897
+ end
898
+
899
+ # @deprecated, use channel.viewer_percentage instead
848
900
  specify 'viewer percentages by gender and age range can be retrieved' do
849
901
  expect(channel.viewer_percentages[:female]['18-24']).to be_a Float
850
902
  expect(channel.viewer_percentages[:female]['25-34']).to be_a Float
@@ -858,9 +910,6 @@ describe Yt::Channel, :partner do
858
910
  expect(channel.viewer_percentages[:male]['45-54']).to be_a Float
859
911
  expect(channel.viewer_percentages[:male]['55-64']).to be_a Float
860
912
  expect(channel.viewer_percentages[:male]['65-']).to be_a Float
861
-
862
- expect(channel.viewer_percentage(gender: :male)).to be_a Float
863
- expect(channel.viewer_percentage(gender: :female)).to be_a Float
864
913
  end
865
914
 
866
915
  specify 'information about its content owner can be retrieved' do
@@ -15,7 +15,7 @@ describe Yt::Video, :partner do
15
15
 
16
16
  describe 'earnings can be retrieved for a specific day' do
17
17
  context 'in which the video made any money' do
18
- let(:earnings) {video.earnings_on 5.days.ago}
18
+ let(:earnings) {video.earnings_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
19
19
  it { expect(earnings).to be_a Float }
20
20
  end
21
21
 
@@ -26,22 +26,14 @@ describe Yt::Video, :partner do
26
26
  end
27
27
 
28
28
  describe 'earnings can be retrieved for a range of days' do
29
- let(:date) { 4.days.ago }
30
-
31
- specify 'with a given start (:since option)' do
32
- expect(video.earnings(since: date).keys.min).to eq date.to_date
33
- end
29
+ let(:date) { ENV['YT_TEST_PARTNER_VIDEO_DATE'] }
34
30
 
35
- specify 'with a given end (:until option)' do
36
- expect(video.earnings(until: date).keys.max).to eq date.to_date
37
- end
38
-
39
- specify 'with a given start (:from option)' do
40
- expect(video.earnings(from: date).keys.min).to eq date.to_date
31
+ specify 'with a given start and end (:since / :until option)' do
32
+ expect(video.earnings(since: date, until: date).keys.min).to eq date.to_date
41
33
  end
42
34
 
43
- specify 'with a given end (:to option)' do
44
- expect(video.earnings(to: date).keys.max).to eq date.to_date
35
+ specify 'with a given start and end (:from / :to option)' do
36
+ expect(video.earnings(from: date, to: date).keys.min).to eq date.to_date
45
37
  end
46
38
  end
47
39
 
@@ -113,7 +105,7 @@ describe Yt::Video, :partner do
113
105
  end
114
106
 
115
107
  describe 'views can be grouped by embedded player location' do
116
- let(:range) { {since: 4.days.ago, until: 3.days.ago} }
108
+ let(:range) { {since: ENV['YT_TEST_PARTNER_VIDEO_DATE'], until: 3.days.ago} }
117
109
 
118
110
  specify 'with the :by option set to :embedded_player_location' do
119
111
  views = video.views range.merge by: :embedded_player_location
@@ -142,7 +134,7 @@ describe Yt::Video, :partner do
142
134
 
143
135
  describe 'comments can be retrieved for a specific day' do
144
136
  context 'in which the video was partnered' do
145
- let(:comments) { video.comments_on 5.days.ago}
137
+ let(:comments) { video.comments_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
146
138
  it { expect(comments).to be_a Float }
147
139
  end
148
140
 
@@ -189,7 +181,7 @@ describe Yt::Video, :partner do
189
181
 
190
182
  describe 'likes can be retrieved for a specific day' do
191
183
  context 'in which the video was partnered' do
192
- let(:likes) { video.likes_on 5.days.ago}
184
+ let(:likes) { video.likes_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
193
185
  it { expect(likes).to be_a Float }
194
186
  end
195
187
 
@@ -236,7 +228,7 @@ describe Yt::Video, :partner do
236
228
 
237
229
  describe 'dislikes can be retrieved for a specific day' do
238
230
  context 'in which the video was partnered' do
239
- let(:dislikes) { video.dislikes_on 5.days.ago}
231
+ let(:dislikes) { video.dislikes_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
240
232
  it { expect(dislikes).to be_a Float }
241
233
  end
242
234
 
@@ -283,7 +275,7 @@ describe Yt::Video, :partner do
283
275
 
284
276
  describe 'shares can be retrieved for a specific day' do
285
277
  context 'in which the video was partnered' do
286
- let(:shares) { video.shares_on 5.days.ago}
278
+ let(:shares) { video.shares_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
287
279
  it { expect(shares).to be_a Float }
288
280
  end
289
281
 
@@ -330,7 +322,7 @@ describe Yt::Video, :partner do
330
322
 
331
323
  describe 'gained subscribers can be retrieved for a specific day' do
332
324
  context 'in which the video was partnered' do
333
- let(:subscribers_gained) { video.subscribers_gained_on 5.days.ago}
325
+ let(:subscribers_gained) { video.subscribers_gained_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
334
326
  it { expect(subscribers_gained).to be_a Float }
335
327
  end
336
328
 
@@ -377,7 +369,7 @@ describe Yt::Video, :partner do
377
369
 
378
370
  describe 'lost subscribers can be retrieved for a specific day' do
379
371
  context 'in which the video was partnered' do
380
- let(:subscribers_lost) { video.subscribers_lost_on 5.days.ago}
372
+ let(:subscribers_lost) { video.subscribers_lost_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
381
373
  it { expect(subscribers_lost).to be_a Float }
382
374
  end
383
375
 
@@ -424,7 +416,7 @@ describe Yt::Video, :partner do
424
416
 
425
417
  describe 'added favorites can be retrieved for a specific day' do
426
418
  context 'in which the video was partnered' do
427
- let(:favorites_added) { video.favorites_added_on 5.days.ago}
419
+ let(:favorites_added) { video.favorites_added_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
428
420
  it { expect(favorites_added).to be_a Float }
429
421
  end
430
422
 
@@ -471,7 +463,7 @@ describe Yt::Video, :partner do
471
463
 
472
464
  describe 'removed favorites can be retrieved for a specific day' do
473
465
  context 'in which the video was partnered' do
474
- let(:favorites_removed) { video.favorites_removed_on 5.days.ago}
466
+ let(:favorites_removed) { video.favorites_removed_on ENV['YT_TEST_PARTNER_VIDEO_DATE']}
475
467
  it { expect(favorites_removed).to be_a Float }
476
468
  end
477
469
 
@@ -761,6 +753,58 @@ describe Yt::Video, :partner do
761
753
  end
762
754
  end
763
755
 
756
+ describe 'viewer percentage can be retrieved for a range of days' do
757
+ let(:viewer_percentage) { video.viewer_percentage since: 1.year.ago, until: 10.days.ago}
758
+ it { expect(viewer_percentage).to be_a Hash }
759
+ end
760
+
761
+ describe 'viewer_percentage can be grouped by gender and age group' do
762
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
763
+ let(:keys) { range.values }
764
+
765
+ specify 'without a :by option (default)' do
766
+ viewer_percentage = video.viewer_percentage range
767
+ expect(viewer_percentage.keys).to match_array [:female, :male]
768
+ expect(viewer_percentage[:female].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
769
+ expect(viewer_percentage[:female].values).to all(be_instance_of Float)
770
+ expect(viewer_percentage[:male].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
771
+ expect(viewer_percentage[:male].values).to all(be_instance_of Float)
772
+ end
773
+
774
+ specify 'with the :by option set to :gender_age_group' do
775
+ viewer_percentage = video.viewer_percentage range.merge by: :gender_age_group
776
+ expect(viewer_percentage.keys).to match_array [:female, :male]
777
+ expect(viewer_percentage[:female].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
778
+ expect(viewer_percentage[:female].values).to all(be_instance_of Float)
779
+ expect(viewer_percentage[:male].keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
780
+ expect(viewer_percentage[:male].values).to all(be_instance_of Float)
781
+ end
782
+ end
783
+
784
+ describe 'viewer_percentage can be grouped by gender' do
785
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
786
+ let(:keys) { range.values }
787
+
788
+ specify 'with the :by option set to :gender' do
789
+ viewer_percentage = video.viewer_percentage range.merge by: :gender
790
+ expect(viewer_percentage.keys).to match_array [:female, :male]
791
+ expect(viewer_percentage[:female]).to be_a Float
792
+ expect(viewer_percentage[:male]).to be_a Float
793
+ end
794
+ end
795
+
796
+ describe 'viewer_percentage can be grouped by age group' do
797
+ let(:range) { {since: 1.year.ago.to_date, until: 1.week.ago.to_date} }
798
+ let(:keys) { range.values }
799
+
800
+ specify 'with the :by option set to :age_group' do
801
+ viewer_percentage = video.viewer_percentage range.merge by: :age_group
802
+ expect(viewer_percentage.keys - %w(65- 35-44 45-54 13-17 25-34 55-64 18-24)).to be_empty
803
+ expect(viewer_percentage.values).to all(be_instance_of Float)
804
+ end
805
+ end
806
+
807
+ # @deprecated, use video.viewer_percentage instead
764
808
  specify 'viewer percentages by gender and age range can be retrieved' do
765
809
  expect(video.viewer_percentages[:female]['18-24']).to be_a Float
766
810
  expect(video.viewer_percentages[:female]['25-34']).to be_a Float
@@ -774,9 +818,6 @@ describe Yt::Video, :partner do
774
818
  expect(video.viewer_percentages[:male]['45-54']).to be_a Float
775
819
  expect(video.viewer_percentages[:male]['55-64']).to be_a Float
776
820
  expect(video.viewer_percentages[:male]['65-']).to be_a Float
777
-
778
- expect(video.viewer_percentage(gender: :male)).to be_a Float
779
- expect(video.viewer_percentage(gender: :female)).to be_a Float
780
821
  end
781
822
  end
782
823
 
@@ -5,7 +5,7 @@ describe Yt::PlaylistItem, :server_app do
5
5
  subject(:item) { Yt::PlaylistItem.new id: id }
6
6
 
7
7
  context 'given an existing playlist item' do
8
- let(:id) { 'PLjW_GNR5Ir0GWEP_oveGBNjTvKkYyZfsna1TZDCBP-Z8' }
8
+ let(:id) { 'PLjW_GNR5Ir0GMlbJzA-aW0UV8TchJFb8p3uzrLNcZKPY' }
9
9
 
10
10
  it 'returns valid snippet data' do
11
11
  expect(item.snippet).to be_a Yt::Snippet
@@ -5,7 +5,7 @@ describe Yt::Playlist, :server_app do
5
5
  subject(:playlist) { Yt::Playlist.new id: id }
6
6
 
7
7
  context 'given an existing playlist' do
8
- let(:id) { 'PLSWYkYzOrPMRCK6j0UgryI8E0NHhoVdRc' }
8
+ let(:id) { 'PLSWYkYzOrPMT9pJG5St5G0WDalhRzGkU4' }
9
9
 
10
10
  it 'returns valid snippet data' do
11
11
  expect(playlist.snippet).to be_a Yt::Snippet
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.14.7
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-17 00:00:00.000000000 Z
11
+ date: 2015-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport