yt 0.32.6 → 0.33.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.
- checksums.yaml +4 -4
- data/.rspec +1 -4
- data/CHANGELOG.md +19 -0
- data/README.md +22 -32
- data/YOUTUBE_IT.md +4 -4
- data/lib/yt.rb +0 -1
- data/lib/yt/associations/has_reports.rb +9 -14
- data/lib/yt/collections/reports.rb +5 -7
- data/lib/yt/models/resource.rb +69 -3
- data/lib/yt/models/url.rb +2 -60
- data/lib/yt/request.rb +6 -2
- data/lib/yt/version.rb +1 -1
- data/yt.gemspec +5 -2
- metadata +31 -169
- data/spec/collections/claims_spec.rb +0 -62
- data/spec/collections/comment_threads_spec.rb +0 -46
- data/spec/collections/playlist_items_spec.rb +0 -44
- data/spec/collections/playlists_spec.rb +0 -27
- data/spec/collections/policies_spec.rb +0 -30
- data/spec/collections/references_spec.rb +0 -30
- data/spec/collections/reports_spec.rb +0 -30
- data/spec/collections/subscriptions_spec.rb +0 -25
- data/spec/collections/videos_spec.rb +0 -43
- data/spec/constants/geography_spec.rb +0 -16
- data/spec/errors/forbidden_spec.rb +0 -10
- data/spec/errors/missing_auth_spec.rb +0 -24
- data/spec/errors/no_items_spec.rb +0 -10
- data/spec/errors/request_error_spec.rb +0 -44
- data/spec/errors/server_error_spec.rb +0 -10
- data/spec/errors/unauthorized_spec.rb +0 -10
- data/spec/models/account_spec.rb +0 -138
- data/spec/models/annotation_spec.rb +0 -180
- data/spec/models/asset_spec.rb +0 -32
- data/spec/models/channel_spec.rb +0 -127
- data/spec/models/claim_event_spec.rb +0 -62
- data/spec/models/claim_history_spec.rb +0 -27
- data/spec/models/claim_spec.rb +0 -223
- data/spec/models/comment_spec.rb +0 -40
- data/spec/models/comment_thread_spec.rb +0 -93
- data/spec/models/configuration_spec.rb +0 -44
- data/spec/models/content_detail_spec.rb +0 -52
- data/spec/models/content_owner_detail_spec.rb +0 -6
- data/spec/models/file_detail_spec.rb +0 -13
- data/spec/models/live_streaming_detail_spec.rb +0 -6
- data/spec/models/ownership_spec.rb +0 -59
- data/spec/models/player_spec.rb +0 -13
- data/spec/models/playlist_item_spec.rb +0 -120
- data/spec/models/playlist_spec.rb +0 -138
- data/spec/models/policy_rule_spec.rb +0 -63
- data/spec/models/policy_spec.rb +0 -41
- data/spec/models/rating_spec.rb +0 -12
- data/spec/models/reference_spec.rb +0 -249
- data/spec/models/request_spec.rb +0 -204
- data/spec/models/resource_spec.rb +0 -42
- data/spec/models/right_owner_spec.rb +0 -71
- data/spec/models/snippet_spec.rb +0 -13
- data/spec/models/statistics_set_spec.rb +0 -13
- data/spec/models/status_spec.rb +0 -13
- data/spec/models/subscription_spec.rb +0 -30
- data/spec/models/url_spec.rb +0 -78
- data/spec/models/video_category_spec.rb +0 -21
- data/spec/models/video_spec.rb +0 -669
- data/spec/requests/as_account/account_spec.rb +0 -143
- data/spec/requests/as_account/authentications_spec.rb +0 -127
- data/spec/requests/as_account/channel_spec.rb +0 -246
- data/spec/requests/as_account/channels_spec.rb +0 -18
- data/spec/requests/as_account/playlist_item_spec.rb +0 -55
- data/spec/requests/as_account/playlist_spec.rb +0 -218
- data/spec/requests/as_account/thumbnail.jpg +0 -0
- data/spec/requests/as_account/video.mp4 +0 -0
- data/spec/requests/as_account/video_spec.rb +0 -408
- data/spec/requests/as_content_owner/account_spec.rb +0 -29
- data/spec/requests/as_content_owner/advertising_options_set_spec.rb +0 -15
- data/spec/requests/as_content_owner/asset_spec.rb +0 -31
- data/spec/requests/as_content_owner/bulk_report_job_spec.rb +0 -19
- data/spec/requests/as_content_owner/channel_spec.rb +0 -1836
- data/spec/requests/as_content_owner/claim_history_spec.rb +0 -20
- data/spec/requests/as_content_owner/claim_spec.rb +0 -17
- data/spec/requests/as_content_owner/content_owner_spec.rb +0 -370
- data/spec/requests/as_content_owner/match_policy_spec.rb +0 -17
- data/spec/requests/as_content_owner/ownership_spec.rb +0 -25
- data/spec/requests/as_content_owner/playlist_spec.rb +0 -767
- data/spec/requests/as_content_owner/video_group_spec.rb +0 -112
- data/spec/requests/as_content_owner/video_spec.rb +0 -1223
- data/spec/requests/as_server_app/channel_spec.rb +0 -54
- data/spec/requests/as_server_app/comment_spec.rb +0 -22
- data/spec/requests/as_server_app/comment_thread_spec.rb +0 -27
- data/spec/requests/as_server_app/comment_threads_spec.rb +0 -41
- data/spec/requests/as_server_app/playlist_item_spec.rb +0 -30
- data/spec/requests/as_server_app/playlist_spec.rb +0 -33
- data/spec/requests/as_server_app/url_spec.rb +0 -94
- data/spec/requests/as_server_app/video_spec.rb +0 -60
- data/spec/requests/as_server_app/videos_spec.rb +0 -40
- data/spec/requests/unauthenticated/video_spec.rb +0 -14
- data/spec/spec_helper.rb +0 -20
- data/spec/support/fail_matcher.rb +0 -15
- data/spec/support/global_hooks.rb +0 -48
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f3d794d74e724d49a7a96cf3d27dcfbeabcc0c42a7a9766deaae7e01be367c0
|
|
4
|
+
data.tar.gz: bf836aa2984cb6c6903881d56a6f60aad68504a5cb7013c276e6466ee075cf71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74662b9a898592295377a2e3bd51295682a67d07c773abc27945706c858860e74ce4ce4c77ca20455a674a746090603ef49261bce21b40418984430f93f5d18c
|
|
7
|
+
data.tar.gz: 74f82bc61b9bde908766aa6c1e19afe7da9fbeb42faeead0a37d2cc31def646b71f333c0e3dde43589027b28cf3e781d04d1824b58be7db99af3b31914debc0d
|
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ 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.33.0 - 2020-04-10
|
|
10
|
+
|
|
11
|
+
If your code calls reports methods such as `views`, `likes`, or `reports`,
|
|
12
|
+
do not include `by: :week` option since `7DayTotals` dimension will no longer be
|
|
13
|
+
supported by YouTube API as of [April 15, 2020](https://developers.google.com/youtube/analytics/revision_history#october-15,-2019).
|
|
14
|
+
|
|
15
|
+
Use `by: :day` option instead and add up the numbers from each day.
|
|
16
|
+
|
|
17
|
+
If you keep using `by: :week` option after this change it will raise an error
|
|
18
|
+
(before the gem upgrade) or it will run with `day` dimension instead (after
|
|
19
|
+
the gem upgrade, like any other random input).
|
|
20
|
+
|
|
21
|
+
* [REMOVAL] Remove `by: :week` option for reports.
|
|
22
|
+
* [FEATURE] Add back the option of initializing a resource by its URL.
|
|
23
|
+
* [BUGFIX] Limit retries on refreshing tokens
|
|
24
|
+
|
|
25
|
+
**Breaking change**
|
|
26
|
+
|
|
27
|
+
If your code is using constant `Yt::URL::CHANNEL_PATTERNS` etc then it's moved to `Yt::Resource::CHANNEL_PATTERNS`, `Yt::Resource::VIDEO_PATTERNS`, and `Yt::Resource::PLAYLIST_PATTERNS`.
|
|
9
28
|
|
|
10
29
|
## 0.32.6 - 2020-02-07
|
|
11
30
|
|
data/README.md
CHANGED
|
@@ -526,44 +526,34 @@ If a variable is set in both places, then `Yt.configure` takes precedence.
|
|
|
526
526
|
How to test
|
|
527
527
|
===========
|
|
528
528
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
1. tests in `spec/models`, `spec/collections` and `spec/errors` **do not hit** the YouTube API
|
|
532
|
-
1. tests in `spec/requests` **hit** the YouTube API and require authentication
|
|
533
|
-
|
|
534
|
-
To only run tests against models, collections and errors (which do not hit the API), type:
|
|
529
|
+
To run tests:
|
|
535
530
|
|
|
536
531
|
```bash
|
|
537
|
-
rspec
|
|
532
|
+
rspec
|
|
538
533
|
```
|
|
539
534
|
|
|
540
|
-
|
|
535
|
+
We recommend RSpec >= 3.8.
|
|
541
536
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
537
|
+
Yt comes with two different sets of tests:
|
|
538
|
+
|
|
539
|
+
1. Unit tests in `spec/models`, `spec/collections` and `spec/errors`
|
|
540
|
+
2. Legacy integration tests in `spec/requests`
|
|
541
|
+
|
|
542
|
+
Coming soon will be a new set of high-level integration tests.
|
|
543
|
+
|
|
544
|
+
Integration tests are recorded with VCR. Some of the tests refer to
|
|
545
|
+
fixture data that an arbitrary account may not have access to. If you
|
|
546
|
+
need to modify one of these tests or re-record the cassette, we'd
|
|
547
|
+
suggest working against your own version of the testing setup. Then in
|
|
548
|
+
your pull request, we can help canonize your test/fixtures.
|
|
549
|
+
|
|
550
|
+
Some of the integration tests require authentication. These can be set
|
|
551
|
+
with the following environment variables:
|
|
545
552
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
* Account-based tests, which require a valid refresh token along with
|
|
552
|
-
the application-level credentials the refresh token was created with
|
|
553
|
-
(`YT_TEST_DEVICE_REFRESH_TOKEN`, `YT_TEST_DEVICE_CLIENT_ID`, and
|
|
554
|
-
`YT_TEST_DEVICE_CLIENT_SECRET` respectively).
|
|
555
|
-
* Server application tests, which use a server API key
|
|
556
|
-
(`YT_TEST_SERVER_API_KEY`).
|
|
557
|
-
* Tests that excercise YouTube's partner functionality. This requires an
|
|
558
|
-
a partner channel id (`YT_TEST_CONTENT_OWNER_NAME`), a refresh token
|
|
559
|
-
that's authenticated with that channel
|
|
560
|
-
(`YT_TEST_CONTENT_OWNER_REFRESH_TOKEN`), and the corresponding
|
|
561
|
-
application (`YT_TEST_PARTNER_CLIENT_ID` and
|
|
562
|
-
(`YT_TEST_PARTNER_CLIENT_SECRET`).
|
|
563
|
-
|
|
564
|
-
The refresh tokens need to be generated with the `youtube`,
|
|
565
|
-
`yt-analytics` and `userinfo.profile` permissions in order for tests to
|
|
566
|
-
pass.
|
|
553
|
+
* `YT_TEST_CLIENT_ID`
|
|
554
|
+
* `YT_TEST_CLIENT_SECRET`
|
|
555
|
+
* `YT_TEST_API_KEY`
|
|
556
|
+
* `YT_TEST_REFRESH_TOKEN`
|
|
567
557
|
|
|
568
558
|
How to release new versions
|
|
569
559
|
===========================
|
data/YOUTUBE_IT.md
CHANGED
|
@@ -141,7 +141,7 @@ client = YouTubeIt::Client.new
|
|
|
141
141
|
client.videos_by(:query => "penguin", :author => "liz")
|
|
142
142
|
# with yt: the 'author' filter was removed from YouTube API V3, so the
|
|
143
143
|
# request must be done using the channel of the requested author
|
|
144
|
-
channel = Yt::Channel.new
|
|
144
|
+
channel = Yt::Channel.new url: 'youtube.com/liz'
|
|
145
145
|
channel.videos.where(q: 'penguin')
|
|
146
146
|
```
|
|
147
147
|
|
|
@@ -176,7 +176,7 @@ client = YouTubeIt::Client.new
|
|
|
176
176
|
client.videos_by(:user => 'liz')
|
|
177
177
|
# with yt: the 'author' filter was removed from YouTube API V3, so the
|
|
178
178
|
# request must be done using the channel of the requested author
|
|
179
|
-
channel = Yt::Channel.new
|
|
179
|
+
channel = Yt::Channel.new url: 'youtube.com/liz'
|
|
180
180
|
channel.videos.where(q: 'penguin')
|
|
181
181
|
```
|
|
182
182
|
|
|
@@ -188,7 +188,7 @@ client = YouTubeIt::Client.new
|
|
|
188
188
|
client.videos_by(:favorites, :user => 'liz')
|
|
189
189
|
# with yt: note that only *old* channels have a "Favorites" playlist, since
|
|
190
190
|
# "Favorites" has been deprecated by YouTube in favor of "Liked Videos".
|
|
191
|
-
channel = Yt::Channel.new
|
|
191
|
+
channel = Yt::Channel.new url: 'youtube.com/liz'
|
|
192
192
|
channel.related_playlists.find{|p| p.title == 'Favorites'}
|
|
193
193
|
```
|
|
194
194
|
|
|
@@ -832,4 +832,4 @@ TODO
|
|
|
832
832
|
|
|
833
833
|
$ client.activity(user) #default current user
|
|
834
834
|
|
|
835
|
-
-->
|
|
835
|
+
-->
|
data/lib/yt.rb
CHANGED
|
@@ -13,7 +13,6 @@ require 'yt/models/video_group'
|
|
|
13
13
|
require 'yt/models/comment_thread'
|
|
14
14
|
require 'yt/models/ownership'
|
|
15
15
|
require 'yt/models/advertising_options_set'
|
|
16
|
-
require 'yt/models/url'
|
|
17
16
|
|
|
18
17
|
# An object-oriented Ruby client for YouTube.
|
|
19
18
|
# Helps creating applications that need to interact with YouTube objects.
|
|
@@ -10,7 +10,7 @@ module Yt
|
|
|
10
10
|
# @option options [Array<Symbol>] :only The metrics to generate reports
|
|
11
11
|
# for.
|
|
12
12
|
# @option options [Symbol] :by (:day) The dimension to collect metrics
|
|
13
|
-
# by. Accepted values are: +:day+, +:
|
|
13
|
+
# by. Accepted values are: +:day+, +:month+.
|
|
14
14
|
# @option options [#to_date] :since The first day of the time-range.
|
|
15
15
|
# Also aliased as +:from+.
|
|
16
16
|
# @option options [#to_date] :until The last day of the time-range.
|
|
@@ -40,11 +40,6 @@ module Yt
|
|
|
40
40
|
# @example Get the $1 for this and last month:
|
|
41
41
|
# resource.$1 since: 1.month.ago, by: :month
|
|
42
42
|
# # => {Wed, 01 Apr 2014..Thu, 30 Apr 2014 => 12.0, Fri, 01 May 2014..Sun, 31 May 2014 => 34.0, …}
|
|
43
|
-
# @return [Hash<Range<Date, Date>, $2>] if grouped by week, the $1
|
|
44
|
-
# for each week in the time-range.
|
|
45
|
-
# @example Get the $1 for this and last week:
|
|
46
|
-
# resource.$1 since: 1.week.ago, by: :week
|
|
47
|
-
# # => {Wed, 01 Apr 2014..Tue, 07 Apr 2014 => 20.0, Wed, 08 Apr 2014..Tue, 14 Apr 2014 => 13.0, …}
|
|
48
43
|
# @macro report
|
|
49
44
|
|
|
50
45
|
# @!macro [new] report_with_range
|
|
@@ -75,19 +70,19 @@ module Yt
|
|
|
75
70
|
|
|
76
71
|
# @!macro [new] report_by_day
|
|
77
72
|
# @option options [Symbol] :by (:day) The dimension to collect $1 by.
|
|
78
|
-
# Accepted values are: +:day+, +:
|
|
73
|
+
# Accepted values are: +:day+, +:month+.
|
|
79
74
|
# @macro report_with_day
|
|
80
75
|
|
|
81
76
|
# @!macro [new] report_by_day_and_country
|
|
82
77
|
# @option options [Symbol] :by (:day) The dimension to collect $1 by.
|
|
83
|
-
# Accepted values are: +:day+, +:
|
|
78
|
+
# Accepted values are: +:day+, +:month+, :+range+.
|
|
84
79
|
# @macro report_with_day
|
|
85
80
|
# @macro report_with_range
|
|
86
81
|
# @macro report_with_country
|
|
87
82
|
|
|
88
83
|
# @!macro [new] report_by_day_and_state
|
|
89
84
|
# @option options [Symbol] :by (:day) The dimension to collect $1 by.
|
|
90
|
-
# Accepted values are: +:day+, +:
|
|
85
|
+
# Accepted values are: +:day+, +:month+, :+range+.
|
|
91
86
|
# @macro report_with_day
|
|
92
87
|
# @macro report_with_range
|
|
93
88
|
# @macro report_with_country_and_state
|
|
@@ -128,7 +123,7 @@ module Yt
|
|
|
128
123
|
|
|
129
124
|
# @!macro [new] report_by_video_dimensions
|
|
130
125
|
# @option options [Symbol] :by (:day) The dimension to collect $1 by.
|
|
131
|
-
# Accepted values are: +:day+, +:
|
|
126
|
+
# Accepted values are: +:day+, +:month+, +:range+,
|
|
132
127
|
# +:traffic_source+,+:search_term+, +:playback_location+,
|
|
133
128
|
# +:related_video+, +:embedded_player_location+.
|
|
134
129
|
# @option options [Array<Symbol>] :includes ([:id]) if grouped by
|
|
@@ -162,7 +157,7 @@ module Yt
|
|
|
162
157
|
|
|
163
158
|
# @!macro [new] report_by_channel_dimensions
|
|
164
159
|
# @option options [Symbol] :by (:day) The dimension to collect $1 by.
|
|
165
|
-
# Accepted values are: +:day+, +:
|
|
160
|
+
# Accepted values are: +:day+, +:month+, +:range+,
|
|
166
161
|
# +:traffic_source+, +:search_term+, +:playback_location+, +:video+,
|
|
167
162
|
# +:related_video+, +:playlist+, +:embedded_player_location+.
|
|
168
163
|
# @return [Hash<Symbol, $2>] if grouped by embedded player location,
|
|
@@ -175,7 +170,7 @@ module Yt
|
|
|
175
170
|
|
|
176
171
|
# @!macro [new] report_by_playlist_dimensions
|
|
177
172
|
# @option options [Symbol] :by (:day) The dimension to collect $1 by.
|
|
178
|
-
# Accepted values are: +:day+, +:
|
|
173
|
+
# Accepted values are: +:day+, +:month+, +:range+,
|
|
179
174
|
# +:traffic_source+, +:playback_location+, +:related_video+, +:video+,
|
|
180
175
|
# +:playlist+.
|
|
181
176
|
# @macro report_with_channel_dimensions
|
|
@@ -228,7 +223,7 @@ module Yt
|
|
|
228
223
|
def define_reports_method(metric, type)
|
|
229
224
|
(@metrics ||= {})[metric] = type
|
|
230
225
|
define_method :reports do |options = {}|
|
|
231
|
-
from = options[:since] || options[:from] || (options[:by].in?([:day, :
|
|
226
|
+
from = options[:since] || options[:from] || (options[:by].in?([:day, :month]) ? 5.days.ago : '2005-02-01')
|
|
232
227
|
to = options[:until] || options[:to] || Date.today
|
|
233
228
|
location = options[:in]
|
|
234
229
|
country = location.is_a?(Hash) ? location[:country] : location
|
|
@@ -252,7 +247,7 @@ module Yt
|
|
|
252
247
|
|
|
253
248
|
def define_metric_method(metric)
|
|
254
249
|
define_method metric do |options = {}|
|
|
255
|
-
from = options[:since] || options[:from] || (options[:by].in?([:day, :
|
|
250
|
+
from = options[:since] || options[:from] || (options[:by].in?([:day, :month]) ? 5.days.ago : '2005-02-01')
|
|
256
251
|
to = options[:until] || options[:to] || Date.today
|
|
257
252
|
location = options[:in]
|
|
258
253
|
country = location.is_a?(Hash) ? location[:country] : location
|
|
@@ -6,7 +6,6 @@ module Yt
|
|
|
6
6
|
class Reports < Base
|
|
7
7
|
DIMENSIONS = Hash.new({name: 'day', parse: ->(day, *values) { @metrics.keys.zip(values.map{|v| {Date.iso8601(day) => v}}).to_h} }).tap do |hash|
|
|
8
8
|
hash[:month] = {name: 'month', parse: ->(month, *values) { @metrics.keys.zip(values.map{|v| {Range.new(Date.strptime(month, '%Y-%m').beginning_of_month, Date.strptime(month, '%Y-%m').end_of_month) => v} }).to_h} }
|
|
9
|
-
hash[:week] = {name: '7DayTotals', parse: ->(last_day_of_week, *values) { @metrics.keys.zip(values.map{|v| {Range.new(Date.strptime(last_day_of_week) - 6, Date.strptime(last_day_of_week)) => v} }).to_h} }
|
|
10
9
|
hash[:range] = {parse: ->(*values) { @metrics.keys.zip(values.map{|v| {total: v}}).to_h } }
|
|
11
10
|
hash[:traffic_source] = {name: 'insightTrafficSourceType', parse: ->(source, *values) { @metrics.keys.zip(values.map{|v| {TRAFFIC_SOURCES.key(source) => v}}).to_h} }
|
|
12
11
|
hash[:playback_location] = {name: 'insightPlaybackLocationType', parse: ->(location, *values) { @metrics.keys.zip(values.map{|v| {PLAYBACK_LOCATIONS.key(location) => v}}).to_h} }
|
|
@@ -140,11 +139,6 @@ module Yt
|
|
|
140
139
|
end
|
|
141
140
|
if dimension == :month
|
|
142
141
|
hash = hash.transform_values{|h| h.sort_by{|range, v| range.first}.to_h}
|
|
143
|
-
elsif dimension == :week
|
|
144
|
-
hash = hash.transform_values do |h|
|
|
145
|
-
h.select{|range, v| range.last.wday == days_range.last.wday}.
|
|
146
|
-
sort_by{|range, v| range.first}.to_h
|
|
147
|
-
end
|
|
148
142
|
elsif dimension == :day
|
|
149
143
|
hash = hash.transform_values{|h| h.sort_by{|day, v| day}.to_h}
|
|
150
144
|
elsif dimension.in? [:traffic_source, :country, :state, :playback_location, :device_type, :operating_system, :subscribed_status]
|
|
@@ -159,11 +153,15 @@ module Yt
|
|
|
159
153
|
# same query is a workaround that works and can hardly cause any damage.
|
|
160
154
|
# Similarly, once in while YouTube responds with a random 503 error.
|
|
161
155
|
rescue Yt::Error => e
|
|
162
|
-
(max_retries > 0) && rescue?(e) ? sleep(
|
|
156
|
+
(max_retries > 0) && rescue?(e) ? sleep(retry_time) && within(days_range, country, state, dimension, videos, historical, max_retries - 1) : raise
|
|
163
157
|
end
|
|
164
158
|
|
|
165
159
|
private
|
|
166
160
|
|
|
161
|
+
def retry_time
|
|
162
|
+
3
|
|
163
|
+
end
|
|
164
|
+
|
|
167
165
|
def type_cast(value, type)
|
|
168
166
|
case [type]
|
|
169
167
|
when [Integer] then value.to_i if value
|
data/lib/yt/models/resource.rb
CHANGED
|
@@ -12,7 +12,9 @@ module Yt
|
|
|
12
12
|
|
|
13
13
|
# @!attribute [r] id
|
|
14
14
|
# @return [String] the ID that YouTube uses to identify each resource.
|
|
15
|
-
|
|
15
|
+
def id
|
|
16
|
+
@id ||= @match['id'] || fetch_channel_id
|
|
17
|
+
end
|
|
16
18
|
|
|
17
19
|
### STATUS ###
|
|
18
20
|
|
|
@@ -42,7 +44,12 @@ module Yt
|
|
|
42
44
|
|
|
43
45
|
# @private
|
|
44
46
|
def initialize(options = {})
|
|
45
|
-
|
|
47
|
+
if options[:url]
|
|
48
|
+
@url = options[:url]
|
|
49
|
+
@match = find_pattern_match
|
|
50
|
+
else
|
|
51
|
+
@id = options[:id]
|
|
52
|
+
end
|
|
46
53
|
@auth = options[:auth]
|
|
47
54
|
@snippet = Snippet.new(data: options[:snippet]) if options[:snippet]
|
|
48
55
|
@status = Status.new(data: options[:status]) if options[:status]
|
|
@@ -50,7 +57,11 @@ module Yt
|
|
|
50
57
|
|
|
51
58
|
# @private
|
|
52
59
|
def kind
|
|
53
|
-
|
|
60
|
+
if @url
|
|
61
|
+
@match[:kind].to_s
|
|
62
|
+
else
|
|
63
|
+
self.class.to_s.demodulize.underscore
|
|
64
|
+
end
|
|
54
65
|
end
|
|
55
66
|
|
|
56
67
|
# @private
|
|
@@ -66,8 +77,63 @@ module Yt
|
|
|
66
77
|
end
|
|
67
78
|
end
|
|
68
79
|
|
|
80
|
+
# @return [Array<Regexp>] patterns matching URLs of YouTube playlists.
|
|
81
|
+
PLAYLIST_PATTERNS = [
|
|
82
|
+
%r{^(?:https?://)?(?:www\.)?youtube\.com/playlist/?\?list=(?<id>[a-zA-Z0-9_-]+)},
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# @return [Array<Regexp>] patterns matching URLs of YouTube videos.
|
|
86
|
+
VIDEO_PATTERNS = [
|
|
87
|
+
%r{^(?:https?://)?(?:www\.)?youtube\.com/watch\?v=(?<id>[a-zA-Z0-9_-]{11})},
|
|
88
|
+
%r{^(?:https?://)?(?:www\.)?youtu\.be/(?<id>[a-zA-Z0-9_-]{11})},
|
|
89
|
+
%r{^(?:https?://)?(?:www\.)?youtube\.com/embed/(?<id>[a-zA-Z0-9_-]{11})},
|
|
90
|
+
%r{^(?:https?://)?(?:www\.)?youtube\.com/v/(?<id>[a-zA-Z0-9_-]{11})},
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# @return [Array<Regexp>] patterns matching URLs of YouTube channels.
|
|
94
|
+
CHANNEL_PATTERNS = [
|
|
95
|
+
%r{^(?:https?://)?(?:www\.)?youtube\.com/channel/(?<id>UC[a-zA-Z0-9_-]{22})},
|
|
96
|
+
%r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>c/|user/)?(?<name>[a-zA-Z0-9_-]+)}
|
|
97
|
+
]
|
|
98
|
+
|
|
69
99
|
private
|
|
70
100
|
|
|
101
|
+
def find_pattern_match
|
|
102
|
+
patterns.find do |kind, regex|
|
|
103
|
+
if data = @url.match(regex)
|
|
104
|
+
# Note: With Ruby 2.4, the following is data.named_captures
|
|
105
|
+
break data.names.zip(data.captures).to_h.merge kind: kind
|
|
106
|
+
end
|
|
107
|
+
end || {kind: :unknown}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def patterns
|
|
111
|
+
# @note: :channel *must* be the last since one of its regex eats the
|
|
112
|
+
# remaining patterns. In short, don't change the following order.
|
|
113
|
+
Enumerator.new do |patterns|
|
|
114
|
+
VIDEO_PATTERNS.each {|regex| patterns << [:video, regex]}
|
|
115
|
+
PLAYLIST_PATTERNS.each {|regex| patterns << [:playlist, regex]}
|
|
116
|
+
CHANNEL_PATTERNS.each {|regex| patterns << [:channel, regex]}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def fetch_channel_id
|
|
121
|
+
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
|
|
122
|
+
http.request Net::HTTP::Get.new("/#{@match['format']}#{@match['name']}")
|
|
123
|
+
end
|
|
124
|
+
if response.is_a?(Net::HTTPRedirection)
|
|
125
|
+
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
|
|
126
|
+
http.request Net::HTTP::Get.new(response['location'])
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
regex = %r{<meta itemprop="channelId" content="(?<id>UC[a-zA-Z0-9_-]{22})">}
|
|
130
|
+
if data = response.body.match(regex)
|
|
131
|
+
data[:id]
|
|
132
|
+
else
|
|
133
|
+
raise Yt::Errors::NoItems
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
71
137
|
# Since YouTube API only returns tags on Videos#list, the memoized
|
|
72
138
|
# `@snippet` is erased if the video was instantiated through Video#search
|
|
73
139
|
# (e.g., by calling account.videos or channel.videos), so that the full
|
data/lib/yt/models/url.rb
CHANGED
|
@@ -14,18 +14,17 @@ module Yt
|
|
|
14
14
|
# @param [String] text the name or URL of a YouTube resource (in any form).
|
|
15
15
|
def initialize(text)
|
|
16
16
|
@text = text.to_s.strip
|
|
17
|
-
@match = find_pattern_match
|
|
18
17
|
end
|
|
19
18
|
|
|
20
19
|
# @return [Symbol] the kind of YouTube resource matching the URL.
|
|
21
20
|
# Possible values are: +:playlist+, +:video+, +:channel+, and +:unknown:.
|
|
22
21
|
def kind
|
|
23
|
-
@
|
|
22
|
+
Resource.new(url: @text).kind.to_sym
|
|
24
23
|
end
|
|
25
24
|
|
|
26
25
|
# @return [<String, nil>] the ID of the YouTube resource matching the URL.
|
|
27
26
|
def id
|
|
28
|
-
@
|
|
27
|
+
Resource.new(url: @text).id
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
# @return [<Yt::Channel>] the resource associated with the URL
|
|
@@ -37,63 +36,6 @@ module Yt
|
|
|
37
36
|
else raise Yt::Errors::NoItems
|
|
38
37
|
end.new options.merge(id: id)
|
|
39
38
|
end
|
|
40
|
-
|
|
41
|
-
# @return [Array<Regexp>] patterns matching URLs of YouTube playlists.
|
|
42
|
-
PLAYLIST_PATTERNS = [
|
|
43
|
-
%r{^(?:https?://)?(?:www\.)?youtube\.com/playlist/?\?list=(?<id>[a-zA-Z0-9_-]+)},
|
|
44
|
-
]
|
|
45
|
-
|
|
46
|
-
# @return [Array<Regexp>] patterns matching URLs of YouTube videos.
|
|
47
|
-
VIDEO_PATTERNS = [
|
|
48
|
-
%r{^(?:https?://)?(?:www\.)?youtube\.com/watch\?v=(?<id>[a-zA-Z0-9_-]{11})},
|
|
49
|
-
%r{^(?:https?://)?(?:www\.)?youtu\.be/(?<id>[a-zA-Z0-9_-]{11})},
|
|
50
|
-
%r{^(?:https?://)?(?:www\.)?youtube\.com/embed/(?<id>[a-zA-Z0-9_-]{11})},
|
|
51
|
-
%r{^(?:https?://)?(?:www\.)?youtube\.com/v/(?<id>[a-zA-Z0-9_-]{11})},
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
# @return [Array<Regexp>] patterns matching URLs of YouTube channels.
|
|
55
|
-
CHANNEL_PATTERNS = [
|
|
56
|
-
%r{^(?:https?://)?(?:www\.)?youtube\.com/channel/(?<id>UC[a-zA-Z0-9_-]{22})},
|
|
57
|
-
%r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>c/|user/)?(?<name>[a-zA-Z0-9_-]+)}
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
def find_pattern_match
|
|
63
|
-
patterns.find(-> {{kind: :unknown}}) do |kind, regex|
|
|
64
|
-
if data = @text.match(regex)
|
|
65
|
-
# Note: With Ruby 2.4, the following is data.named_captures
|
|
66
|
-
break data.names.zip(data.captures).to_h.merge kind: kind
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def patterns
|
|
72
|
-
# @note: :channel *must* be the last since one of its regex eats the
|
|
73
|
-
# remaining patterns. In short, don't change the following order.
|
|
74
|
-
Enumerator.new do |patterns|
|
|
75
|
-
VIDEO_PATTERNS.each {|regex| patterns << [:video, regex]}
|
|
76
|
-
PLAYLIST_PATTERNS.each {|regex| patterns << [:playlist, regex]}
|
|
77
|
-
CHANNEL_PATTERNS.each {|regex| patterns << [:channel, regex]}
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def fetch_id
|
|
82
|
-
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
|
|
83
|
-
http.request Net::HTTP::Get.new("/#{@match['format']}#{@match['name']}")
|
|
84
|
-
end
|
|
85
|
-
if response.is_a?(Net::HTTPRedirection)
|
|
86
|
-
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
|
|
87
|
-
http.request Net::HTTP::Get.new(response['location'])
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
regex = %r{<meta itemprop="channelId" content="(?<id>UC[a-zA-Z0-9_-]{22})">}
|
|
91
|
-
if data = response.body.match(regex)
|
|
92
|
-
data[:id]
|
|
93
|
-
else
|
|
94
|
-
raise Yt::Errors::NoItems
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
39
|
end
|
|
98
40
|
end
|
|
99
41
|
end
|