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,38 @@
|
|
1
|
+
require 'yt/collections/subscriptions'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :subscription` method to YouTube resources, which
|
6
|
+
# allows to invoke subscription-related methods, such as .subscribe.
|
7
|
+
# YouTube resources with subscription are: channels.
|
8
|
+
module Subscriptions
|
9
|
+
def subscriptions
|
10
|
+
@subscriptions ||= Collections::Subscriptions.by_channel self
|
11
|
+
end
|
12
|
+
|
13
|
+
def subscribed?
|
14
|
+
subscriptions.any?{|s| s.exists?}
|
15
|
+
end
|
16
|
+
|
17
|
+
def subscribe
|
18
|
+
subscriptions.insert ignore_duplicates: true
|
19
|
+
subscribed?
|
20
|
+
end
|
21
|
+
|
22
|
+
def subscribe!
|
23
|
+
subscriptions.insert
|
24
|
+
subscribed?
|
25
|
+
end
|
26
|
+
|
27
|
+
def unsubscribe
|
28
|
+
subscriptions.delete_all({}, ignore_not_found: true)
|
29
|
+
!subscribed?
|
30
|
+
end
|
31
|
+
|
32
|
+
def unsubscribe!
|
33
|
+
subscriptions.delete_all
|
34
|
+
!subscribed?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'yt/collections/user_infos'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :user_info` method to YouTube resources, which
|
6
|
+
# allows to access to user_info-specific methods like `email`.
|
7
|
+
# YouTube resources with user infos are: accounts.
|
8
|
+
module UserInfos
|
9
|
+
def user_info
|
10
|
+
@user_info ||= user_infos.first
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def user_infos
|
16
|
+
@user_infos ||= Collections::UserInfos.by_account self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'yt/collections/videos'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_many :videos` method to YouTube resources, which
|
6
|
+
# allows to access only the videos that belong to a specific resource.
|
7
|
+
# YouTube resources with videos are: channels.
|
8
|
+
module Videos
|
9
|
+
def videos
|
10
|
+
@videos ||= Collections::Videos.by_channel self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/annotation'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Annotations < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@video = options[:video]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.by_video(video)
|
14
|
+
new video: video, auth: video.auth
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def new_item(data)
|
20
|
+
Yt::Annotation.new data: data
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_params
|
24
|
+
super.tap do |params|
|
25
|
+
params[:format] = :xml
|
26
|
+
params[:host] = 'www.youtube.com'
|
27
|
+
params[:path] = '/annotations_invideo'
|
28
|
+
params[:params] = {video_id: @video.id}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def next_page
|
33
|
+
request = Request.new list_params
|
34
|
+
response = request.run
|
35
|
+
raise unless response.is_a? Net::HTTPOK
|
36
|
+
@page_token = nil
|
37
|
+
|
38
|
+
document = response.body.fetch('document', {})['annotations'] || {}
|
39
|
+
Array.wrap document.fetch 'annotation', []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/channel'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Channels < 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::Channel.new id: data['id'], snippet: data['snippet'], auth: @auth
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_params
|
24
|
+
super.tap do |params|
|
25
|
+
params[:params] = {maxResults: 50, part: 'snippet', mine: true}
|
26
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
27
|
+
params[:path] = '/youtube/v3/channels'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/details_set'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class DetailsSets < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@video = options[:video]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.by_video(video)
|
14
|
+
new video: video, auth: video.auth
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def new_item(data)
|
20
|
+
Yt::DetailsSet.new data: data['contentDetails']
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_params
|
24
|
+
super.tap do |params|
|
25
|
+
params[:params] = {maxResults: 50, part: 'contentDetails', id: @video.id}
|
26
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
|
27
|
+
params[:path] = '/youtube/v3/videos'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/playlist_item'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class PlaylistItems < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@playlist = options[:playlist]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.by_playlist(playlist)
|
14
|
+
new playlist: playlist, auth: playlist.auth
|
15
|
+
end
|
16
|
+
|
17
|
+
# options are id and kind
|
18
|
+
def insert(options = {}) #
|
19
|
+
resource = {kind: "youtube##{options[:kind]}"}
|
20
|
+
resource["#{options[:kind]}Id"] = options[:id]
|
21
|
+
snippet = {playlistId: @playlist.id, resourceId: resource}
|
22
|
+
do_insert body: {snippet: snippet}, params: {part: 'snippet,status'}
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete_all(params = {})
|
26
|
+
do_delete_all params
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def new_item(data)
|
32
|
+
Yt::PlaylistItem.new id: data['id'], snippet: data['snippet'], status: data['status'], auth: @auth
|
33
|
+
end
|
34
|
+
|
35
|
+
def list_params
|
36
|
+
super.tap do |params|
|
37
|
+
params[:params] = {maxResults: 50, part: 'snippet,status', playlistId: @playlist.id}
|
38
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
|
39
|
+
params[:path] = '/youtube/v3/playlistItems'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def insert_params
|
44
|
+
super.tap do |params|
|
45
|
+
params[:path] = '/youtube/v3/playlistItems'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/playlist'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Playlists < 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
|
+
# Valid body (no defaults) are: title (string), description (string), privacy_status (string),
|
18
|
+
# tags (array of strings)
|
19
|
+
def insert(options = {})
|
20
|
+
body = {}
|
21
|
+
|
22
|
+
snippet = options.slice :title, :description, :tags
|
23
|
+
body[:snippet] = snippet if snippet.any?
|
24
|
+
|
25
|
+
status = options[:privacy_status]
|
26
|
+
body[:status] = {privacyStatus: status} if status
|
27
|
+
|
28
|
+
do_insert body: body, params: {part: 'snippet,status'}
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete_all(params = {})
|
32
|
+
do_delete_all params
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def new_item(data)
|
38
|
+
Yt::Playlist.new id: data['id'], snippet: data['snippet'], status: data['status'], auth: @auth
|
39
|
+
end
|
40
|
+
|
41
|
+
def list_params
|
42
|
+
super.tap do |params|
|
43
|
+
params[:params] = {maxResults: 50, part: 'snippet,status', channelId: @channel.id}
|
44
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
|
45
|
+
params[:path] = '/youtube/v3/playlists'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def insert_params
|
50
|
+
super.tap do |params|
|
51
|
+
params[:path] = '/youtube/v3/playlists'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/rating'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Ratings < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@video = options[:video]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.by_video(video)
|
14
|
+
new video: video, auth: video.auth
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def new_item(data)
|
20
|
+
Yt::Rating.new rating: data['rating'], video_id: @video.id, auth: @auth
|
21
|
+
end
|
22
|
+
|
23
|
+
def list_params
|
24
|
+
super.tap do |params|
|
25
|
+
params[:path] = '/youtube/v3/videos/getRating'
|
26
|
+
params[:params] = {id: @video.id}
|
27
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/snippet'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Snippets < Base
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@resource = options[:resource]
|
10
|
+
@auth = options[:auth]
|
11
|
+
end
|
12
|
+
|
13
|
+
# @note Google API must have some caching layer by which if we try to
|
14
|
+
# delete a snippet that we just created, we encounter an error.
|
15
|
+
# To overcome this, if we have just updated the snippet, we must
|
16
|
+
# wait some time before requesting it again.
|
17
|
+
#
|
18
|
+
def self.by_resource(resource)
|
19
|
+
new resource: resource, auth: resource.auth
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def new_item(data)
|
25
|
+
Yt::Snippet.new data: data['snippet']
|
26
|
+
end
|
27
|
+
|
28
|
+
def list_params
|
29
|
+
resources_path = @resource.class.to_s.demodulize.underscore.pluralize
|
30
|
+
super.tap do |params|
|
31
|
+
params[:params] = {id: @resource.id, part: 'snippet'}
|
32
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube.readonly'
|
33
|
+
params[:path] = "/youtube/v3/#{resources_path}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'yt/collections/base'
|
2
|
+
require 'yt/models/subscription'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Collections
|
6
|
+
class Subscriptions < 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
|
+
def insert(options = {})
|
18
|
+
throttle
|
19
|
+
do_insert
|
20
|
+
rescue Yt::RequestError => error
|
21
|
+
raise error unless options[:ignore_duplicates] && error.reasons.include?('subscriptionDuplicate')
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_all(params = {}, options = {})
|
25
|
+
do_delete_all(params) {throttle}
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def new_item(data)
|
31
|
+
Yt::Subscription.new id: data['id'], auth: @auth
|
32
|
+
end
|
33
|
+
|
34
|
+
# @note Google API must have some caching layer by which if we try to
|
35
|
+
# delete a subscription that we just created, we encounter an error.
|
36
|
+
# To overcome this, if we have just updated the subscription, we must
|
37
|
+
# wait some time before requesting it again.
|
38
|
+
#
|
39
|
+
def throttle(seconds = 8)
|
40
|
+
wait = [(@last_changed_at ||= Time.now) - Time.now + seconds, 0].max
|
41
|
+
sleep wait
|
42
|
+
@last_changed_at = Time.now
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch_page(params = {})
|
46
|
+
throttle
|
47
|
+
super params
|
48
|
+
end
|
49
|
+
|
50
|
+
def list_params
|
51
|
+
super.tap do |params|
|
52
|
+
params[:params] = {maxResults: 50, forChannelId: @channel.id, mine: true, part: 'snippet'}
|
53
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
54
|
+
params[:path] = '/youtube/v3/subscriptions'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def insert_params
|
59
|
+
super.tap do |params|
|
60
|
+
params[:path] = '/youtube/v3/subscriptions'
|
61
|
+
params[:params] = {part: 'snippet'}
|
62
|
+
params[:body] = {snippet: {resourceId: {channelId: @channel.id}}}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|