yt 0.19.0 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|