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
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