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
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
* find by url (either video or channel or.. playlist)
|
2
|
+
* Google accounts?
|
3
|
+
* ENV support
|
4
|
+
|
5
|
+
* add has_one :status to video
|
6
|
+
|
7
|
+
* operations like subscribe that require authentication should not fail if
|
8
|
+
called on Yt::Channel without auth but, similarly to account, show the prompt
|
9
|
+
or ask for the device code
|
10
|
+
|
11
|
+
* scope
|
data/bin/yt
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'yt'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
require 'yt'
|
8
|
+
end
|
9
|
+
|
10
|
+
############################
|
11
|
+
|
12
|
+
Yt.configure do |config|
|
13
|
+
config.scenario = :server_app
|
14
|
+
config.api_key = ENV['YT_TEST_APP_SERVER_API_KEY']
|
15
|
+
end
|
16
|
+
|
17
|
+
############################
|
18
|
+
|
19
|
+
channel = Yt::Channel.new id: ARGV[0] || 'UCxO1tY8h1AhOz0T4ENwmpow'
|
20
|
+
|
21
|
+
puts "Title: #{channel.title}"
|
22
|
+
puts "Description: #{channel.description}"
|
23
|
+
puts "Thumbnail: #{channel.thumbnail_url}"
|
24
|
+
puts "Videos: "
|
25
|
+
channel.videos.each do |video|
|
26
|
+
puts " Annotations: #{video.annotations.count}"
|
27
|
+
puts " Duration: #{video.duration}s"
|
28
|
+
puts " Title: #{video.title}"
|
29
|
+
puts " Description: #{video.description}"
|
30
|
+
puts " Thumbnail: #{video.thumbnail_url}"
|
31
|
+
end
|
data/lib/yt.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'yt/utils/request'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Actions
|
5
|
+
module Delete
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def do_delete(extra_delete_params = {})
|
10
|
+
request = Request.new delete_params.merge(extra_delete_params)
|
11
|
+
response = request.run
|
12
|
+
raise unless response.is_a? Net::HTTPNoContent
|
13
|
+
yield response.body
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete_params
|
17
|
+
{}.tap do |params|
|
18
|
+
params[:method] = :delete
|
19
|
+
params[:format] = :json
|
20
|
+
params[:host] = 'www.googleapis.com'
|
21
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
22
|
+
params[:auth] = @auth
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'yt/actions/list'
|
2
|
+
require 'yt/utils/request'
|
3
|
+
|
4
|
+
module Yt
|
5
|
+
module Actions
|
6
|
+
module DeleteAll
|
7
|
+
include List
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def do_delete_all(params = {})
|
12
|
+
where(params).map do |item|
|
13
|
+
yield item if block_given?
|
14
|
+
item.delete
|
15
|
+
end.tap { @items = [] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def where(params = {})
|
19
|
+
list.find_all do |item|
|
20
|
+
params.all? do |method, value|
|
21
|
+
# TODO: could be symbol etc...
|
22
|
+
item.respond_to?(method) && item.send(method) == value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'yt/utils/request'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Actions
|
5
|
+
module Insert
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def do_insert(extra_insert_params = {})
|
10
|
+
request = Request.new insert_params.merge(extra_insert_params)
|
11
|
+
response = request.run
|
12
|
+
raise unless response.is_a? Net::HTTPOK
|
13
|
+
@items = []
|
14
|
+
new_item response.body
|
15
|
+
end
|
16
|
+
|
17
|
+
def insert_params
|
18
|
+
{}.tap do |params|
|
19
|
+
params[:method] = :post
|
20
|
+
params[:format] = :json
|
21
|
+
params[:host] = 'www.googleapis.com'
|
22
|
+
params[:body_type] = :json
|
23
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
24
|
+
params[:auth] = @auth
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'yt/utils/request'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Actions
|
5
|
+
module List
|
6
|
+
|
7
|
+
delegate :count, :first, :any?, :each, :map, to: :list
|
8
|
+
alias size count
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def list
|
13
|
+
@last_index, @page_token = 0, nil
|
14
|
+
Enumerator.new do |items|
|
15
|
+
while next_item = find_next
|
16
|
+
items << next_item
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_next
|
22
|
+
@items ||= []
|
23
|
+
if @items[@last_index].nil? && more_pages?
|
24
|
+
more_items = next_page.map{|data| new_item data}
|
25
|
+
@items.concat more_items
|
26
|
+
end
|
27
|
+
@items[(@last_index +=1) -1]
|
28
|
+
end
|
29
|
+
|
30
|
+
# To be overriden by the subclasses
|
31
|
+
def new_item(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def more_pages?
|
35
|
+
@last_index.zero? || !@page_token.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def next_page
|
39
|
+
params = list_params.dup
|
40
|
+
params[:params][:pageToken] = @page_token if @page_token
|
41
|
+
next_page = fetch_page params
|
42
|
+
@page_token = next_page[:token]
|
43
|
+
next_page[:items]
|
44
|
+
end
|
45
|
+
|
46
|
+
def fetch_page(params = {})
|
47
|
+
request = Request.new params
|
48
|
+
response = request.run
|
49
|
+
raise unless response.is_a? Net::HTTPOK
|
50
|
+
token = response.body['nextPageToken']
|
51
|
+
items = response.body.fetch 'items', []
|
52
|
+
{items: items, token: token}
|
53
|
+
end
|
54
|
+
|
55
|
+
def list_params
|
56
|
+
{}.tap do |params|
|
57
|
+
params[:method] = :get
|
58
|
+
params[:format] = :json
|
59
|
+
params[:host] = 'www.googleapis.com'
|
60
|
+
params[:auth] = @auth
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'yt/utils/request'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Actions
|
5
|
+
module Update
|
6
|
+
def do_update(extra_update_params = {}, options = {})
|
7
|
+
request = Request.new update_params.deep_merge(extra_update_params)
|
8
|
+
response = request.run
|
9
|
+
expected_response = options.fetch :expect, Net::HTTPNoContent
|
10
|
+
raise unless response.is_a? expected_response
|
11
|
+
yield response.body
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_params
|
15
|
+
{}.tap do |params|
|
16
|
+
params[:method] = :put
|
17
|
+
params[:format] = :json
|
18
|
+
params[:host] = 'www.googleapis.com'
|
19
|
+
params[:scope] = 'https://www.googleapis.com/auth/youtube'
|
20
|
+
params[:auth] = @auth
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'active_support/dependencies/autoload'
|
3
|
+
require 'active_support/core_ext/string/inflections' # for camelize
|
4
|
+
|
5
|
+
module Yt
|
6
|
+
module Associations
|
7
|
+
# @note: Using Autoload to avoid circular dependencies.
|
8
|
+
# For instance: Yt::Channel requires Yt::Base, which requires
|
9
|
+
# Yt::Associations, which requires Yt::Associations::Subscription,
|
10
|
+
# which requires Yt::Subscription, which requires Yt::Base
|
11
|
+
extend ActiveSupport::Autoload
|
12
|
+
|
13
|
+
autoload :Annotations
|
14
|
+
autoload :Channels
|
15
|
+
autoload :DetailsSets
|
16
|
+
autoload :PlaylistItems
|
17
|
+
autoload :Playlists
|
18
|
+
autoload :Ratings
|
19
|
+
autoload :Snippets
|
20
|
+
autoload :Statuses
|
21
|
+
autoload :Subscriptions
|
22
|
+
autoload :UserInfos
|
23
|
+
autoload :Videos
|
24
|
+
|
25
|
+
def has_many(attributes, options = {})
|
26
|
+
mod = attributes.to_s.sub(/.*\./, '').camelize
|
27
|
+
include "Yt::Associations::#{mod.pluralize}".constantize
|
28
|
+
delegate *options[:delegate], to: attributes if options[:delegate]
|
29
|
+
end
|
30
|
+
|
31
|
+
alias has_one has_many
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'yt/collections/annotations'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_many :annotations` method to YouTube resources,
|
6
|
+
# which allows to access to content annotation set-specific methods.
|
7
|
+
# YouTube resources with annotations are: videos.
|
8
|
+
module Annotations
|
9
|
+
def annotations
|
10
|
+
@annotations ||= Collections::Annotations.by_video self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yt/collections/channels'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :channel` method to YouTube resources, which
|
6
|
+
# allows to access to channel-specific methods like.
|
7
|
+
# YouTube resources with a channel are: account.
|
8
|
+
module Channels
|
9
|
+
def channel
|
10
|
+
@channel ||= channels.first
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def channels
|
16
|
+
@channels ||= Collections::Channels.by_account self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yt/collections/details_sets'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :details_set` method to YouTube resources, which
|
6
|
+
# allows to access to content detail set-specific methods like `duration`.
|
7
|
+
# YouTube resources with content details are: videos.
|
8
|
+
module DetailsSets
|
9
|
+
def details_set
|
10
|
+
@detail_set ||= details_sets.first
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def details_sets
|
16
|
+
@details_sets ||= Collections::DetailsSets.by_video self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'yt/collections/playlist_items'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_many :playlist_items` method to YouTube resources, which
|
6
|
+
# allows to invoke playlist_item-related methods, such as .add_video.
|
7
|
+
# YouTube resources with playlist items are: playlists.
|
8
|
+
module PlaylistItems
|
9
|
+
def playlist_items
|
10
|
+
@playlist_items ||= Collections::PlaylistItems.by_playlist self
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_video(video_id)
|
14
|
+
playlist_items.insert id: video_id, kind: :video
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_videos(video_ids = [])
|
18
|
+
video_ids.map{|video_id| add_video video_id}
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_playlist_items(attrs = {})
|
22
|
+
playlist_items.delete_all attrs
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'yt/collections/playlists'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_many :playlists` method to YouTube resources, which
|
6
|
+
# allows to invoke playlist-related methods, such as .create_playlist.
|
7
|
+
# YouTube resources with playlist are: channels.
|
8
|
+
module Playlists
|
9
|
+
def playlists
|
10
|
+
@playlists ||= Collections::Playlists.by_channel self
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_playlist(params = {})
|
14
|
+
playlists.insert params
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete_playlists(attrs = {})
|
18
|
+
playlists.delete_all attrs
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'yt/collections/ratings'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :rating` method to YouTube resources, which
|
6
|
+
# allows to invoke rating-related methods, such as .like.
|
7
|
+
# YouTube resources with rating are: videos.
|
8
|
+
module Ratings
|
9
|
+
def rating
|
10
|
+
@rating ||= ratings.first
|
11
|
+
end
|
12
|
+
|
13
|
+
def liked?
|
14
|
+
rating.rating == :like
|
15
|
+
end
|
16
|
+
|
17
|
+
def like
|
18
|
+
rating.update :like
|
19
|
+
liked?
|
20
|
+
end
|
21
|
+
|
22
|
+
def dislike
|
23
|
+
rating.update :dislike
|
24
|
+
!liked?
|
25
|
+
end
|
26
|
+
|
27
|
+
def unlike
|
28
|
+
rating.update :none
|
29
|
+
!liked?
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def ratings
|
35
|
+
@ratings ||= Collections::Ratings.by_video self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'yt/collections/snippets'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :snippet` method to YouTube resources, which
|
6
|
+
# allows to access to content detail set-specific methods like `title`.
|
7
|
+
# YouTube resources with content details are: videos and channels.
|
8
|
+
module Snippets
|
9
|
+
def snippet
|
10
|
+
@snippet ||= snippets.first
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def snippets
|
16
|
+
@snippets ||= Collections::Snippets.by_resource self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'yt/models/status'
|
2
|
+
|
3
|
+
module Yt
|
4
|
+
module Associations
|
5
|
+
# Provides the `has_one :status` method to YouTube resources, which
|
6
|
+
# allows to access to status-specific methods like `public?`.
|
7
|
+
# YouTube resources with status are: playlists.
|
8
|
+
module Statuses
|
9
|
+
def status
|
10
|
+
@status
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|