yt 0.34.0 → 0.34.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33ad5cd77351556b82e952d8475b978539d34cb7b82731f60b91dc000fd675ce
4
- data.tar.gz: 373db01c5faa0076ea61b9e30f81f7959505ea423026dcc2aba227f8f6aacc13
3
+ metadata.gz: d762b0553bfa8bd58e917e9ff11a1f38958c3b072fe35f0a1e77f19aa48926d0
4
+ data.tar.gz: 2b50a350f9d9c13f067fde5f35e7dfaf0aee824bdf98f1399690c6eddd74a730
5
5
  SHA512:
6
- metadata.gz: 0cb261d97cba17e75a42c49420784917bea7a9f6c257f08f928463db81f34784fce53c844d5744c6e72001bb4a600b94cef6592fd2e8ebc368118c5bcec1cb5b
7
- data.tar.gz: 8af926d7f3341c7f3ffdd1b6a71554470fcd13d6fa7710e666eacf2e9968c76efedb5cdbf603ab792c0f076497fafa8e64364cb2a8355ad6b4475218950069b6
6
+ metadata.gz: 839ac9877b734b3254fd2224091e88d82797621183c5a6b1893d70181fe5fe0391b8ca3a5829b7074a8b694198221968ba2d1151a1d47cc8d7dd67f4fed26943
7
+ data.tar.gz: cec4e6c32178f4de39919271f3875012c9ea56233236029135bc4ea7b1113859871319e90491963e27047cb0f0916e999fdd9d02675fd1c3aa3d93f7917a24d9
data/CHANGELOG.md CHANGED
@@ -6,6 +6,17 @@ 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.34.1 - 2026-01-08
10
+
11
+ * [FEATURE] Add channel.channel_sections
12
+ * [FEATURE] Add branding_setting.unsubscribed_trailer
13
+ * [FEATURE] Add engaged_views
14
+ * [FEATURE] Add account.sub (thank you @jkowens)
15
+ * [FEATURE] Add content_owner.live_cuepoints
16
+ * [FIX] Fetch channel_id from channel handle url
17
+ * [BUGFIX] upload_thumbnail and upload_video with path url (thank you @mrj)
18
+ * [IMPROVEMENT] Use `URI.decode_www_form` for Ruby 4.0 (thank you @jules-w2)
19
+
9
20
  ## 0.34.0 - 2024-08-25
10
21
 
11
22
  * Ruby 2.1+ required https://github.com/nullscreen/yt/pull/425
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014–present Fullscreen, Inc.
1
+ Copyright (c) 2014–present Nullscreen
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
17
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
18
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
19
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -14,7 +14,7 @@ module Yt
14
14
  private
15
15
 
16
16
  def get_request(params = {})
17
- @list_request = Yt::Request.new(params).tap do |request|
17
+ @get_request = Yt::Request.new(params).tap do |request|
18
18
  print "#{request.as_curl}\n" if Yt.configuration.developing?
19
19
  end
20
20
  end
@@ -24,10 +24,10 @@ module Yt
24
24
  params[:method] = :get
25
25
  params[:host] = 'www.googleapis.com'
26
26
  params[:auth] = @auth
27
- params[:exptected_response] = Net::HTTPOK
27
+ params[:expected_response] = Net::HTTPOK
28
28
  params[:api_key] = Yt.configuration.api_key if Yt.configuration.api_key
29
29
  end
30
30
  end
31
31
  end
32
32
  end
33
- end
33
+ end
@@ -39,4 +39,4 @@ module Yt
39
39
  end
40
40
  end
41
41
  end
42
- end
42
+ end
@@ -117,7 +117,7 @@ module Yt
117
117
  params[:host] = 'www.googleapis.com'
118
118
  params[:auth] = @auth
119
119
  params[:path] = path
120
- params[:exptected_response] = Net::HTTPOK
120
+ params[:expected_response] = Net::HTTPOK
121
121
  params[:api_key] = Yt.configuration.api_key if Yt.configuration.api_key
122
122
  end
123
123
  end
@@ -189,7 +189,7 @@ module Yt
189
189
  params[:redirect_uri] = @redirect_uri
190
190
  params[:response_type] = :code
191
191
  params[:access_type] = :offline
192
- params[:approval_prompt] = @force ? :force : :auto
192
+ params[:prompt] = :consent if @force
193
193
  # params[:include_granted_scopes] = true
194
194
  params[:state] = @state if @state
195
195
  end
@@ -0,0 +1,30 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/branding_setting'
3
+
4
+ module Yt
5
+ module Collections
6
+ # @private
7
+ class BrandingSettings < Base
8
+
9
+ private
10
+
11
+ def attributes_for_new_item(data)
12
+ { data: data['brandingSettings'] }
13
+ end
14
+
15
+ # @return [Hash] the parameters to submit to YouTube to get the
16
+ # branding_settings of a resource, for instance a channel.
17
+ # @see https://developers.google.com/youtube/v3/docs/channels#resource
18
+ def list_params
19
+ super.tap do |params|
20
+ params[:path] = "/youtube/v3/#{@parent.kind.pluralize}"
21
+ params[:params] = branding_settings_params
22
+ end
23
+ end
24
+
25
+ def branding_settings_params
26
+ { max_results: 50, part: 'brandingSettings', id: @parent.id }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/channel_section'
3
+
4
+ module Yt
5
+ module Collections
6
+ # Provides methods for a collection of YouTube channel sections.
7
+ #
8
+ # Resources with channel_sections is {Yt::Models::Channel channels}.
9
+ class ChannelSections < Base
10
+
11
+ def attributes_for_new_item(data)
12
+ {}.tap do |attributes|
13
+ attributes[:id] = data['id']
14
+ attributes[:snippet] = data['snippet']
15
+ attributes[:content_details] = data['contentDetails']
16
+ end
17
+ end
18
+
19
+ # @return [Hash] the parameters to submit to YouTube to list channel sections.
20
+ # @see https://developers.google.com/youtube/v3/docs/channelSections/list
21
+ def list_params
22
+ super.tap do |params|
23
+ params[:params] = channel_sections_params
24
+ params[:path] = '/youtube/v3/channelSections'
25
+ end
26
+ end
27
+
28
+ def channel_sections_params
29
+ {}.tap do |params|
30
+ params[:part] = 'snippet'
31
+ params.merge! @parent.channel_sections_params if @parent
32
+ # TODO: when to mine, when to on_behalf_of_content_owner
33
+ # if @parent.auth
34
+ # if @parent.auth.owner_name
35
+ # params[:on_behalf_of_content_owner] = @parent.auth.owner_name
36
+ # else
37
+ # params[:mine] = true
38
+ # end
39
+ # end
40
+ params
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,37 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/live_cuepoint'
3
+
4
+ module Yt
5
+ module Collections
6
+ # Provides methods to insert live cuepoints.
7
+ #
8
+ # Resources with live_cuepoints are: {Yt::Models::ContentOwner content owners}.
9
+ class LiveCuepoints < Base
10
+ def insert(attributes = {})
11
+ underscore_keys! attributes
12
+ body = attributes.slice(*body_params)
13
+ params = {on_behalf_of_content_owner: @auth.owner_name}
14
+ apply_where_params! params
15
+ do_insert(params: params, body: body)
16
+ end
17
+
18
+ private
19
+
20
+ def body_params
21
+ [:id, :cue_type, :insertion_offset_time_ms, :walltime_ms, :duration_secs]
22
+ end
23
+
24
+ # @return [Hash] the parameters to submit to YouTube to add a live cuepoint.
25
+ # @see https://developers.google.com/youtube/v3/live/docs/liveBroadcasts/cuepoint
26
+ def insert_params
27
+ super.tap do |params|
28
+ params[:path] = '/youtube/v3/liveBroadcasts/cuepoint'
29
+ end
30
+ end
31
+
32
+ def attributes_for_new_item(data)
33
+ {data: data, auth: @auth, id: data['id']}
34
+ end
35
+ end
36
+ end
37
+ end
@@ -47,19 +47,28 @@ module Yt
47
47
  playlist_page: 'YT_PLAYLIST_PAGE',
48
48
  campaign_card: 'CAMPAIGN_CARD',
49
49
  end_screen: 'END_SCREEN',
50
- info_card: 'INFO_CARD'
50
+ info_card: 'INFO_CARD',
51
+ hashtags: 'HASHTAGS',
52
+ live_redirect: 'LIVE_REDIRECT',
53
+ product_page: 'PRODUCT_PAGE',
54
+ shorts: 'SHORTS',
55
+ sound_page: 'SOUND_PAGE',
56
+ video_remixes: 'VIDEO_REMIXES',
57
+ immersive_live: 'IMMERSIVE_LIVE',
58
+ shorts_content_links: 'SHORTS_CONTENT_LINKS'
51
59
  }
52
60
 
53
- # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#Playback_Location_Dimensions
61
+ # @see https://developers.google.com/youtube/analytics/dimensions#Playback_Location_Dimensions
54
62
  PLAYBACK_LOCATIONS = {
55
63
  channel: 'CHANNEL',
56
64
  watch: 'WATCH',
57
65
  embedded: 'EMBEDDED',
58
66
  other: 'YT_OTHER',
59
67
  external_app: 'EXTERNAL_APP',
60
- search: 'SEARCH', # undocumented but returned by the API
61
- browse: 'BROWSE', # undocumented but returned by the API
62
- mobile: 'MOBILE' # only present for data < September 10, 2013
68
+ search: 'SEARCH',
69
+ browse: 'BROWSE',
70
+ mobile: 'MOBILE', # only present for data < September 10, 2013
71
+ shorts_feed: 'SHORTS_FEED' # undocumented but returned by the API
63
72
  }
64
73
 
65
74
  # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#Playback_Detail_Dimensions
@@ -203,7 +212,6 @@ module Yt
203
212
  params[:filters] = ((params[:filters] || '').split(';') + ["country==US"]).compact.uniq.join(';') if @dimension == :state && !@state
204
213
  params[:filters] = ((params[:filters] || '').split(';') + ["country==#{@country}"]).compact.uniq.join(';') if @country && !@state
205
214
  params[:filters] = ((params[:filters] || '').split(';') + ["province==US-#{@state}"]).compact.uniq.join(';') if @state
206
- params[:filters] = ((params[:filters] || '').split(';') + ['isCurated==1']).compact.uniq.join(';') if @dimension == :playlist
207
215
  params[:filters] = ((params[:filters] || '').split(';') + ['insightPlaybackLocationType==EMBEDDED']).compact.uniq.join(';') if @dimension == :embedded_player_location
208
216
  params[:filters] = ((params[:filters] || '').split(';') + ['insightTrafficSourceType==RELATED_VIDEO']).compact.uniq.join(';') if @dimension == :related_video
209
217
  params[:filters] = ((params[:filters] || '').split(';') + ['insightTrafficSourceType==YT_SEARCH']).compact.uniq.join(';') if @dimension == :search_term
@@ -1,3 +1,4 @@
1
+ require 'open-uri'
1
2
  require 'yt/models/base'
2
3
 
3
4
  module Yt
@@ -14,6 +15,10 @@ module Yt
14
15
  # @return [String] the (Google+) account’s ID.
15
16
  delegate :id, to: :user_info
16
17
 
18
+ # @!attribute [r] sub
19
+ # @return [String] the (Google) account’s unique ID.
20
+ delegate :sub, to: :user_info
21
+
17
22
  # @!attribute [r] email
18
23
  # @return [String] the account’s email address.
19
24
  delegate :email, to: :user_info
@@ -0,0 +1,29 @@
1
+ require 'yt/models/base'
2
+
3
+ module Yt
4
+ module Models
5
+ # @private
6
+ # Encapsulates branding settings about the resource, such as trailer on channel
7
+ # @see https://developers.google.com/youtube/v3/docs/channels#resource-representation
8
+ class BrandingSetting < Base
9
+ attr_reader :data
10
+
11
+ def initialize(options = {})
12
+ @data = options[:data]
13
+ end
14
+
15
+ has_attribute :channel, default: {}
16
+
17
+ def unsubscribed_trailer
18
+ channel['unsubscribedTrailer']
19
+ end
20
+
21
+ has_attribute :image, default: {}
22
+
23
+ def banner_external_url
24
+ image['bannerExternalUrl']
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -1,4 +1,5 @@
1
1
  require 'yt/models/resource'
2
+ require "fileutils"
2
3
 
3
4
  module Yt
4
5
  module Models
@@ -14,6 +15,38 @@ module Yt
14
15
  delegate :language, to: :snippet
15
16
  delegate :name, to: :snippet
16
17
  delegate :status, to: :snippet
18
+
19
+ # Downloads a caption file.
20
+ # @param [String] path A name for the downloaded file with caption content.
21
+ # @see https://developers.google.com/youtube/v3/docs/captions#resource
22
+ def download(path)
23
+ case io
24
+ when StringIO then File.open(path, 'w') { |f| f.write(io.read) }
25
+ when Tempfile then io.close; FileUtils.mv(io.path, path)
26
+ end
27
+ end
28
+
29
+ def io
30
+ @io ||= get_request(download_params).open_uri
31
+ end
32
+
33
+ private
34
+
35
+ # @return [Hash] the parameters to submit to YouTube to download caption.
36
+ # @see https://developers.google.com/youtube/v3/docs/captions/download
37
+ def download_params
38
+ {}.tap do |params|
39
+ params[:method] = :get
40
+ params[:host] = 'youtube.googleapis.com'
41
+ params[:auth] = @auth
42
+ params[:exptected_response] = Net::HTTPOK
43
+ params[:api_key] = Yt.configuration.api_key if Yt.configuration.api_key
44
+ params[:path] = "/youtube/v3/captions/#{@id}"
45
+ if @auth.owner_name
46
+ params[:params] = {on_behalf_of_content_owner: @auth.owner_name}
47
+ end
48
+ end
49
+ end
17
50
  end
18
51
  end
19
52
  end
@@ -126,6 +126,10 @@ module Yt
126
126
  # explicitly select the option to keep all subscriptions private.
127
127
  has_many :subscribed_channels
128
128
 
129
+ # @!attribute [r] channel_sections
130
+ # @return [Yt::Collections::ChannelSections] the channel’s channel sections.
131
+ has_many :channel_sections
132
+
129
133
  ### ANALYTICS ###
130
134
 
131
135
  # @macro reports
@@ -133,6 +137,9 @@ module Yt
133
137
  # @macro report_by_channel_dimensions
134
138
  has_report :views, Integer
135
139
 
140
+ # @macro report_by_channel_dimensions
141
+ has_report :engaged_views, Integer
142
+
136
143
  # @macro report_by_channel_dimensions
137
144
  has_report :estimated_minutes_watched, Integer
138
145
 
@@ -229,6 +236,18 @@ module Yt
229
236
  statistics_set.hidden_subscriber_count == false
230
237
  end
231
238
 
239
+ ### BRANDING SETTINGS ###
240
+
241
+ has_one :branding_setting
242
+
243
+ # @!attribute [r] unsubscribed_trailer
244
+ # @return [String] the channel’s trailer video id.
245
+ delegate :unsubscribed_trailer, to: :branding_setting
246
+
247
+ # @!attribute [r] banner_external_url
248
+ # @return [String] the channel’s banner image URL.
249
+ delegate :banner_external_url, to: :branding_setting
250
+
232
251
  ### CONTENT OWNER DETAILS ###
233
252
 
234
253
  has_one :content_owner_detail
@@ -286,6 +305,16 @@ module Yt
286
305
  if options[:content_owner_details]
287
306
  @content_owner_detail = ContentOwnerDetail.new data: options[:content_owner_details]
288
307
  end
308
+ if options[:branding_settings]
309
+ @branding_setting = BrandingSetting.new data: options[:branding_settings]
310
+ end
311
+ end
312
+
313
+ # @private
314
+ # Used for `has_many :channel_sections` to return all youtube#channelSection items
315
+ # of the channel.
316
+ def channel_sections_params
317
+ {channel_id: id}
289
318
  end
290
319
 
291
320
  # @private
@@ -0,0 +1,25 @@
1
+ require 'yt/models/base'
2
+
3
+ module Yt
4
+ module Models
5
+ class ChannelSection < Base
6
+ attr_reader :data
7
+
8
+ # @private
9
+ def initialize(options = {})
10
+ @id = options[:id]
11
+ @data = options[:snippet]
12
+ end
13
+
14
+ has_attribute :type
15
+ has_attribute :channel_id
16
+ has_attribute :position, type: Integer
17
+
18
+ ### ID ###
19
+
20
+ # @!attribute [r] id
21
+ # @return [String] the ID that YouTube uses to identify each resource.
22
+ attr_reader :id
23
+ end
24
+ end
25
+ end
@@ -11,6 +11,10 @@ module Yt
11
11
  # @return [Yt::Collections::PartneredChannels] the channels managed by the CMS account.
12
12
  has_many :partnered_channels
13
13
 
14
+ # @!attribute [r] live_cuepoints
15
+ # @return [Yt::Collections::LiveCuepoints] the live_cuepoints inserted by the content owner.
16
+ has_many :live_cuepoints
17
+
14
18
  # @!attribute [r] claims
15
19
  # @return [Yt::Collections::Claims] the claims administered by the content owner.
16
20
  has_many :claims
@@ -13,4 +13,4 @@ module Yt
13
13
  has_attribute :published_at, type: Time
14
14
  end
15
15
  end
16
- end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'yt/models/base'
2
+
3
+ module Yt
4
+ module Models
5
+ # Provides methods to interact with YouTube Content ID live cuepoints.
6
+ # @see https://developers.google.com/youtube/v3/live/docs/liveCuepoints
7
+ class LiveCuepoint < Base
8
+ def initialize(options = {})
9
+ @data = options[:data]
10
+ @id = options[:id]
11
+ @auth = options[:auth]
12
+ end
13
+
14
+ # @return [String] the ID that YouTube assigns and uses to uniquely
15
+ # identify the live_cuepoint.
16
+ has_attribute :id
17
+
18
+ # @return [String] the ID that uniquely identifies the broadcast that the
19
+ # live_cuepoint is associated with.
20
+ has_attribute :broadcast_id
21
+ end
22
+ end
23
+ end
@@ -160,6 +160,9 @@ module Yt
160
160
  # @macro report_by_playlist_dimensions
161
161
  has_report :views, Integer
162
162
 
163
+ # @macro report_by_playlist_dimensions
164
+ has_report :engaged_views, Integer
165
+
163
166
  # @macro report_by_playlist_dimensions
164
167
  has_report :estimated_minutes_watched, Integer
165
168
 
@@ -202,7 +205,7 @@ module Yt
202
205
  else
203
206
  params[:ids] = "channel==#{channel_id}"
204
207
  end
205
- params[:filters] = "playlist==#{id};isCurated==1"
208
+ params[:filters] = "playlist==#{id}"
206
209
  end
207
210
  end
208
211
 
@@ -169,4 +169,4 @@ module Yt
169
169
  end
170
170
  end
171
171
  end
172
- end
172
+ end
@@ -12,13 +12,7 @@ module Yt
12
12
 
13
13
  # @!attribute [r] id
14
14
  # @return [String] the ID that YouTube uses to identify each resource.
15
- def id
16
- if @id.nil? && @match && @match[:kind] == :channel
17
- @id ||= fetch_channel_id
18
- else
19
- @id
20
- end
21
- end
15
+ attr_reader :id
22
16
 
23
17
  ### STATUS ###
24
18
 
@@ -51,7 +45,11 @@ module Yt
51
45
  if options[:url]
52
46
  @url = options[:url]
53
47
  @match = find_pattern_match
54
- @id = @match['id']
48
+ if kind == "channel" && @match.key?('format')
49
+ @id ||= fetch_channel_id
50
+ else
51
+ @id = @match['id']
52
+ end
55
53
  else
56
54
  @id = options[:id]
57
55
  end
@@ -98,7 +96,8 @@ module Yt
98
96
  # @return [Array<Regexp>] patterns matching URLs of YouTube channels.
99
97
  CHANNEL_PATTERNS = [
100
98
  %r{^(?:https?://)?(?:www\.)?youtube\.com/channel/(?<id>UC[a-zA-Z0-9_-]{22})},
101
- %r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>c/|user/)?(?<name>[a-zA-Z0-9_-]+)}
99
+ %r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>c/|user/)?(?<name>[a-zA-Z0-9_-]+)},
100
+ %r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>@)(?<name>[a-zA-Z0-9_-]+)}
102
101
  ]
103
102
 
104
103
  private
@@ -106,8 +105,7 @@ module Yt
106
105
  def find_pattern_match
107
106
  patterns.find do |kind, regex|
108
107
  if data = @url.match(regex)
109
- # Note: With Ruby 2.4, the following is data.named_captures
110
- break data.names.zip(data.captures).to_h.merge kind: kind
108
+ break data.named_captures.merge kind: kind
111
109
  end
112
110
  end || {kind: :unknown}
113
111
  end
@@ -123,19 +121,44 @@ module Yt
123
121
  end
124
122
 
125
123
  def fetch_channel_id
126
- response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
127
- http.request Net::HTTP::Get.new("/#{@match['format']}#{@match['name']}")
128
- end
129
- if response.is_a?(Net::HTTPRedirection)
124
+ api_key = Yt.configuration.api_key if Yt.configuration.api_key
125
+ case @match['format']
126
+ when "@"
127
+ handle = "@#{@match['name']}"
128
+ response = Net::HTTP.start 'youtube.googleapis.com', 443, use_ssl: true do |http|
129
+ http.request Net::HTTP::Get.new("/youtube/v3/channels?part=snippet&forHandle=#{handle}&key=#{api_key}")
130
+ end
131
+ if response.is_a?(Net::HTTPOK) && item = JSON(response.body)['items']&.first
132
+ item['id']
133
+ else
134
+ raise Yt::Errors::NoItems
135
+ end
136
+ when "user/"
137
+ username = @match['name']
138
+ response = Net::HTTP.start 'youtube.googleapis.com', 443, use_ssl: true do |http|
139
+ http.request Net::HTTP::Get.new("/youtube/v3/channels?part=snippet&forUsername=#{username}&key=#{api_key}")
140
+ end
141
+ if response.is_a?(Net::HTTPOK) && item = JSON(response.body)['items']&.first
142
+ item['id']
143
+ else
144
+ raise Yt::Errors::NoItems
145
+ end
146
+ else # "c/", nil
130
147
  response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
131
- http.request Net::HTTP::Get.new(response['location'])
148
+ http.request Net::HTTP::Get.new("/#{@match['format']}#{@match['name']}")
149
+ end
150
+ if response.is_a?(Net::HTTPRedirection)
151
+ response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
152
+ http.request Net::HTTP::Get.new(response['location'])
153
+ end
154
+ end
155
+ # puts response.body
156
+ regex = %r{(?<id>UC[a-zA-Z0-9_-]{22})}
157
+ if data = response.body.match(regex)
158
+ data[:id]
159
+ else
160
+ raise Yt::Errors::NoItems
132
161
  end
133
- end
134
- regex = %r{<meta itemprop="channelId" content="(?<id>UC[a-zA-Z0-9_-]{22})">}
135
- if data = response.body.match(regex)
136
- data[:id]
137
- else
138
- raise Yt::Errors::NoItems
139
162
  end
140
163
  end
141
164
 
@@ -1,4 +1,3 @@
1
- require 'cgi'
2
1
  require 'yt/models/base'
3
2
 
4
3
  module Yt
@@ -30,7 +29,7 @@ module Yt
30
29
  private
31
30
 
32
31
  def session_params
33
- CGI::parse(@uri.query).tap{|hash| hash.each{|k,v| hash[k] = v.first}}
32
+ URI.decode_www_form(@uri.query || "").to_h
34
33
  end
35
34
 
36
35
  # @note: YouTube documentation states that a valid upload returns an HTTP
@@ -11,6 +11,7 @@ module Yt
11
11
  end
12
12
 
13
13
  has_attribute :id, default: ''
14
+ has_attribute :sub, default: ''
14
15
  has_attribute :email, default: ''
15
16
  has_attribute :verified_email, default: false, camelize: false
16
17
  has_attribute :name, default: ''
@@ -1,3 +1,4 @@
1
+ require 'open-uri'
1
2
  require 'yt/models/resource'
2
3
 
3
4
  module Yt
@@ -421,6 +422,9 @@ module Yt
421
422
  # @macro report_by_video_dimensions
422
423
  has_report :views, Integer
423
424
 
425
+ # @macro report_by_video_dimensions
426
+ has_report :engaged_views, Integer
427
+
424
428
  # @macro report_by_video_dimensions
425
429
  has_report :estimated_minutes_watched, Integer
426
430
 
@@ -546,7 +550,7 @@ module Yt
546
550
  # @raise [Yt::Errors::RequestError] if path_or_url is not a valid path
547
551
  # or URL.
548
552
  def upload_thumbnail(path_or_url)
549
- file = URI.open(path_or_url) rescue StringIO.new
553
+ file = URI.open(path_or_url)
550
554
  session = resumable_sessions.insert file.size
551
555
 
552
556
  session.update(body: file) do |data|
@@ -37,6 +37,9 @@ module Yt
37
37
  # @macro report_by_video_dimensions
38
38
  has_report :views, Integer
39
39
 
40
+ # @macro report_by_video_dimensions
41
+ has_report :engaged_views, Integer
42
+
40
43
  # @macro report_by_video_dimensions
41
44
  has_report :estimated_minutes_watched, Integer
42
45
 
data/lib/yt/request.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'net/http' # for Net::HTTP.start
2
2
  require 'uri' # for URI.json
3
3
  require 'json' # for JSON.parse
4
+ require "open-uri" # for URI.open
4
5
  require 'active_support' # does not load anything by default, but is required
5
6
  require 'active_support/core_ext' # for Hash.from_xml, Hash.to_param
6
7
 
@@ -84,6 +85,10 @@ module Yt
84
85
  end
85
86
  end
86
87
 
88
+ def open_uri
89
+ URI.open(uri.to_s, 'Authorization' => "Bearer #{@auth.access_token}")
90
+ end
91
+
87
92
  # Returns the +cURL+ version of the request, useful to re-run the request
88
93
  # in a shell terminal.
89
94
  # @return [String] the +cURL+ version of the request.
data/lib/yt/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yt
2
- VERSION = '0.34.0'
2
+ VERSION = '0.34.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.34.0
4
+ version: 0.34.1
5
5
  platform: ruby
6
6
  authors:
7
- - Claudio Baccigalupo
7
+ - Nullscreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-10 00:00:00.000000000 Z
11
+ date: 2026-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rspec
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -52,34 +38,6 @@ dependencies:
52
38
  - - ">="
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rake
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: pry
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
41
  - !ruby/object:Gem::Dependency
84
42
  name: vcr
85
43
  requirement: !ruby/object:Gem::Requirement
@@ -110,27 +68,16 @@ dependencies:
110
68
  version: '0'
111
69
  description: Youtube V3 API client.
112
70
  email:
113
- - claudio@fullscreen.net
114
- executables:
115
- - console
116
- - yt
71
+ - nullscreen.code@gmail.com
72
+ executables: []
117
73
  extensions: []
118
74
  extra_rdoc_files: []
119
75
  files:
120
- - ".gitignore"
121
- - ".rspec"
122
- - ".travis.yml"
123
76
  - ".yardopts"
124
77
  - CHANGELOG.md
125
- - Gemfile
126
78
  - MIT-LICENSE
127
79
  - README.md
128
- - Rakefile
129
80
  - YOUTUBE_IT.md
130
- - bin/console
131
- - bin/yt
132
- - gemfiles/Gemfile.activesupport-3.x
133
- - gemfiles/Gemfile.activesupport-4.x
134
81
  - lib/yt.rb
135
82
  - lib/yt/actions/base.rb
136
83
  - lib/yt/actions/delete.rb
@@ -151,9 +98,11 @@ files:
151
98
  - lib/yt/collections/assets.rb
152
99
  - lib/yt/collections/authentications.rb
153
100
  - lib/yt/collections/base.rb
101
+ - lib/yt/collections/branding_settings.rb
154
102
  - lib/yt/collections/bulk_report_jobs.rb
155
103
  - lib/yt/collections/bulk_reports.rb
156
104
  - lib/yt/collections/captions.rb
105
+ - lib/yt/collections/channel_sections.rb
157
106
  - lib/yt/collections/channels.rb
158
107
  - lib/yt/collections/claim_histories.rb
159
108
  - lib/yt/collections/claims.rb
@@ -165,6 +114,7 @@ files:
165
114
  - lib/yt/collections/file_details.rb
166
115
  - lib/yt/collections/group_infos.rb
167
116
  - lib/yt/collections/group_items.rb
117
+ - lib/yt/collections/live_cuepoints.rb
168
118
  - lib/yt/collections/live_streaming_details.rb
169
119
  - lib/yt/collections/ownerships.rb
170
120
  - lib/yt/collections/partnered_channels.rb
@@ -205,10 +155,12 @@ files:
205
155
  - lib/yt/models/asset_snippet.rb
206
156
  - lib/yt/models/authentication.rb
207
157
  - lib/yt/models/base.rb
158
+ - lib/yt/models/branding_setting.rb
208
159
  - lib/yt/models/bulk_report.rb
209
160
  - lib/yt/models/bulk_report_job.rb
210
161
  - lib/yt/models/caption.rb
211
162
  - lib/yt/models/channel.rb
163
+ - lib/yt/models/channel_section.rb
212
164
  - lib/yt/models/claim.rb
213
165
  - lib/yt/models/claim_event.rb
214
166
  - lib/yt/models/claim_history.rb
@@ -223,6 +175,7 @@ files:
223
175
  - lib/yt/models/group_info.rb
224
176
  - lib/yt/models/group_item.rb
225
177
  - lib/yt/models/id.rb
178
+ - lib/yt/models/live_cuepoint.rb
226
179
  - lib/yt/models/live_streaming_detail.rb
227
180
  - lib/yt/models/match_policy.rb
228
181
  - lib/yt/models/ownership.rb
@@ -249,8 +202,7 @@ files:
249
202
  - lib/yt/models/video_group.rb
250
203
  - lib/yt/request.rb
251
204
  - lib/yt/version.rb
252
- - yt.gemspec
253
- homepage: http://github.com/Fullscreen/yt
205
+ homepage: http://github.com/nullscreen/yt
254
206
  licenses:
255
207
  - MIT
256
208
  metadata: {}
@@ -269,7 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
269
221
  - !ruby/object:Gem::Version
270
222
  version: '0'
271
223
  requirements: []
272
- rubygems_version: 3.5.22
224
+ rubygems_version: 3.4.20
273
225
  signing_key:
274
226
  specification_version: 4
275
227
  summary: Yt makes it easy to interact with Youtube V3 API by providing a modular,
data/.gitignore DELETED
@@ -1,27 +0,0 @@
1
- # See http://help.github.com/ignore-files/ for more about ignoring files.
2
- #
3
- # If you find yourself ignoring temporary files generated by your text editor
4
- # or operating system, you probably want to add a global ignore instead:
5
- # git config --global core.excludesfile '~/.gitignore_global'
6
-
7
- # Ignore bundler config.
8
- /.bundle
9
-
10
- # Ignore the default SQLite database.
11
- /db/*.sqlite3
12
- /db/*.sqlite3-journal
13
-
14
- coverage/
15
- Gemfile.lock
16
-
17
- # Ignore all logfiles and tempfiles.
18
- /log/*.log
19
- /tmp
20
- **/tmp
21
- *.gem
22
- *.sqlite3
23
-
24
- doc/
25
- .yardoc/
26
- _site/
27
- TODO.md
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --color
2
- --exclude_pattern spec/requests/as_content_owner/*_spec.rb
3
- --tag=~slow
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- language: ruby
2
- notifications:
3
- email: true
4
- matrix:
5
- include:
6
- - rvm: 2.6.3
7
- gemfile: gemfiles/Gemfile.activesupport-4.x
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in yt.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,11 +0,0 @@
1
- require "bundler"
2
- Bundler.setup
3
- Bundler::GemHelper.install_tasks
4
-
5
- require "rspec/core/rake_task"
6
- require "rspec/core/version"
7
-
8
- desc "Run all examples"
9
- RSpec::Core::RakeTask.new :spec
10
-
11
- task default: [:spec]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "yt"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- require "pry"
12
- Pry.start
13
-
14
- # require "irb"
15
- # IRB.start(__FILE__)
data/bin/yt DELETED
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- begin
4
- require 'yt'
5
- rescue LoadError
6
- require 'rubygems'
7
- require 'yt'
8
- end
9
-
10
- Yt.configuration.log_level = :debug
11
- id = ARGV[1] || 'rdwz7QiG0lk'
12
-
13
- case ARGV[0]
14
- when 'info'
15
- puts "Yt version #{Yt::VERSION}"
16
- video = Yt::Video.new id: id
17
- puts video.title
18
- when 'video'
19
- video = Yt::Video.new id: id
20
- views = "#{video.view_count} #{'view'.pluralize video.view_count}"
21
- likes = "#{video.like_count} #{'like'.pluralize video.like_count}"
22
- puts "'#{video.title}' by #{video.channel_title} has #{views} and #{likes}"
23
- end
@@ -1,4 +0,0 @@
1
- source 'http://rubygems.org'
2
-
3
- gem 'activesupport', '~> 3.0'
4
- gemspec path: '../'
@@ -1,4 +0,0 @@
1
- source 'http://rubygems.org'
2
-
3
- gem 'activesupport', '~> 4.0'
4
- gemspec path: '../'
data/yt.gemspec DELETED
@@ -1,36 +0,0 @@
1
- lib = File.expand_path('../lib', __FILE__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'yt/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "yt"
7
- spec.version = Yt::VERSION
8
- spec.authors = ["Claudio Baccigalupo"]
9
- spec.email = ["claudio@fullscreen.net"]
10
- spec.description = %q{Youtube V3 API client.}
11
- spec.summary = %q{Yt makes it easy to interact with Youtube V3 API by
12
- providing a modular, intuitive and tested Ruby-style API.}
13
- spec.homepage = "http://github.com/Fullscreen/yt"
14
- spec.license = "MIT"
15
-
16
- spec.required_ruby_version = '>= 2.1'
17
-
18
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
- end
21
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
22
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
23
- spec.require_paths = ["lib"]
24
-
25
- spec.add_dependency 'activesupport'
26
-
27
- # For development / Code coverage / Documentation
28
- spec.add_development_dependency 'bundler'
29
- spec.add_development_dependency 'rspec'
30
- spec.add_development_dependency 'rake'
31
- # spec.add_development_dependency 'yard'
32
- # spec.add_development_dependency 'coveralls'
33
- spec.add_development_dependency 'pry'
34
- spec.add_development_dependency 'vcr'
35
- spec.add_development_dependency 'webmock'
36
- end