yt 0.11.6 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +13 -2
  4. data/lib/yt/actions/list.rb +11 -1
  5. data/lib/yt/associations/has_attribute.rb +51 -0
  6. data/lib/yt/associations/has_many.rb +0 -15
  7. data/lib/yt/collections/content_owners.rb +0 -4
  8. data/lib/yt/collections/playlist_items.rb +1 -1
  9. data/lib/yt/collections/resources.rb +1 -1
  10. data/lib/yt/collections/subscribed_channels.rb +46 -0
  11. data/lib/yt/collections/subscribers.rb +33 -0
  12. data/lib/yt/collections/subscriptions.rb +0 -23
  13. data/lib/yt/models/account.rb +6 -1
  14. data/lib/yt/models/annotation.rb +4 -10
  15. data/lib/yt/models/asset.rb +3 -10
  16. data/lib/yt/models/base.rb +2 -0
  17. data/lib/yt/models/channel.rb +53 -25
  18. data/lib/yt/models/claim.rb +13 -25
  19. data/lib/yt/models/content_detail.rb +9 -11
  20. data/lib/yt/models/content_owner_detail.rb +2 -8
  21. data/lib/yt/models/device_flow.rb +3 -11
  22. data/lib/yt/models/live_streaming_detail.rb +5 -25
  23. data/lib/yt/models/ownership.rb +8 -8
  24. data/lib/yt/models/playlist.rb +14 -10
  25. data/lib/yt/models/playlist_item.rb +13 -0
  26. data/lib/yt/models/policy.rb +10 -14
  27. data/lib/yt/models/policy_rule.rb +15 -21
  28. data/lib/yt/models/reference.rb +13 -37
  29. data/lib/yt/models/right_owner.rb +6 -17
  30. data/lib/yt/models/snippet.rb +19 -33
  31. data/lib/yt/models/statistics_set.rb +9 -23
  32. data/lib/yt/models/status.rb +20 -25
  33. data/lib/yt/models/subscription.rb +2 -8
  34. data/lib/yt/models/user_info.rb +11 -33
  35. data/lib/yt/version.rb +1 -1
  36. data/spec/collections/subscriptions_spec.rb +0 -7
  37. data/spec/errors/forbidden_spec.rb +10 -0
  38. data/spec/errors/server_error_spec.rb +10 -0
  39. data/spec/models/subscription_spec.rb +0 -9
  40. data/spec/requests/as_account/account_spec.rb +8 -0
  41. data/spec/requests/as_account/channel_spec.rb +40 -6
  42. data/spec/requests/as_account/playlist_item_spec.rb +26 -0
  43. data/spec/requests/as_account/playlist_spec.rb +1 -0
  44. data/spec/requests/as_server_app/channel_spec.rb +9 -0
  45. metadata +9 -2
@@ -2,6 +2,7 @@ require 'yt/actions/delete'
2
2
  require 'yt/actions/update'
3
3
  require 'yt/actions/patch'
4
4
 
5
+ require 'yt/associations/has_attribute'
5
6
  require 'yt/associations/has_authentication'
6
7
  require 'yt/associations/has_many'
7
8
  require 'yt/associations/has_one'
@@ -17,6 +18,7 @@ module Yt
17
18
  include Actions::Update
18
19
  include Actions::Patch
19
20
 
21
+ include Associations::HasAttribute
20
22
  extend Associations::HasReports
21
23
  extend Associations::HasViewerPercentages
22
24
  extend Associations::HasOne
@@ -5,10 +5,6 @@ module Yt
5
5
  # A channel resource contains information about a YouTube channel.
6
6
  # @see https://developers.google.com/youtube/v3/docs/channels
7
7
  class Channel < Resource
8
- # @!attribute [r] subscriptions
9
- # @return [Yt::Collections::Subscriptions] the channel’s subscriptions.
10
- has_many :subscriptions
11
-
12
8
  # @!attribute [r] videos
13
9
  # @return [Yt::Collections::Videos] the channel’s videos.
14
10
  has_many :videos
@@ -53,6 +49,20 @@ module Yt
53
49
  has_one :content_owner_detail
54
50
  delegate :content_owner, :linked_at, to: :content_owner_detail
55
51
 
52
+ # @!attribute [r] subscribed_channels
53
+ # @return [Yt::Collections::SubscribedChannels] the channels that the channel is subscribed to.
54
+ # @raise [Yt::Errors::Forbidden] if the owner of the channel has
55
+ # explicitly select the option to keep all subscriptions private.
56
+ has_many :subscribed_channels
57
+
58
+ # @!attribute [r] subscription
59
+ # @return [Yt::Models::Subscription] the channel’s subscription by auth.
60
+ # @raise [Yt::Errors::NoItems] if {Resource#auth auth} is not
61
+ # subscribed to the channel.
62
+ # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
63
+ # return an authenticated account.
64
+ has_one :subscription
65
+
56
66
  # Returns whether the authenticated account is subscribed to the channel.
57
67
  #
58
68
  # This method requires {Resource#auth auth} to return an
@@ -61,55 +71,64 @@ module Yt
61
71
  # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
62
72
  # return an authenticated account.
63
73
  def subscribed?
64
- subscriptions.any?{|s| s.exists?}
74
+ sleep [(@subscriptions_updated_at || Time.now) - Time.now, 0].max
75
+ subscription.exists?
76
+ rescue Errors::NoItems
77
+ false
65
78
  end
66
79
 
67
- # Subscribes the authenticated account to the channel.
68
- # Does not raise an error if the account was already subscribed.
80
+ # Unsubscribes the authenticated account from the channel.
81
+ # Raises an error if the account was not subscribed.
69
82
  #
70
83
  # This method requires {Resource#auth auth} to return an
71
84
  # authenticated instance of {Yt::Account}.
85
+ # @raise [Yt::Errors::RequestError] if {Resource#auth auth} was not
86
+ # subscribed to the channel.
72
87
  # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
73
88
  # return an authenticated account.
74
- def subscribe
75
- subscriptions.insert ignore_errors: true
89
+ def unsubscribe!
90
+ subscription.delete.tap{ throttle_subscriptions }
76
91
  end
77
92
 
78
- # Subscribes the authenticated account to the channel.
79
- # Raises an error if the account was already subscribed.
93
+ # Unsubscribes the authenticated account from the channel.
94
+ # Does not raise an error if the account was not subscribed.
80
95
  #
81
96
  # This method requires {Resource#auth auth} to return an
82
97
  # authenticated instance of {Yt::Account}.
83
- # @raise [Yt::Errors::RequestError] if {Resource#auth auth} was already
84
- # subscribed to the channel.
85
98
  # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
86
99
  # return an authenticated account.
87
- def subscribe!
88
- subscriptions.insert
100
+ def unsubscribe
101
+ unsubscribe! if subscribed?
89
102
  end
90
103
 
91
- # Unsubscribes the authenticated account from the channel.
92
- # Does not raise an error if the account was not subscribed.
104
+ # Subscribes the authenticated account to the channel.
105
+ # Raises an error if the account was already subscribed.
93
106
  #
94
107
  # This method requires {Resource#auth auth} to return an
95
108
  # authenticated instance of {Yt::Account}.
109
+ # @raise [Yt::Errors::RequestError] if {Resource#auth auth} was already
110
+ # subscribed to the channel.
96
111
  # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
97
112
  # return an authenticated account.
98
- def unsubscribe
99
- subscriptions.delete_all({}, ignore_errors: true)
113
+ def subscribe!
114
+ subscriptions.insert.tap do |subscription|
115
+ throttle_subscriptions
116
+ @subscription = subscription
117
+ end
100
118
  end
101
119
 
102
- # Unsubscribes the authenticated account from the channel.
103
- # Raises an error if the account was not subscribed.
120
+ # Subscribes the authenticated account to the channel.
121
+ # Does not raise an error if the account was already subscribed.
104
122
  #
105
123
  # This method requires {Resource#auth auth} to return an
106
124
  # authenticated instance of {Yt::Account}.
107
- # @raise [Yt::Errors::RequestError] if {Resource#auth auth} was not
108
- # subscribed to the channel.
109
125
  # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
110
126
  # return an authenticated account.
111
- def unsubscribe!
112
- subscriptions.delete_all
127
+ def subscribe
128
+ subscriptions.insert(ignore_errors: true).tap do |subscription|
129
+ throttle_subscriptions
130
+ @subscription = subscription
131
+ end
113
132
  end
114
133
 
115
134
  def create_playlist(params = {})
@@ -150,6 +169,15 @@ module Yt
150
169
  def content_owner_details_params
151
170
  {on_behalf_of_content_owner: auth.owner_name || auth.id}
152
171
  end
172
+
173
+ # @private
174
+ # @note Google API must have some caching layer by which if we try to
175
+ # delete a subscription that we just created, we encounter an error.
176
+ # To overcome this, if we have just updated the subscription, we must
177
+ # wait some time before requesting it again.
178
+ def throttle_subscriptions(seconds = 10)
179
+ @subscriptions_updated_at = Time.now + seconds
180
+ end
153
181
  end
154
182
  end
155
183
  end
@@ -24,21 +24,15 @@ module Yt
24
24
 
25
25
  # @return [String] the ID that YouTube assigns and uses to uniquely
26
26
  # identify the claim.
27
- def id
28
- @id ||= @data['id']
29
- end
27
+ has_attribute :id
30
28
 
31
29
  # @return [String] the unique YouTube asset ID that identifies the asset
32
30
  # associated with the claim.
33
- def asset_id
34
- @asset_id ||= @data["assetId"]
35
- end
31
+ has_attribute :asset_id
36
32
 
37
33
  # @return [String] the unique YouTube video ID that identifies the video
38
34
  # associated with the claim.
39
- def video_id
40
- @video_id ||= @data["videoId"]
41
- end
35
+ has_attribute :video_id
42
36
 
43
37
  # Status
44
38
 
@@ -50,9 +44,7 @@ module Yt
50
44
  # @note When updating a claim, you can update its status from active to
51
45
  # inactive to effectively release the claim, but the API does not
52
46
  # support other updates to a claim’s status.
53
- def status
54
- @status ||= @data["status"]
55
- end
47
+ has_attribute :status
56
48
 
57
49
  # @return [Boolean] whether the claim is active.
58
50
  def active?
@@ -101,9 +93,7 @@ module Yt
101
93
  # @return [String] whether the claim covers the audio, video, or
102
94
  # audiovisual portion of the claimed content. Valid values are: audio,
103
95
  # audiovisual, video.
104
- def content_type
105
- @content_type ||= @data["contentType"]
106
- end
96
+ has_attribute :content_type
107
97
 
108
98
  # @return [Boolean] whether the covers the audio of the content.
109
99
  def audio?
@@ -121,13 +111,11 @@ module Yt
121
111
  end
122
112
 
123
113
  # @return [Time] the date and time that the claim was created.
124
- def created_at
125
- @created_at ||= Time.parse @data["timeCreated"]
126
- end
114
+ has_attribute :created_at, type: Time, from: :time_created
127
115
 
128
116
  # @return [Boolean] whether a third party created the claim.
129
- def third_party?
130
- @third_party_claim ||= @data['thirdPartyClaim'] == true
117
+ has_attribute :third_party?, from: :third_party_claim do |value|
118
+ value == true
131
119
  end
132
120
 
133
121
  # Return whether the video should be blocked where not explicitly owned.
@@ -141,16 +129,16 @@ module Yt
141
129
  # though it will not be monetized in those countries. However, if you
142
130
  # set this property to true, then the video will be monetized in the
143
131
  # United States and Canada and blocked in all other countries.
144
- def block_outside_ownership?
145
- @block_outside_ownership ||= @data["blockOutsideOwnership"]
146
- end
132
+ has_attribute :block_outside_ownership?, from: :block_outside_ownership
147
133
 
148
134
  # @return [String] The unique ID that YouTube uses to identify the
149
135
  # reference that generated the match.
150
- def match_reference_id
151
- @match_reference_id ||= @data.fetch('matchInfo', {})['referenceId']
136
+ has_attribute :match_reference_id, from: :match_info do |match_info|
137
+ (match_info || {})['referenceId']
152
138
  end
153
139
 
140
+ private
141
+
154
142
  # @see https://developers.google.com/youtube/partner/docs/v1/claims/update
155
143
  def patch_params
156
144
  super.tap do |params|
@@ -12,30 +12,28 @@ module Yt
12
12
  end
13
13
 
14
14
  # @return [Integer] the duration of the video (in seconds).
15
- def duration
16
- @duration ||= to_seconds @data.fetch('duration', 0)
15
+ has_attribute :duration, default: 0 do |value|
16
+ to_seconds value
17
17
  end
18
18
 
19
19
  # @return [Boolean] whether the video is available in 3D.
20
- def stereoscopic?
21
- @stereoscopic ||= @data['dimension'] == '3d'
20
+ has_attribute :stereoscopic?, from: :dimension do |dimension|
21
+ dimension == '3d'
22
22
  end
23
23
 
24
24
  # @return [Boolean] whether the video is available in high definition.
25
- def hd?
26
- @hd ||= @data['definition'] == 'hd'
25
+ has_attribute :hd?, from: :definition do |definition|
26
+ definition == 'hd'
27
27
  end
28
28
 
29
29
  # @return [Boolean] whether captions are available for the video.
30
- def captioned?
31
- @hd ||= @data['caption'] == 'true'
30
+ has_attribute :captioned?, from: :caption do |caption|
31
+ caption == 'true'
32
32
  end
33
33
 
34
34
  # @return [Boolean] whether the video represents licensed content, which
35
35
  # means that the content has been claimed by a YouTube content partner.
36
- def licensed?
37
- @licensed ||= @data.fetch 'licensedContent', false
38
- end
36
+ has_attribute :licensed?, default: false, from: :licensed_content
39
37
 
40
38
  private
41
39
 
@@ -21,9 +21,7 @@ module Yt
21
21
  # permissions to administer the channel.
22
22
  # @raise [Yt::Errors::Forbidden] if {Resource#auth auth} does not
23
23
  # return an authenticated content owner.
24
- def content_owner
25
- @content_owner ||= @data['contentOwner']
26
- end
24
+ has_attribute :content_owner
27
25
 
28
26
  # Returns the date and time of when the channel was linked to the content
29
27
  # owner.
@@ -37,11 +35,7 @@ module Yt
37
35
  # permissions to administer the channel.
38
36
  # @raise [Yt::Errors::Forbidden] if {Resource#auth auth} does not
39
37
  # return an authenticated content owner.
40
- def linked_at
41
- @linked_at ||= if @data['timeLinked']
42
- Time.parse @data['timeLinked']
43
- end
44
- end
38
+ has_attribute :linked_at, type: Time, from: :time_linked
45
39
  end
46
40
  end
47
41
  end
@@ -7,17 +7,9 @@ module Yt
7
7
  @data = options[:data]
8
8
  end
9
9
 
10
- def device_code
11
- @device_code ||= @data['device_code']
12
- end
13
-
14
- def user_code
15
- @user_code ||= @data['user_code']
16
- end
17
-
18
- def verification_url
19
- @verification_url ||= @data['verification_url']
20
- end
10
+ has_attribute :device_code, camelize: false
11
+ has_attribute :user_code, camelize: false
12
+ has_attribute :verification_url, camelize: false
21
13
  end
22
14
  end
23
15
  end
@@ -15,47 +15,27 @@ module Yt
15
15
  # @return [Time] if the broadcast has begun, the time that the broadcast
16
16
  # actually started.
17
17
  # @return [nil] if the broadcast has not begun.
18
- def actual_start_time
19
- @actual_start_time ||= if @data['actualStartTime']
20
- Time.parse @data['actualStartTime']
21
- end
22
- end
18
+ has_attribute :actual_start_time, type: Time
23
19
 
24
20
  # @return [Time] if the broadcast is over, the time that the broadcast
25
21
  # actually ended.
26
22
  # @return [nil] if the broadcast is not over.
27
- def actual_end_time
28
- @actual_end_time ||= if @data['actualEndTime']
29
- Time.parse @data['actualEndTime']
30
- end
31
- end
23
+ has_attribute :actual_end_time, type: Time
32
24
 
33
25
  # @return [Time] the time that the broadcast is scheduled to begin.
34
- def scheduled_start_time
35
- @scheduled_start_time ||= if @data['scheduledStartTime']
36
- Time.parse @data['scheduledStartTime']
37
- end
38
- end
26
+ has_attribute :scheduled_start_time, type: Time
39
27
 
40
28
  # @return [Time] if the broadcast is scheduled to end, the time that the
41
29
  # broadcast is scheduled to end.
42
30
  # @return [nil] if the broadcast is scheduled to continue indefinitely.
43
- def scheduled_end_time
44
- @scheduled_end_time ||= if @data['scheduledEndTime']
45
- Time.parse @data['scheduledEndTime']
46
- end
47
- end
31
+ has_attribute :scheduled_end_time, type: Time
48
32
 
49
33
  # @return [Integer] if the broadcast has current viewers and the
50
34
  # broadcast owner has not hidden the viewcount for the video, the
51
35
  # number of viewers currently watching the broadcast.
52
36
  # @return [nil] if the broadcast has ended or the broadcast owner has
53
37
  # hidden the viewcount for the video.
54
- def concurrent_viewers
55
- @concurrent_viewers ||= if @data['concurrentViewers']
56
- @data['concurrentViewers'].to_i
57
- end
58
- end
38
+ has_attribute :concurrent_viewers, type: Integer
59
39
  end
60
40
  end
61
41
  end
@@ -29,26 +29,26 @@ module Yt
29
29
  # General asset ownership is used for all types of assets and is the
30
30
  # only type of ownership data that can be provided for assets that are
31
31
  # not compositions.
32
- def general_owners
33
- @general_owners ||= as_owners @data['general']
32
+ has_attribute :general_owners, from: :general do |data|
33
+ as_owners data
34
34
  end
35
35
 
36
36
  # @return [Array<RightOwner>] a list that identifies owners of the
37
37
  # performance rights for a composition asset.
38
- def performance_owners
39
- @performance_owners ||= as_owners @data['performance']
38
+ has_attribute :performance_owners, from: :performance do |data|
39
+ as_owners data
40
40
  end
41
41
 
42
42
  # @return [Array<RightOwner>] a list that identifies owners of the
43
43
  # synchronization rights for a composition asset.
44
- def synchronization_owners
45
- @synchronization_owners ||= as_owners @data['synchronization']
44
+ has_attribute :synchronization_owners, from: :synchronization do |data|
45
+ as_owners data
46
46
  end
47
47
 
48
48
  # @return [Array<RightOwner>] a list that identifies owners of the
49
49
  # mechanical rights for a composition asset.
50
- def mechanical_owners
51
- @mechanical_owners ||= as_owners @data['mechanical']
50
+ has_attribute :mechanical_owners, from: :mechanical do |data|
51
+ as_owners data
52
52
  end
53
53
 
54
54
  private
@@ -35,20 +35,22 @@ module Yt
35
35
  # @raise [Yt::Errors::Unauthorized] if {Resource#auth auth} does not
36
36
  # return an account with permissions to update the playlist.
37
37
  # @return [Yt::PlaylistItem] the item added to the playlist.
38
- def add_video(video_id)
39
- playlist_items.insert video_params(video_id), ignore_errors: true
38
+ def add_video(video_id, attributes = {})
39
+ playlist_item_params = playlist_item_params(video_id, attributes)
40
+ playlist_items.insert playlist_item_params, ignore_errors: true
40
41
  end
41
42
 
42
- def add_video!(video_id)
43
- playlist_items.insert video_params(video_id)
43
+ def add_video!(video_id, attributes = {})
44
+ playlist_item_params = playlist_item_params(video_id, attributes)
45
+ playlist_items.insert playlist_item_params
44
46
  end
45
47
 
46
- def add_videos(video_ids = [])
47
- video_ids.map{|video_id| add_video video_id}
48
+ def add_videos(video_ids = [], attributes = {})
49
+ video_ids.map{|video_id| add_video video_id, attributes}
48
50
  end
49
51
 
50
- def add_videos!(video_ids = [])
51
- video_ids.map{|video_id| add_video! video_id}
52
+ def add_videos!(video_ids = [], attributes = {})
53
+ video_ids.map{|video_id| add_video! video_id, attributes}
52
54
  end
53
55
 
54
56
  def delete_playlist_items(attrs = {})
@@ -67,8 +69,10 @@ module Yt
67
69
 
68
70
  # @todo: extend camelize to also camelize the nested hashes, so we
69
71
  # don’t have to write videoId
70
- def video_params(video_id)
71
- {resource_id: {kind: 'youtube#video', videoId: video_id}}
72
+ def playlist_item_params(video_id, params = {})
73
+ params.dup.tap do |params|
74
+ params[:resource_id] ||= {kind: 'youtube#video', videoId: video_id}
75
+ end
72
76
  end
73
77
  end
74
78
  end