yt 0.19.0 → 0.20.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +1 -1
- data/lib/yt/associations/has_reports.rb +80 -44
- data/lib/yt/models/annotation.rb +1 -0
- data/lib/yt/models/asset.rb +0 -2
- data/lib/yt/models/channel.rb +186 -119
- data/lib/yt/models/claim.rb +0 -5
- data/lib/yt/models/content_owner_detail.rb +1 -25
- data/lib/yt/models/id.rb +1 -0
- data/lib/yt/models/playlist.rb +124 -38
- data/lib/yt/models/playlist_item.rb +62 -16
- data/lib/yt/models/reference.rb +0 -6
- data/lib/yt/models/resource.rb +11 -0
- data/lib/yt/models/snippet.rb +6 -96
- data/lib/yt/models/statistics_set.rb +1 -19
- data/lib/yt/models/status.rb +0 -2
- data/lib/yt/models/video.rb +24 -36
- data/lib/yt/version.rb +1 -1
- data/spec/models/channel_spec.rb +107 -0
- data/spec/models/content_owner_detail_spec.rb +0 -24
- data/spec/models/playlist_item_spec.rb +95 -6
- data/spec/models/playlist_spec.rb +87 -0
- data/spec/models/snippet_spec.rb +1 -118
- data/spec/models/statistics_set_spec.rb +1 -64
- data/spec/models/video_spec.rb +112 -0
- data/spec/requests/as_account/channel_spec.rb +1 -1
- data/spec/requests/as_account/playlist_item_spec.rb +1 -1
- data/spec/requests/as_account/playlist_spec.rb +1 -1
- data/spec/requests/as_account/video_spec.rb +2 -2
- data/spec/requests/as_content_owner/content_owner_spec.rb +4 -4
- data/spec/requests/as_content_owner/playlist_spec.rb +259 -0
- data/spec/requests/as_content_owner/video_spec.rb +38 -0
- metadata +1 -1
@@ -12,32 +12,14 @@ module Yt
|
|
12
12
|
@data = options[:data]
|
13
13
|
end
|
14
14
|
|
15
|
-
# @return [Integer] the number of times the resource has been viewed.
|
16
15
|
has_attribute :view_count, type: Integer
|
17
|
-
|
18
|
-
# @return [Integer] the number of comments for the resource.
|
19
16
|
has_attribute :comment_count, type: Integer
|
20
|
-
|
21
|
-
# @return [Integer] the number of users who liked the resource.
|
22
17
|
has_attribute :like_count, type: Integer
|
23
|
-
|
24
|
-
# @return [Integer] the number of users who disliked the resource.
|
25
18
|
has_attribute :dislike_count, type: Integer
|
26
|
-
|
27
|
-
# @return [Integer] the number of users who currently have the resource
|
28
|
-
# marked as a favorite resource.
|
29
19
|
has_attribute :favorite_count, type: Integer
|
30
|
-
|
31
|
-
# @return [Integer] the number of videos updated to the resource.
|
32
20
|
has_attribute :video_count, type: Integer
|
33
|
-
|
34
|
-
# @return [Integer] the number of subscriber the resource has.
|
35
21
|
has_attribute :subscriber_count, type: Integer
|
36
|
-
|
37
|
-
# @return [Boolean] whether the number of subscribers is publicly visible.
|
38
|
-
has_attribute :subscriber_count_visible?, from: :hidden_subscriber_count do |hidden|
|
39
|
-
hidden == false
|
40
|
-
end
|
22
|
+
has_attribute :hidden_subscriber_count
|
41
23
|
end
|
42
24
|
end
|
43
25
|
end
|
data/lib/yt/models/status.rb
CHANGED
data/lib/yt/models/video.rb
CHANGED
@@ -6,7 +6,7 @@ module Yt
|
|
6
6
|
# @see https://developers.google.com/youtube/v3/docs/videos
|
7
7
|
class Video < Resource
|
8
8
|
|
9
|
-
|
9
|
+
### SNIPPET ###
|
10
10
|
|
11
11
|
# @!attribute [r] title
|
12
12
|
# @return [String] the video’s title.
|
@@ -16,7 +16,7 @@ module Yt
|
|
16
16
|
# @return [String] the video’s description.
|
17
17
|
delegate :description, to: :snippet
|
18
18
|
|
19
|
-
#
|
19
|
+
# Returns the URL of the video’s thumbnail.
|
20
20
|
# @!method thumbnail_url(size = :default)
|
21
21
|
# @param [Symbol, String] size The size of the video’s thumbnail.
|
22
22
|
# @return [String] if +size+ is +default+, the URL of a 120x90px image.
|
@@ -364,7 +364,7 @@ module Yt
|
|
364
364
|
# hidden the viewcount for the video or the video is not live.
|
365
365
|
delegate :concurrent_viewers, to: :live_streaming_detail
|
366
366
|
|
367
|
-
###
|
367
|
+
### ASSOCIATIONS ###
|
368
368
|
|
369
369
|
# @!attribute [r] annotations
|
370
370
|
# @return [Yt::Collections::Annotations] the video’s annotations.
|
@@ -372,65 +372,65 @@ module Yt
|
|
372
372
|
|
373
373
|
### ANALYTICS ###
|
374
374
|
|
375
|
-
# @macro
|
375
|
+
# @macro report_by_video_dimensions
|
376
376
|
has_report :views
|
377
377
|
|
378
|
-
# @macro
|
378
|
+
# @macro report_by_video_dimensions
|
379
|
+
has_report :estimated_minutes_watched
|
380
|
+
|
381
|
+
# @macro report_by_gender_and_age_group
|
379
382
|
has_report :viewer_percentage
|
380
383
|
|
381
|
-
# @macro
|
384
|
+
# @macro report_by_day
|
382
385
|
has_report :comments
|
383
386
|
|
384
|
-
# @macro
|
387
|
+
# @macro report_by_day
|
385
388
|
has_report :likes
|
386
389
|
|
387
|
-
# @macro
|
390
|
+
# @macro report_by_day
|
388
391
|
has_report :dislikes
|
389
392
|
|
390
|
-
# @macro
|
393
|
+
# @macro report_by_day
|
391
394
|
has_report :shares
|
392
395
|
|
393
396
|
# @note This is not the total number of subscribers gained by the video’s
|
394
397
|
# channel, but the subscribers gained *from* the video’s page.
|
395
|
-
# @macro
|
398
|
+
# @macro report_by_day
|
396
399
|
has_report :subscribers_gained
|
397
400
|
|
398
401
|
# @note This is not the total number of subscribers lost by the video’s
|
399
402
|
# channel, but the subscribers lost *from* the video’s page.
|
400
|
-
# @macro
|
403
|
+
# @macro report_by_day
|
401
404
|
has_report :subscribers_lost
|
402
405
|
|
403
|
-
# @macro
|
406
|
+
# @macro report_by_day
|
404
407
|
has_report :favorites_added
|
405
408
|
|
406
|
-
# @macro
|
409
|
+
# @macro report_by_day
|
407
410
|
has_report :favorites_removed
|
408
411
|
|
409
|
-
# @macro
|
412
|
+
# @macro report_by_day
|
410
413
|
has_report :average_view_duration
|
411
414
|
|
412
|
-
# @macro
|
415
|
+
# @macro report_by_day
|
413
416
|
has_report :average_view_percentage
|
414
417
|
|
415
|
-
# @macro
|
416
|
-
has_report :estimated_minutes_watched
|
417
|
-
|
418
|
-
# @macro daily_report
|
418
|
+
# @macro report_by_day
|
419
419
|
has_report :annotation_clicks
|
420
420
|
|
421
|
-
# @macro
|
421
|
+
# @macro report_by_day
|
422
422
|
has_report :annotation_click_through_rate
|
423
423
|
|
424
|
-
# @macro
|
424
|
+
# @macro report_by_day
|
425
425
|
has_report :annotation_close_rate
|
426
426
|
|
427
|
-
# @macro
|
427
|
+
# @macro report_by_day
|
428
428
|
has_report :earnings
|
429
429
|
|
430
|
-
# @macro
|
430
|
+
# @macro report_by_day
|
431
431
|
has_report :impressions
|
432
432
|
|
433
|
-
# @macro
|
433
|
+
# @macro report_by_day
|
434
434
|
has_report :monetized_playbacks
|
435
435
|
|
436
436
|
### STATISTICS ###
|
@@ -466,7 +466,6 @@ module Yt
|
|
466
466
|
# player that will play the video.
|
467
467
|
delegate :embed_html, to: :player
|
468
468
|
|
469
|
-
|
470
469
|
### ACTIONS (UPLOAD, UPDATE, DELETE) ###
|
471
470
|
|
472
471
|
has_many :resumable_sessions
|
@@ -602,17 +601,6 @@ module Yt
|
|
602
601
|
|
603
602
|
private
|
604
603
|
|
605
|
-
# Since YouTube API only returns tags on Videos#list, the memoized
|
606
|
-
# `@snippet` is erased if the video was instantiated through Video#search
|
607
|
-
# (e.g., by calling account.videos or channel.videos), so that the full
|
608
|
-
# snippet (with tags and category) is loaded, rather than the partial one.
|
609
|
-
def ensure_complete_snippet(attribute)
|
610
|
-
unless snippet.public_send(attribute).present? || snippet.complete?
|
611
|
-
@snippet = nil
|
612
|
-
end
|
613
|
-
snippet.public_send attribute
|
614
|
-
end
|
615
|
-
|
616
604
|
# TODO: instead of having Video, Playlist etc override this method,
|
617
605
|
# they should define *what* can be updated in their own *update*
|
618
606
|
# method.
|
data/lib/yt/version.rb
CHANGED
data/spec/models/channel_spec.rb
CHANGED
@@ -4,6 +4,113 @@ require 'yt/models/channel'
|
|
4
4
|
describe Yt::Channel do
|
5
5
|
subject(:channel) { Yt::Channel.new attrs }
|
6
6
|
|
7
|
+
describe '#title' do
|
8
|
+
context 'given a snippet with a title' do
|
9
|
+
let(:attrs) { {snippet: {"title"=>"Fullscreen"}} }
|
10
|
+
it { expect(channel.title).to eq 'Fullscreen' }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'given a snippet without a title' do
|
14
|
+
let(:attrs) { {snippet: {}} }
|
15
|
+
it { expect(channel.title).to eq '' }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#description' do
|
20
|
+
context 'given a snippet with a description' do
|
21
|
+
let(:attrs) { {snippet: {"description"=>"A cool channel."}} }
|
22
|
+
it { expect(channel.description).to eq 'A cool channel.' }
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'given a snippet without a description' do
|
26
|
+
let(:attrs) { {snippet: {}} }
|
27
|
+
it { expect(channel.description).to eq '' }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#thumbnail_url' do
|
32
|
+
context 'given a snippet with thumbnails' do
|
33
|
+
let(:attrs) { {snippet: {"thumbnails"=>{
|
34
|
+
"default"=>{"url"=> "http://example.com/88x88.jpg"},
|
35
|
+
"medium"=>{"url"=> "http://example.com/240x240.jpg"},
|
36
|
+
}}} }
|
37
|
+
it { expect(channel.thumbnail_url).to eq 'http://example.com/88x88.jpg' }
|
38
|
+
it { expect(channel.thumbnail_url 'default').to eq 'http://example.com/88x88.jpg' }
|
39
|
+
it { expect(channel.thumbnail_url :default).to eq 'http://example.com/88x88.jpg' }
|
40
|
+
it { expect(channel.thumbnail_url :medium).to eq 'http://example.com/240x240.jpg' }
|
41
|
+
it { expect(channel.thumbnail_url :high).to be_nil }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'given a snippet without thumbnails' do
|
45
|
+
let(:attrs) { {snippet: {}} }
|
46
|
+
it { expect(channel.thumbnail_url).to be_nil }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#published_at' do
|
51
|
+
context 'given a snippet with a timestamp' do
|
52
|
+
let(:attrs) { {snippet: {"publishedAt"=>"2014-04-22T19:14:49.000Z"}} }
|
53
|
+
it { expect(channel.published_at.year).to be 2014 }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#comment_count' do
|
58
|
+
context 'given a video with comments' do
|
59
|
+
let(:attrs) { {statistics: {"commentCount"=>"33"}} }
|
60
|
+
it { expect(channel.comment_count).to be 33 }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#video_count' do
|
65
|
+
context 'given a video with videos' do
|
66
|
+
let(:attrs) { {statistics: {"videoCount"=>"42"}} }
|
67
|
+
it { expect(channel.video_count).to be 42 }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#subscriber_count' do
|
72
|
+
context 'given a video with subscribers' do
|
73
|
+
let(:attrs) { {statistics: {"subscriberCount"=>"12"}} }
|
74
|
+
it { expect(channel.subscriber_count).to be 12 }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#subscriber_count_visible?' do
|
79
|
+
context 'given a video with publicly visible subscribers' do
|
80
|
+
let(:attrs) { {statistics: {"hiddenSubscriberCount"=>false}} }
|
81
|
+
it { expect(channel).to be_subscriber_count_visible }
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'given a video with hidden subscribers' do
|
85
|
+
let(:attrs) { {statistics: {"hiddenSubscriberCount"=>true}} }
|
86
|
+
it { expect(channel).not_to be_subscriber_count_visible }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#content_owner' do
|
91
|
+
context 'given a content_owner_detail with a content owner' do
|
92
|
+
let(:attrs) { {content_owner_details: {"contentOwner"=>"FullScreen"}} }
|
93
|
+
it { expect(channel.content_owner).to eq 'FullScreen' }
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'given a content_owner_detail without a content owner' do
|
97
|
+
let(:attrs) { {content_owner_details: {}} }
|
98
|
+
it { expect(channel.content_owner).to be_nil }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#linked_at' do
|
103
|
+
context 'given a content_owner_detail with a timeLinked' do
|
104
|
+
let(:attrs) { {content_owner_details: {"timeLinked"=>"2014-04-22T19:14:49.000Z"}} }
|
105
|
+
it { expect(channel.linked_at.year).to be 2014 }
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'given a content_owner_detail with a timeLinked' do
|
109
|
+
let(:attrs) { {content_owner_details: {}} }
|
110
|
+
it { expect(channel.linked_at).to be_nil }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
7
114
|
describe '#snippet' do
|
8
115
|
context 'given fetching a channel returns a snippet' do
|
9
116
|
let(:attrs) { {snippet: {"title"=>"Fullscreen"}} }
|
@@ -3,28 +3,4 @@ require 'yt/models/content_owner_detail'
|
|
3
3
|
|
4
4
|
describe Yt::ContentOwnerDetail do
|
5
5
|
subject(:content_owner_detail) { Yt::ContentOwnerDetail.new data: data }
|
6
|
-
|
7
|
-
describe '#content_owner' do
|
8
|
-
context 'given a content_owner_detail with a content owner' do
|
9
|
-
let(:data) { {"contentOwner"=>"FullScreen"} }
|
10
|
-
it { expect(content_owner_detail.content_owner).to eq 'FullScreen' }
|
11
|
-
end
|
12
|
-
|
13
|
-
context 'given a content_owner_detail without a content owner' do
|
14
|
-
let(:data) { {} }
|
15
|
-
it { expect(content_owner_detail.content_owner).to be_nil }
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe '#linked_at' do
|
20
|
-
context 'given a content_owner_detail with a timeLinked' do
|
21
|
-
let(:data) { {"timeLinked"=>"2014-04-22T19:14:49.000Z"} }
|
22
|
-
it { expect(content_owner_detail.linked_at.year).to be 2014 }
|
23
|
-
end
|
24
|
-
|
25
|
-
context 'given a content_owner_detail with a timeLinked' do
|
26
|
-
let(:data) { {} }
|
27
|
-
it { expect(content_owner_detail.linked_at).to be_nil }
|
28
|
-
end
|
29
|
-
end
|
30
6
|
end
|
@@ -2,19 +2,108 @@ require 'spec_helper'
|
|
2
2
|
require 'yt/models/playlist_item'
|
3
3
|
|
4
4
|
describe Yt::PlaylistItem do
|
5
|
-
subject(:
|
5
|
+
subject(:item) { Yt::PlaylistItem.new attrs }
|
6
|
+
|
7
|
+
describe '#title' do
|
8
|
+
context 'given a snippet with a title' do
|
9
|
+
let(:attrs) { {snippet: {"title"=>"Fullscreen"}} }
|
10
|
+
it { expect(item.title).to eq 'Fullscreen' }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'given a snippet without a title' do
|
14
|
+
let(:attrs) { {snippet: {}} }
|
15
|
+
it { expect(item.title).to eq '' }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#published_at' do
|
20
|
+
context 'given a snippet with a timestamp' do
|
21
|
+
let(:attrs) { {snippet: {"publishedAt"=>"2014-04-22T19:14:49.000Z"}} }
|
22
|
+
it { expect(item.published_at.year).to be 2014 }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#description' do
|
27
|
+
context 'given a snippet with a description' do
|
28
|
+
let(:attrs) { {snippet: {"description"=>"A video."}} }
|
29
|
+
it { expect(item.description).to eq 'A video.' }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'given a snippet without a description' do
|
33
|
+
let(:attrs) { {snippet: {}} }
|
34
|
+
it { expect(item.description).to eq '' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#thumbnail_url' do
|
39
|
+
context 'given a snippet with thumbnails' do
|
40
|
+
let(:attrs) { {snippet: {"thumbnails"=>{
|
41
|
+
"default"=>{"url"=> "http://example.com/120x90.jpg"},
|
42
|
+
"medium"=>{"url"=> "http://example.com/320x180.jpg"},
|
43
|
+
}}} }
|
44
|
+
it { expect(item.thumbnail_url).to eq 'http://example.com/120x90.jpg' }
|
45
|
+
it { expect(item.thumbnail_url 'default').to eq 'http://example.com/120x90.jpg' }
|
46
|
+
it { expect(item.thumbnail_url :default).to eq 'http://example.com/120x90.jpg' }
|
47
|
+
it { expect(item.thumbnail_url :medium).to eq 'http://example.com/320x180.jpg' }
|
48
|
+
it { expect(item.thumbnail_url :large).to be_nil }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'given a snippet without thumbnails' do
|
52
|
+
let(:attrs) { {snippet: {}} }
|
53
|
+
it { expect(item.thumbnail_url).to be_nil }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#channel_id' do
|
58
|
+
context 'given a snippet with a channel ID' do
|
59
|
+
let(:attrs) { {snippet: {"channelId"=>"UCxO1tY8h1AhOz0T4ENwmpow"}} }
|
60
|
+
it { expect(item.channel_id).to eq 'UCxO1tY8h1AhOz0T4ENwmpow' }
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'given a snippet without a channel ID' do
|
64
|
+
let(:attrs) { {snippet: {}} }
|
65
|
+
it { expect(item.channel_id).to be_nil }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#channel_title' do
|
70
|
+
context 'given a snippet with a channel title' do
|
71
|
+
let(:attrs) { {snippet: {"channelTitle"=>"Fullscreen"}} }
|
72
|
+
it { expect(item.channel_title).to eq 'Fullscreen' }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'given a snippet without a channel title' do
|
76
|
+
let(:attrs) { {snippet: {}} }
|
77
|
+
it { expect(item.channel_title).to be_nil }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe '#video_id and #video' do
|
82
|
+
context 'given a snippet with a video resource' do
|
83
|
+
let(:attrs) { {snippet: {"resourceId"=>{"kind"=>"youtube#video","videoId"=>"W4GhTprSsOY"}}} }
|
84
|
+
it { expect(item.video_id).to eq 'W4GhTprSsOY' }
|
85
|
+
it { expect(item.video).to be_a Yt::Models::Video }
|
86
|
+
it { expect(item.video.id).to eq 'W4GhTprSsOY' }
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'given a snippet without a video resource' do
|
90
|
+
let(:attrs) { {snippet: {}} }
|
91
|
+
it { expect(item.video_id).to be_nil }
|
92
|
+
it { expect(item.video).to be_nil }
|
93
|
+
end
|
94
|
+
end
|
6
95
|
|
7
96
|
describe '#snippet' do
|
8
97
|
context 'given fetching a playlist item returns a snippet' do
|
9
98
|
let(:attrs) { {snippet: {"position"=>0}} }
|
10
|
-
it { expect(
|
99
|
+
it { expect(item.snippet).to be_a Yt::Snippet }
|
11
100
|
end
|
12
101
|
end
|
13
102
|
|
14
103
|
describe '#status' do
|
15
104
|
context 'given fetching a playlist item returns a status' do
|
16
105
|
let(:attrs) { {status: {"privacyStatus"=>"public"}} }
|
17
|
-
it { expect(
|
106
|
+
it { expect(item.status).to be_a Yt::Status }
|
18
107
|
end
|
19
108
|
end
|
20
109
|
|
@@ -22,10 +111,10 @@ describe Yt::PlaylistItem do
|
|
22
111
|
let(:attrs) { {id: 'playlist-item-id'} }
|
23
112
|
|
24
113
|
context 'given an existing playlist item' do
|
25
|
-
before { expect(
|
114
|
+
before { expect(item).to receive(:do_delete).and_yield }
|
26
115
|
|
27
|
-
it { expect(
|
28
|
-
it { expect{
|
116
|
+
it { expect(item.delete).to be true }
|
117
|
+
it { expect{item.delete}.to change{item.exists?} }
|
29
118
|
end
|
30
119
|
end
|
31
120
|
end
|