yt 0.11.6 → 0.12.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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c55e1f1f22fd6b8b4e6fe44a48fc7810ea9d0339
4
- data.tar.gz: 240e018e958c9e5ebe768736da774dfafaaf345f
3
+ metadata.gz: 54ab025a1959cb9cb0ec25849d5936357c1c5bdf
4
+ data.tar.gz: 5cec0556974d50170a889a7a2348962a0cb1754f
5
5
  SHA512:
6
- metadata.gz: d1573d2e0347daa451e4e77912307d27986694d9e2a1839d4c1ac632703f5a2343dc36ea91a83b57f27a5125179048a89fb2df6b8bc91bb2b153e028ae85f77c
7
- data.tar.gz: 250cc4b5a7ac15fa4499347c181b954a73a60620385960da00b83ef8058ab4a60d847ebd134ad5770ea5a1f280e55eddedb9549fbaae7fe11ae687409cf98e43
6
+ metadata.gz: 3427b8b2d9f3cd1b1d1640a0adb1e2946cefc2a254e123bb9127ffd417e5cc13e6e8068ec885e48b8c383b467e0bb510a25b6085d77c8163ac6d926064f39381
7
+ data.tar.gz: 780a2e9ef4ebb816086d3c3ef82992ef0bb94caaeae3f255108e8d7877015539ab2a3d38451bb056f1e3854c6cca9721ffd1eaef492e74a0f95eb0537bc0ec17
@@ -6,6 +6,41 @@ 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.12.1 - unreleased
10
+
11
+ * [ENHANCEMENT] Add `position` option to add_video (to specify where in a playlist to add a video)
12
+ * [FEATURE] Add `update` to PlaylistItem (to change the position of the item in the playlist)
13
+
14
+ ## 0.12.0 - 2014-08-31
15
+
16
+ **How to upgrade**
17
+
18
+ If your code never calls the `delete` method directly on a Subscription
19
+ object (to delete subscriptions by id), then you are good to go.
20
+
21
+ If it does, then be aware that trying to delete an unknown subscription will
22
+ now raise a RequestError, and will not accept `ignore_errors` as an option:
23
+
24
+ account = Yt::Account.new access_token: 'ya29...'
25
+ subscription = Yt::Subscription.new id: '--unknown-id--', auth: account
26
+ # old behavior
27
+ subscription.delete ignore_errors: true # => false
28
+ # new behavior
29
+ subscription.delete # => raises Yt::Errors::RequestError "subscriptionNotFound"
30
+
31
+ Note that the `unsubscribe` and `unsubscribe!` methods of `Channel` have not
32
+ changed, so you can still try to unsubscribe from a channel and not raise an
33
+ error by using the `unsubscribe` method:
34
+
35
+ account = Yt::Account.new access_token: 'ya29...'
36
+ channel = Yt::Channel.new id: 'UC-CHANNEL-ID', auth: account
37
+ channel.unsubscribe # => returns falsey if you were not subscribed
38
+ channel.unsubscribe! # => raises Yt::Errors::RequestError if you were not subscribed
39
+
40
+ * [ENHANCEMENT] Replace `has_many :subscriptions` with `has_one :subscription` in Channel
41
+ * [FEATURE] Add `subscribed_channels` to Channel (list which channels the channel is subscribed to)
42
+ * [FEATURE] Add `subscribers` to Account (list which channels are subscribed to an account)
43
+
9
44
  ## 0.11.6 - 2014-08-28
10
45
 
11
46
  * [BUGFIX] Make Resource.new(url: url).title hit the right endpoint
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.11.6'
44
+ gem 'yt', '~> 0.12.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*)
@@ -61,6 +61,7 @@ Use [Yt::Account](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Acco
61
61
  * access the channel managed by the account
62
62
  * access the videos uploaded by the account
63
63
  * upload a video
64
+ * list the channels subscribed to an account
64
65
 
65
66
  ```ruby
66
67
  # Accounts can be initialized with access token, refresh token or an authorization code
@@ -75,6 +76,10 @@ account.videos.first #=> #<Yt::Models::Video @id=...>
75
76
  account.upload_video 'my_video.mp4', title: 'My new video', privacy_status: 'private'
76
77
  account.upload_video 'http://example.com/remote.m4v', title: 'My other video', tags: ['music']
77
78
 
79
+ account.subscribers.count #=> 2
80
+ account.subscribers.first #=> #<Yt::Models::Channel @id=...>
81
+ account.subscribers.first.title #=> 'Fullscreen'
82
+
78
83
  ```
79
84
 
80
85
  *The methods above require to be authenticated as a YouTube account (see below).*
@@ -134,6 +139,7 @@ Use [Yt::Channel](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/Chan
134
139
  * read the attributes of a channel
135
140
  * access the videos of a channel
136
141
  * access the playlists of a channel
142
+ * access the channels that the channel is subscribed to
137
143
  * subscribe to and unsubscribe from a channel
138
144
  * create and delete playlists from a channel
139
145
  * retrieve the daily earnings, views, comments, likes, dislikes, shares and impressions of a channel
@@ -161,6 +167,9 @@ channel.videos.first #=> #<Yt::Models::Video @id=...>
161
167
 
162
168
  channel.playlists.count #=> 2
163
169
  channel.playlists.first #=> #<Yt::Models::Playlist @id=...>
170
+
171
+ channel.subscribed_channels.count #=> 132
172
+ channel.subscribed_channels.first #=> #<Yt::Models::Channel @id=...>
164
173
  ```
165
174
 
166
175
  *The methods above do not require authentication.*
@@ -371,7 +380,7 @@ playlist.playlist_items.first #=> #<Yt::Models::PlaylistItem @id=...>
371
380
 
372
381
  ```ruby
373
382
  playlist.update title: 'A <title> with angle brackets', description: 'desc', tags: ['new tag'], privacy_status: 'private'
374
- playlist.add_video 'MESycYJytkU'
383
+ playlist.add_video 'MESycYJytkU', position: 2
375
384
  playlist.add_videos ['MESycYJytkU', 'MESycYJytkU']
376
385
  playlist.delete_playlist_items title: 'Fullscreen Creator Platform' #=> [true]
377
386
  ```
@@ -384,6 +393,7 @@ Yt::PlaylistItem
384
393
  Use [Yt::PlaylistItem](http://rubydoc.info/github/Fullscreen/yt/master/Yt/Models/PlaylistItem) to:
385
394
 
386
395
  * read the attributes of a playlist item
396
+ * update the position of an item inside a playlist
387
397
  * delete a playlist item
388
398
 
389
399
  ```ruby
@@ -406,6 +416,7 @@ item.video #=> #<Yt::Models::Video @id=...>
406
416
  *The methods above do not require authentication.*
407
417
 
408
418
  ```ruby
419
+ item.update position: 3 #=> true
409
420
  item.delete #=> true
410
421
  ```
411
422
 
@@ -24,9 +24,19 @@ module Yt
24
24
  end
25
25
  end
26
26
 
27
- # @private
28
27
  # Returns the total number of items that YouTube can provide for the
29
28
  # given request, either all in one page or in consecutive pages.
29
+ #
30
+ # This number comes from the 'totalResults' component of the 'pageInfo'
31
+ # which, accordingly to YouTube documentation, *does not always match
32
+ # the actual number of items in the response*.
33
+ #
34
+ # For instance, when retrieving a list of channels, 'totalResults' might
35
+ # include inactive channels, which are filtered out from the response.
36
+ #
37
+ # The only way to obtain the *real* number of returned items is to
38
+ # iterate through all the pages, which can results in many requests.
39
+ # To avoid this, +total_results+ is provided as a good size estimation.
30
40
  def total_results
31
41
  response = request(list_params).run
32
42
  total_results = response.body.fetch('pageInfo', {})['totalResults']
@@ -0,0 +1,51 @@
1
+ require 'yt/models/timestamp'
2
+
3
+ module Yt
4
+ module Associations
5
+ module HasAttribute
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ private
11
+
12
+ def type_cast(value, type)
13
+ case [type]
14
+ when [Time] then Yt::Timestamp.parse(value) if value
15
+ when [Integer] then value.to_i if value
16
+ when [Float] then value.to_f if value
17
+ when [Symbol] then value.to_sym if value
18
+ when [Hash] then value || {}
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ def has_attribute(attribute, options = {}, &block)
24
+ define_memoized_method(attribute) do
25
+ field = options.fetch(:from, attribute).to_s
26
+ field = field.camelize(:lower) if options.fetch(:camelize, true)
27
+ value = @data.fetch field, options[:default]
28
+ value = type_cast value, options[:type] if options[:type]
29
+ block_given? ? instance_exec(value, &block) : value
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ # A wrapper around Ruby’s +define_method+ that, in addition to adding an
36
+ # instance method called +name+, adds an instance variable called +@name+
37
+ # that stores the result of +name+ the first time is invoked, and returns
38
+ # it every other time. Especially useful if invoking +name+ takes a long
39
+ # time.
40
+ def define_memoized_method(name, &method)
41
+ ivar_name = "@#{name.to_s.gsub /[?!]$/, ''}"
42
+
43
+ define_method name do
44
+ value = instance_variable_get ivar_name
45
+ instance_variable_set ivar_name, value || instance_eval(&method)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -12,25 +12,10 @@ module Yt
12
12
  require 'active_support' # does not load anything by default
13
13
  require 'active_support/core_ext/string/inflections' # for camelize ...
14
14
  require "yt/collections/#{attributes}"
15
-
16
15
  collection_name = attributes.to_s.sub(/.*\./, '').camelize.pluralize
17
16
  collection = "Yt::Collections::#{collection_name}".constantize
18
17
  define_memoized_method(attributes) { collection.of self }
19
18
  end
20
-
21
- private
22
-
23
- # A wrapper around Ruby’s +define_method+ that, in addition to adding an
24
- # instance method called +name+, adds an instance variable called +@name+
25
- # that stores the result of +name+ the first time is invoked, and returns
26
- # it every other time. Especially useful if invoking +name+ takes a long
27
- # time.
28
- def define_memoized_method(name, &method)
29
- define_method name do
30
- ivar = instance_variable_get "@#{name}"
31
- instance_variable_set "@#{name}", ivar || instance_eval(&method)
32
- end
33
- end
34
19
  end
35
20
  end
36
21
  end
@@ -14,10 +14,6 @@ module Yt
14
14
  {owner_name: data['id'], authentication: @auth.authentication}
15
15
  end
16
16
 
17
- def new_item(data)
18
- Yt::ContentOwner.new owner_name: data['id'], authentication: @auth.authentication
19
- end
20
-
21
17
  # @return [Hash] the parameters to submit to YouTube to list content
22
18
  # owners administered by the account.
23
19
  # @see https://developers.google.com/youtube/partner/docs/v1/contentOwners/list
@@ -24,7 +24,7 @@ module Yt
24
24
  end
25
25
 
26
26
  def insert_parts
27
- {snippet: {keys: [:playlist_id, :resource_id]}}
27
+ {snippet: {keys: [:playlist_id, :resource_id, :position]}}
28
28
  end
29
29
  end
30
30
  end
@@ -39,7 +39,7 @@ module Yt
39
39
  def build_insert_body_part(part, attributes = {})
40
40
  {}.tap do |body_part|
41
41
  part[:keys].map do |key|
42
- body_part[camelize key] = attributes[key]
42
+ body_part[camelize key] = attributes[key] if attributes[key]
43
43
  end
44
44
  end
45
45
  end
@@ -0,0 +1,46 @@
1
+ require 'yt/collections/channels'
2
+
3
+ module Yt
4
+ module Collections
5
+ # Provides methods to interact with the list of channels a resource is
6
+ # subscribed to.
7
+ #
8
+ # Resources with subscribed channels are: {Yt::Models::Channel channels}.
9
+ #
10
+ class SubscribedChannels < Channels
11
+
12
+ private
13
+
14
+ def attributes_for_new_item(data)
15
+ snippet = data.fetch 'snippet', {}
16
+ resource = snippet.fetch 'resourceId', {}
17
+ {id: resource['channelId'], snippet: snippet, auth: @auth}
18
+ end
19
+
20
+ # @return [Hash] the parameters to submit to YouTube to list subscribers.
21
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/list
22
+ def list_params
23
+ super.tap{|params| params[:path] = '/youtube/v3/subscriptions'}
24
+ end
25
+
26
+ # @private
27
+ # @note Subscribers overwrites +channel_params+ since the query
28
+ # is slightly different.
29
+ def channels_params
30
+ {}.tap do |params|
31
+ params[:max_results] = 50
32
+ params[:part] = 'snippet'
33
+ params[:channel_id] = @parent.id
34
+ apply_where_params! params
35
+ end
36
+ end
37
+
38
+ # @private
39
+ # @note Subscribers overwrites +list_resources+ since the objects to
40
+ # instatiate belongs to Channel class not Subscriber.
41
+ def resource_class
42
+ Yt::Models::Channel
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ require 'yt/collections/subscribed_channels'
2
+
3
+ module Yt
4
+ module Collections
5
+ # Provides methods to interact with subscribers of a YouTube resource.
6
+ #
7
+ # Resources with subscribers are: {Yt::Models::Account accounts}.
8
+ #
9
+ # Confusingly, YouTube API provides the +same+ endpoint to either
10
+ # retrieve the channels that you are subscribed to or the channels
11
+ # that are subscribed to you. The difference relies in the setting the
12
+ # +mySubscribers+ parameter to true and in reading the information
13
+ # from the +subscriberSnippet+ part, not the +snippet+ part.
14
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/list
15
+ class Subscribers < SubscribedChannels
16
+
17
+ private
18
+
19
+ def attributes_for_new_item(data)
20
+ snippet = data.fetch 'subscriberSnippet', {}
21
+ {id: snippet['channelId'], snippet: snippet, auth: @auth}
22
+ end
23
+
24
+ def channels_params
25
+ {}.tap do |params|
26
+ params[:max_results] = 50
27
+ params[:part] = 'subscriberSnippet'
28
+ params[:my_subscribers] = true
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -6,7 +6,6 @@ module Yt
6
6
  class Subscriptions < Base
7
7
 
8
8
  def insert(options = {})
9
- throttle
10
9
  do_insert
11
10
  rescue Yt::Error => error
12
11
  ignorable_error = error.reasons.include? 'subscriptionDuplicate'
@@ -14,34 +13,12 @@ module Yt
14
13
  raise error unless options[:ignore_errors] && ignorable_error
15
14
  end
16
15
 
17
- def delete_all(params = {}, options = {})
18
- throttle
19
- do_delete_all params, options
20
- end
21
-
22
16
  private
23
17
 
24
18
  def attributes_for_new_item(data)
25
19
  {id: data['id'], auth: @auth}
26
20
  end
27
21
 
28
- # @note Google API must have some caching layer by which if we try to
29
- # delete a subscription that we just created, we encounter an error.
30
- # To overcome this, if we have just updated the subscription, we must
31
- # wait some time before requesting it again.
32
- #
33
- def throttle(seconds = 11)
34
- @last_changed_at ||= Time.now - seconds
35
- wait = [@last_changed_at - Time.now + seconds, 0].max
36
- sleep wait
37
- @last_changed_at = Time.now
38
- end
39
-
40
- def fetch_page(params = {})
41
- throttle
42
- super params
43
- end
44
-
45
22
  # @return [Hash] the parameters to submit to YouTube to list subscriptions.
46
23
  # @see https://developers.google.com/youtube/v3/docs/subscriptions/list
47
24
  def list_params
@@ -9,7 +9,8 @@ module Yt
9
9
  # @!attribute [r] channel
10
10
  # @return [Yt::Models::Channel] the account’s channel.
11
11
  has_one :channel
12
- delegate :playlists, :create_playlist, :delete_playlists, to: :channel
12
+ delegate :playlists, :create_playlist, :delete_playlists,
13
+ :subscribed_channels, to: :channel
13
14
 
14
15
  # @!attribute [r] user_info
15
16
  # @return [Yt::Models::UserInfo] the account’s profile information.
@@ -21,6 +22,10 @@ module Yt
21
22
  # @return [Yt::Collections::Videos] the videos owned by the account.
22
23
  has_many :videos
23
24
 
25
+ # @!attribute [r] subscribers
26
+ # @return [Yt::Collections::Subscribers] the channels subscribed to the account.
27
+ has_many :subscribers
28
+
24
29
  # @return [String] name of the CMS account, if the account is partnered.
25
30
  # @return [nil] if the account is not a partnered content owner.
26
31
  attr_reader :owner_name
@@ -4,7 +4,7 @@ module Yt
4
4
  # @note YouTube API V3 does not provide access to video annotations,
5
5
  # therefore a legacy XML endpoint is used to retrieve annotations.
6
6
  # @see https://www.youtube.com/yt/playbook/annotations.html
7
- class Annotation
7
+ class Annotation < Base
8
8
  # @param [Hash] options the options to initialize an Annotation.
9
9
  # @option options [String] :data The XML representation of an annotation
10
10
  def initialize(options = {})
@@ -77,9 +77,7 @@ module Yt
77
77
  @text ||= @data['TEXT'] || ''
78
78
  end
79
79
 
80
- def type
81
- @type ||= @data.fetch 'type', ''
82
- end
80
+ has_attribute :type, default: ''
83
81
 
84
82
  def link_class
85
83
  @link_class ||= url['link_class']
@@ -93,9 +91,7 @@ module Yt
93
91
  @url ||= action.fetch 'url', {}
94
92
  end
95
93
 
96
- def action
97
- @action ||= @data.fetch 'action', {}
98
- end
94
+ has_attribute :action, default: {}
99
95
 
100
96
  def top
101
97
  @top ||= positions.map{|pos| pos['y'].to_f}.max
@@ -129,9 +125,7 @@ module Yt
129
125
  @region ||= segment.fetch 'movingRegion', {}
130
126
  end
131
127
 
132
- def segment
133
- @segment ||= (@data['segment'] || {})
134
- end
128
+ has_attribute :segment, type: Hash
135
129
  end
136
130
  end
137
131
  end
@@ -42,9 +42,7 @@ module Yt
42
42
 
43
43
  # @return [String] the ID that YouTube assigns and uses to uniquely
44
44
  # identify the asset.
45
- def id
46
- @id ||= @data['id']
47
- end
45
+ has_attribute :id
48
46
 
49
47
  # @return [String] the asset’s type. This value determines the metadata
50
48
  # fields that you can set for the asset. In addition, certain API
@@ -54,9 +52,7 @@ module Yt
54
52
  # Valid values for this property are: art_track_video, composition,
55
53
  # episode, general, movie, music_video, season, show, sound_recording,
56
54
  # video_game, and web.
57
- def type
58
- @type ||= @data['type']
59
- end
55
+ has_attribute :type
60
56
 
61
57
  # Status
62
58
 
@@ -68,9 +64,7 @@ module Yt
68
64
  # the status of an asset, so it’s impossible to update, although the
69
65
  # documentation says this should be the case. If YouTube ever fixes
70
66
  # the API, then the following code can be uncommented.
71
- # def status
72
- # @status ||= @data["status"]
73
- # end
67
+ # has_attribute :status
74
68
  #
75
69
  # # @return [Boolean] whether the asset is active.
76
70
  # def active?
@@ -97,7 +91,6 @@ module Yt
97
91
  params[:params] = {on_behalf_of_content_owner: @auth.owner_name}
98
92
  end
99
93
  end
100
-
101
94
  end
102
95
  end
103
96
  end