yt 0.0.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/.yardopts +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +78 -0
- data/HISTORY.md +37 -0
- data/MIT-LICENSE +20 -0
- data/README.md +325 -0
- data/Rakefile +1 -0
- data/TODO.md +11 -0
- data/bin/yt +31 -0
- data/lib/yt.rb +2 -0
- data/lib/yt/actions/delete.rb +27 -0
- data/lib/yt/actions/delete_all.rb +28 -0
- data/lib/yt/actions/insert.rb +29 -0
- data/lib/yt/actions/list.rb +65 -0
- data/lib/yt/actions/update.rb +25 -0
- data/lib/yt/associations.rb +33 -0
- data/lib/yt/associations/annotations.rb +15 -0
- data/lib/yt/associations/channels.rb +20 -0
- data/lib/yt/associations/details_sets.rb +20 -0
- data/lib/yt/associations/playlist_items.rb +26 -0
- data/lib/yt/associations/playlists.rb +22 -0
- data/lib/yt/associations/ratings.rb +39 -0
- data/lib/yt/associations/snippets.rb +20 -0
- data/lib/yt/associations/statuses.rb +14 -0
- data/lib/yt/associations/subscriptions.rb +38 -0
- data/lib/yt/associations/user_infos.rb +21 -0
- data/lib/yt/associations/videos.rb +14 -0
- data/lib/yt/collections/annotations.rb +43 -0
- data/lib/yt/collections/base.rb +13 -0
- data/lib/yt/collections/channels.rb +32 -0
- data/lib/yt/collections/details_sets.rb +32 -0
- data/lib/yt/collections/playlist_items.rb +50 -0
- data/lib/yt/collections/playlists.rb +56 -0
- data/lib/yt/collections/ratings.rb +32 -0
- data/lib/yt/collections/snippets.rb +38 -0
- data/lib/yt/collections/subscriptions.rb +67 -0
- data/lib/yt/collections/user_infos.rb +41 -0
- data/lib/yt/collections/videos.rb +32 -0
- data/lib/yt/config.rb +55 -0
- data/lib/yt/models/account.rb +68 -0
- data/lib/yt/models/annotation.rb +137 -0
- data/lib/yt/models/base.rb +11 -0
- data/lib/yt/models/channel.rb +17 -0
- data/lib/yt/models/configuration.rb +29 -0
- data/lib/yt/models/description.rb +98 -0
- data/lib/yt/models/details_set.rb +31 -0
- data/lib/yt/models/playlist.rb +65 -0
- data/lib/yt/models/playlist_item.rb +42 -0
- data/lib/yt/models/rating.rb +28 -0
- data/lib/yt/models/snippet.rb +48 -0
- data/lib/yt/models/status.rb +26 -0
- data/lib/yt/models/subscription.rb +35 -0
- data/lib/yt/models/user_info.rb +66 -0
- data/lib/yt/models/video.rb +16 -0
- data/lib/yt/utils/request.rb +85 -0
- data/lib/yt/version.rb +3 -0
- data/spec/associations/device_auth/channels_spec.rb +10 -0
- data/spec/associations/device_auth/details_sets_spec.rb +19 -0
- data/spec/associations/device_auth/playlist_items_spec.rb +42 -0
- data/spec/associations/device_auth/playlists_spec.rb +42 -0
- data/spec/associations/device_auth/ratings_spec.rb +30 -0
- data/spec/associations/device_auth/snippets_spec.rb +30 -0
- data/spec/associations/device_auth/subscriptions_spec.rb +27 -0
- data/spec/associations/device_auth/user_infos_spec.rb +10 -0
- data/spec/associations/device_auth/videos_spec.rb +22 -0
- data/spec/associations/no_auth/annotations_spec.rb +15 -0
- data/spec/associations/server_auth/channels_spec.rb +2 -0
- data/spec/associations/server_auth/details_sets_spec.rb +18 -0
- data/spec/associations/server_auth/playlist_items_spec.rb +17 -0
- data/spec/associations/server_auth/playlists_spec.rb +17 -0
- data/spec/associations/server_auth/ratings_spec.rb +2 -0
- data/spec/associations/server_auth/snippets_spec.rb +28 -0
- data/spec/associations/server_auth/subscriptions_spec.rb +2 -0
- data/spec/associations/server_auth/user_infos_spec.rb +2 -0
- data/spec/associations/server_auth/videos_spec.rb +20 -0
- data/spec/collections/annotations_spec.rb +6 -0
- data/spec/collections/channels_spec.rb +6 -0
- data/spec/collections/details_sets_spec.rb +6 -0
- data/spec/collections/playlist_items_spec.rb +23 -0
- data/spec/collections/playlists_spec.rb +26 -0
- data/spec/collections/ratings_spec.rb +6 -0
- data/spec/collections/snippets_spec.rb +6 -0
- data/spec/collections/subscriptions_spec.rb +30 -0
- data/spec/collections/user_infos_spec.rb +6 -0
- data/spec/collections/videos_spec.rb +6 -0
- data/spec/models/annotation_spec.rb +131 -0
- data/spec/models/channel_spec.rb +13 -0
- data/spec/models/description_spec.rb +94 -0
- data/spec/models/details_set_spec.rb +23 -0
- data/spec/models/playlist_item_spec.rb +32 -0
- data/spec/models/playlist_spec.rb +52 -0
- data/spec/models/rating_spec.rb +13 -0
- data/spec/models/snippet_spec.rb +66 -0
- data/spec/models/status_spec.rb +42 -0
- data/spec/models/subscription_spec.rb +37 -0
- data/spec/models/user_info_spec.rb +69 -0
- data/spec/models/video_spec.rb +13 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/device_app.rb +16 -0
- data/spec/support/server_app.rb +10 -0
- data/yt.gemspec +30 -0
- metadata +209 -17
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/user_info'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class UserInfos < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@account = options[:account]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.by_account(account)
|
14
|
+
new account: account, auth: account.auth
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def new_item(data)
|
20
|
+
Yt::UserInfo.new data: data
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_params
|
24
|
+
super.tap do |params|
|
25
|
+
params[:path] = '/oauth2/v2/userinfo'
|
26
|
+
# TODO: Remove youtube from here, implement incremental scopes
|
27
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def next_page
|
32
|
+
request = Request.new list_params
|
33
|
+
response = request.run
|
34
|
+
raise unless response.is_a? Net::HTTPOK
|
35
|
+
@page_token = nil
|
36
|
+
|
37
|
+
Array.wrap response.body
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/video'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Videos < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@channel = options[:channel]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.by_channel(channel)
|
14
|
+
new channel: channel, auth: channel.auth
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def new_item(data)
|
20
|
+
Yt::Video.new id: data['id']['videoId'], snippet: data['snippet'], auth: @auth
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_params
|
24
|
+
super.tap do |params|
|
25
|
+
params[:params] = {channelId: @channel.id, type: :video, maxResults: 50, part: 'snippet'}
|
26
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
27
|
+
params[:path] = '/youtube/v3/search'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/yt/config.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'yt/models/configuration'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
# Provides methods to read and write runtime configuration information.
|
5
|
+
#
|
6
|
+
# Configuration options are loaded from `~/.yt`, `.yt`, command line
|
7
|
+
# switches, and the `YT_OPTS` environment variable (listed in lowest to
|
8
|
+
# highest precedence).
|
9
|
+
#
|
10
|
+
# @note Config is the only module auto-loaded in the Yt module,
|
11
|
+
# in order to have a syntax as easy as Yt.configure
|
12
|
+
#
|
13
|
+
# @example A server-to-server YouTube client app
|
14
|
+
#
|
15
|
+
# Yt.configure do |config|
|
16
|
+
# config.scenario = :server_app
|
17
|
+
# config.api_key = 'ABCDEFGHIJ1234567890'
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example A web YouTube client app
|
21
|
+
#
|
22
|
+
# Yt.configure do |config|
|
23
|
+
# config.client_id = 'ABCDEFGHIJ1234567890'
|
24
|
+
# config.client_secret = 'ABCDEFGHIJ1234567890'
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
module Config
|
28
|
+
# Yields the global configuration to a block.
|
29
|
+
# @yield [Yt::Configuration] global configuration
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# Yt.configure do |config|
|
33
|
+
# config.scenario = :server_app
|
34
|
+
# config.api_key = 'ABCDEFGHIJ1234567890'
|
35
|
+
# end
|
36
|
+
# @see Yt::Configuration
|
37
|
+
def configure
|
38
|
+
yield configuration if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the global [Configuration](Yt/Configuration) object. While you
|
42
|
+
# _can_ use this method to access the configuration, the more common
|
43
|
+
# convention is to use [Yt.configure](Yt#configure-class_method).
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# Yt.configuration.api_key = 'ABCDEFGHIJ1234567890'
|
47
|
+
# @see Yt.configure
|
48
|
+
# @see Yt::Configuration
|
49
|
+
def configuration
|
50
|
+
@configuration ||= Yt::Configuration.new
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
extend Config
|
55
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'yt/models/base'
|
2
|
+
require 'yt/config'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
# Provides methods to access a YouTube account.
|
6
|
+
class Account < Base
|
7
|
+
|
8
|
+
has_one :channel, delegate: [:videos, :playlists, :create_playlist, :delete_playlists, :update_playlists]
|
9
|
+
has_one :user_info, delegate: [:id, :email, :has_verified_email?, :gender,
|
10
|
+
:name, :given_name, :family_name, :profile_url, :avatar_url, :locale, :hd]
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
# By default is someone passes a refresh_token but not a scope, we can assume it's a youtube one
|
14
|
+
@scope = options.fetch :scope, 'https://www.googleapis.com/auth/youtube'
|
15
|
+
@access_token = options[:access_token]
|
16
|
+
@refresh_token = options[:refresh_token]
|
17
|
+
@redirect_url = options[:redirect_url]
|
18
|
+
end
|
19
|
+
|
20
|
+
def access_token_for(scope)
|
21
|
+
# TODO incremental scope
|
22
|
+
|
23
|
+
# HERE manage the fact that we must change some scope on device,
|
24
|
+
# like 'https://www.googleapis.com/auth/youtube.readonly' is not accepted
|
25
|
+
if Yt.configuration.scenario == :device_app && scope == 'https://www.googleapis.com/auth/youtube.readonly'
|
26
|
+
scope = 'https://www.googleapis.com/auth/youtube'
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO !! include? is not enough, because (for instance) 'youtube' also includes 'youtube.readonly'
|
30
|
+
|
31
|
+
# unless (@scope == scope) || (scope == 'https://www.googleapis.com/auth/youtube.readonly' && @scope =='https://www.googleapis.com/auth/youtube')
|
32
|
+
# @scope = scope
|
33
|
+
# @access_token = @refresh_token = nil
|
34
|
+
# end
|
35
|
+
@access_token ||= refresh_access_token || get_access_token
|
36
|
+
end
|
37
|
+
|
38
|
+
def auth
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Obtain a new access token using the refresh token
|
45
|
+
def refresh_access_token
|
46
|
+
if @refresh_token
|
47
|
+
body = {grant_type: 'refresh_token', refresh_token: @refresh_token}
|
48
|
+
request = Request.new auth_params.deep_merge(body: body)
|
49
|
+
response = request.run
|
50
|
+
response.body['access_token']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def auth_params
|
55
|
+
{
|
56
|
+
host: 'accounts.google.com',
|
57
|
+
path: '/o/oauth2/token',
|
58
|
+
format: :json,
|
59
|
+
body_type: :form,
|
60
|
+
method: :post,
|
61
|
+
body: {
|
62
|
+
client_id: Yt.configuration.client_id,
|
63
|
+
client_secret: Yt.configuration.client_secret
|
64
|
+
}
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Yt
|
2
|
+
# Provides methods to access and analyze a single YouTube annotation.
|
3
|
+
class Annotation
|
4
|
+
# Instantiate an Annotation object from its YouTube XML representation.
|
5
|
+
#
|
6
|
+
# @note There is no documented way to access annotations through API.
|
7
|
+
# There is an endpoint that returns an XML in an undocumented format,
|
8
|
+
# which is here parsed into a comprehensible set of attributes.
|
9
|
+
#
|
10
|
+
# @param [String] xml_data The YouTube XML representation of an annotation
|
11
|
+
def initialize(options = {})
|
12
|
+
@data = options[:data]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Checks whether the entire annotation box remains above y
|
16
|
+
#
|
17
|
+
# @param [Integer] y Vertical position in the Youtube video (0 to 100)
|
18
|
+
#
|
19
|
+
# @return [Boolean] Whether the box remains above y
|
20
|
+
def above?(y)
|
21
|
+
top && top < y
|
22
|
+
end
|
23
|
+
|
24
|
+
# Checks whether the entire annotation box remains below y
|
25
|
+
#
|
26
|
+
# @param [Integer] y Vertical position in the Youtube video (0 to 100)
|
27
|
+
#
|
28
|
+
# @return [Boolean] Whether the box remains below y
|
29
|
+
def below?(y)
|
30
|
+
bottom && bottom > y
|
31
|
+
end
|
32
|
+
|
33
|
+
# Checks whether there is a link to subscribe.
|
34
|
+
# Should a branding watermark also counts, because it links to the channel?
|
35
|
+
#
|
36
|
+
# @return [Boolean] Whether there is a link to subscribe in the annotation
|
37
|
+
def has_link_to_subscribe?(options = {}) # TODO: options for which videos
|
38
|
+
link_class == '5'
|
39
|
+
end
|
40
|
+
|
41
|
+
# Checks whether there is a link to a video.
|
42
|
+
# An Invideo featured video also counts
|
43
|
+
#
|
44
|
+
# @return [Boolean] Whether there is a link to a video in the annotation
|
45
|
+
def has_link_to_video?(options = {}) # TODO: options for which videos
|
46
|
+
link_class == '1' || type == 'promotion'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Checks whether there is a link to a playlist.
|
50
|
+
# A link to a video with the playlist in the URL also counts
|
51
|
+
#
|
52
|
+
# @return [Boolean] Whether there is a link to a playlist in the annotation
|
53
|
+
def has_link_to_playlist?
|
54
|
+
link_class == '2' || text.include?('&list=')
|
55
|
+
end
|
56
|
+
|
57
|
+
# Checks whether the link opens in the same window.
|
58
|
+
#
|
59
|
+
# @return [Boolean] Whether the link opens in the same window
|
60
|
+
def has_link_to_same_window?
|
61
|
+
link_target == 'current'
|
62
|
+
end
|
63
|
+
|
64
|
+
# Checks whether the annotation comes from InVideo Programming
|
65
|
+
#
|
66
|
+
# @return [Boolean] Whether the annotation comes from InVideo Programming
|
67
|
+
def has_invideo_programming?
|
68
|
+
type == 'promotion' || type == 'branding'
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Boolean] Whether the annotation starts after the number of seconds
|
72
|
+
# @note This is broken for invideo programming, because they do not
|
73
|
+
# have the timestamp in the region, but in the "data" field
|
74
|
+
def starts_after?(seconds)
|
75
|
+
timestamps.first > seconds if timestamps.any?
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Boolean] Whether the annotation starts before the number of seconds
|
79
|
+
# @note This is broken for invideo programming, because they do not
|
80
|
+
# have the timestamp in the region, but in the "data" field
|
81
|
+
def starts_before?(seconds)
|
82
|
+
timestamps.first < seconds if timestamps.any?
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def text
|
88
|
+
@text ||= @data.fetch 'TEXT', ''
|
89
|
+
end
|
90
|
+
|
91
|
+
def type
|
92
|
+
@type ||= @data.fetch 'type', ''
|
93
|
+
end
|
94
|
+
|
95
|
+
def link_class
|
96
|
+
@link_class ||= url['link_class']
|
97
|
+
end
|
98
|
+
|
99
|
+
def link_target
|
100
|
+
@link_target ||= url['target']
|
101
|
+
end
|
102
|
+
|
103
|
+
def url
|
104
|
+
@url ||= action.fetch 'url', {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def action
|
108
|
+
@action ||= @data.fetch 'action', {}
|
109
|
+
end
|
110
|
+
|
111
|
+
def top
|
112
|
+
@top ||= positions.map{|pos| pos['y'].to_f}.max
|
113
|
+
end
|
114
|
+
|
115
|
+
def bottom
|
116
|
+
@bottom ||= positions.map{|pos| pos['y'].to_f + pos['h'].to_f}.max
|
117
|
+
end
|
118
|
+
|
119
|
+
def timestamps
|
120
|
+
@timestamps ||= positions.map do |pos|
|
121
|
+
Time.parse(pos['t']) - Time.parse('0:00')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def positions
|
126
|
+
@positions ||= region['rectRegion'] || region['anchoredRegion'] || []
|
127
|
+
end
|
128
|
+
|
129
|
+
def region
|
130
|
+
@region ||= segment.fetch 'movingRegion', {}
|
131
|
+
end
|
132
|
+
|
133
|
+
def segment
|
134
|
+
@segment ||= (@data['segment'] || {})
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'yt/models/base'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
class Channel < Base
|
5
|
+
attr_reader :id, :auth
|
6
|
+
has_one :snippet, delegate: [:title, :description, :thumbnail_url, :published_at]
|
7
|
+
has_many :subscriptions
|
8
|
+
has_many :videos
|
9
|
+
has_many :playlists
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@id = options[:id]
|
13
|
+
@auth = options[:auth]
|
14
|
+
@snippet = Snippet.new(data: options[:snippet]) if options[:snippet]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Yt
|
2
|
+
# Stores runtime configuration information.
|
3
|
+
#
|
4
|
+
# Configuration options are loaded from `~/.yt`, `.yt`, command line
|
5
|
+
# switches, and the `YT_OPTS` environment variable (listed in lowest to
|
6
|
+
# highest precedence).
|
7
|
+
#
|
8
|
+
# @example A server-to-server YouTube client app
|
9
|
+
#
|
10
|
+
# Yt.configure do |config|
|
11
|
+
# config.scenario = :server_app
|
12
|
+
# config.api_key = 'ABCDEFGHIJ1234567890'
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @example A web YouTube client app
|
16
|
+
#
|
17
|
+
# Yt.configure do |config|
|
18
|
+
# config.client_id = 'ABCDEFGHIJ1234567890'
|
19
|
+
# config.client_secret = 'ABCDEFGHIJ1234567890'
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class Configuration
|
23
|
+
attr_accessor :scenario, :api_key, :client_id, :client_secret, :account
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@scenario = :web_app
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Yt
|
2
|
+
# Provides read-only access to the description of a YouTube resource.
|
3
|
+
# Resources with descriptions are: videos and channels.
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
#
|
7
|
+
# description = Yt::Description.new 'Fullscreen provides a suite of end-to-end YouTube tools and services to many of the world’s leading brands and media companies.'
|
8
|
+
# description.to_s.slice(0,19) # => 'Fullscreen provides'
|
9
|
+
# description.length # => 127
|
10
|
+
#
|
11
|
+
class Description < String
|
12
|
+
# Returns whether the description includes a YouTube video URL
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
#
|
16
|
+
# description = Yt::Description.new 'Link to video: youtube.com/watch?v=MESycYJytkU'
|
17
|
+
# description.has_link_to_video? #=> true
|
18
|
+
#
|
19
|
+
# @return [Boolean] Whether the description includes a link to a video
|
20
|
+
def has_link_to_video?
|
21
|
+
# TODO: might take as an option WHICH video to link to
|
22
|
+
# in order to check if it's my own video
|
23
|
+
regex? :video_long_url, :video_short_url
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns whether the description includes a YouTube channel URL
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
#
|
30
|
+
# description = Yt::Description.new Link to channel: youtube.com/fullscreen
|
31
|
+
# description.has_link_to_channel? #=> true
|
32
|
+
#
|
33
|
+
# @return [Boolean] Whether the description includes a link to a channel
|
34
|
+
def has_link_to_channel?(options = {}) # TODO: which channel
|
35
|
+
# TODO: might take as an option WHICH channel to link to
|
36
|
+
# in order to check if it's my own channel
|
37
|
+
regex? :channel_long_url, :channel_short_url, :channel_user_url
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns whether the description includes a YouTube subscription URL
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
#
|
44
|
+
# description = Yt::Description.new Link to subscribe: youtube.com/subscription_center?add_user=fullscreen
|
45
|
+
# description.has_link_to_subscribe? #=> true
|
46
|
+
#
|
47
|
+
# @return [Boolean] Whether the description includes a link to subscribe
|
48
|
+
def has_link_to_subscribe?(options = {}) # TODO: which channel
|
49
|
+
# TODO: might take as an option WHICH channel to subscribe to
|
50
|
+
# in order to check if it's my own channel
|
51
|
+
regex? :subscribe_center_url, :subscribe_widget_url, :subscribe_confirm_url
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns whether the description includes a YouTube playlist URL
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
#
|
58
|
+
# description = Yt::Description.new Link to playlist: youtube.com/playlist?list=LLxO1tY8h1AhOz0T4ENwmpow
|
59
|
+
# description.has_link_to_playlist? #=> true
|
60
|
+
#
|
61
|
+
# @return [Boolean] Whether the description includes a link to a playlist
|
62
|
+
def has_link_to_playlist?
|
63
|
+
regex? :playlist_long_url, :playlist_embed_url
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def regex?(*keys)
|
69
|
+
keys.find{|key| self =~ regex_for(key)}
|
70
|
+
end
|
71
|
+
|
72
|
+
def regex_for(key)
|
73
|
+
host, name = '(?:https?://)?(?:www\.)?', '([a-zA-Z0-9_-]+)'
|
74
|
+
case key
|
75
|
+
when :video_long_url
|
76
|
+
%r{#{host}youtube\.com/watch\?v=#{name}}
|
77
|
+
when :video_short_url
|
78
|
+
%r{#{host}youtu\.be/#{name}}
|
79
|
+
when :channel_long_url
|
80
|
+
%r{#{host}youtube\.com/channel/#{name}}
|
81
|
+
when :channel_short_url
|
82
|
+
%r{#{host}youtube\.com/#{name}}
|
83
|
+
when :channel_user_url
|
84
|
+
%r{#{host}youtube\.com/user/#{name}}
|
85
|
+
when :subscribe_center_url
|
86
|
+
%r{#{host}youtube\.com/subscription_center\?add_user=#{name}}
|
87
|
+
when :subscribe_widget_url
|
88
|
+
%r{#{host}youtube\.com/subscribe_widget\?p=#{name}}
|
89
|
+
when :subscribe_confirm_url
|
90
|
+
%r{#{host}youtube\.com/channel/(?:[a-zA-Z0-9&_=-]*)\?sub_confirmation=1}
|
91
|
+
when :playlist_long_url
|
92
|
+
%r{#{host}youtube\.com/playlist\?list=#{name}}
|
93
|
+
when :playlist_embed_url
|
94
|
+
%r{#{host}youtube\.com/watch\?v=(?:[a-zA-Z0-9&_=-]*)&list=#{name}}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|