yt 0.0.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +24 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/.yardopts +1 -0
  6. data/Gemfile +9 -0
  7. data/Gemfile.lock +78 -0
  8. data/HISTORY.md +37 -0
  9. data/MIT-LICENSE +20 -0
  10. data/README.md +325 -0
  11. data/Rakefile +1 -0
  12. data/TODO.md +11 -0
  13. data/bin/yt +31 -0
  14. data/lib/yt.rb +2 -0
  15. data/lib/yt/actions/delete.rb +27 -0
  16. data/lib/yt/actions/delete_all.rb +28 -0
  17. data/lib/yt/actions/insert.rb +29 -0
  18. data/lib/yt/actions/list.rb +65 -0
  19. data/lib/yt/actions/update.rb +25 -0
  20. data/lib/yt/associations.rb +33 -0
  21. data/lib/yt/associations/annotations.rb +15 -0
  22. data/lib/yt/associations/channels.rb +20 -0
  23. data/lib/yt/associations/details_sets.rb +20 -0
  24. data/lib/yt/associations/playlist_items.rb +26 -0
  25. data/lib/yt/associations/playlists.rb +22 -0
  26. data/lib/yt/associations/ratings.rb +39 -0
  27. data/lib/yt/associations/snippets.rb +20 -0
  28. data/lib/yt/associations/statuses.rb +14 -0
  29. data/lib/yt/associations/subscriptions.rb +38 -0
  30. data/lib/yt/associations/user_infos.rb +21 -0
  31. data/lib/yt/associations/videos.rb +14 -0
  32. data/lib/yt/collections/annotations.rb +43 -0
  33. data/lib/yt/collections/base.rb +13 -0
  34. data/lib/yt/collections/channels.rb +32 -0
  35. data/lib/yt/collections/details_sets.rb +32 -0
  36. data/lib/yt/collections/playlist_items.rb +50 -0
  37. data/lib/yt/collections/playlists.rb +56 -0
  38. data/lib/yt/collections/ratings.rb +32 -0
  39. data/lib/yt/collections/snippets.rb +38 -0
  40. data/lib/yt/collections/subscriptions.rb +67 -0
  41. data/lib/yt/collections/user_infos.rb +41 -0
  42. data/lib/yt/collections/videos.rb +32 -0
  43. data/lib/yt/config.rb +55 -0
  44. data/lib/yt/models/account.rb +68 -0
  45. data/lib/yt/models/annotation.rb +137 -0
  46. data/lib/yt/models/base.rb +11 -0
  47. data/lib/yt/models/channel.rb +17 -0
  48. data/lib/yt/models/configuration.rb +29 -0
  49. data/lib/yt/models/description.rb +98 -0
  50. data/lib/yt/models/details_set.rb +31 -0
  51. data/lib/yt/models/playlist.rb +65 -0
  52. data/lib/yt/models/playlist_item.rb +42 -0
  53. data/lib/yt/models/rating.rb +28 -0
  54. data/lib/yt/models/snippet.rb +48 -0
  55. data/lib/yt/models/status.rb +26 -0
  56. data/lib/yt/models/subscription.rb +35 -0
  57. data/lib/yt/models/user_info.rb +66 -0
  58. data/lib/yt/models/video.rb +16 -0
  59. data/lib/yt/utils/request.rb +85 -0
  60. data/lib/yt/version.rb +3 -0
  61. data/spec/associations/device_auth/channels_spec.rb +10 -0
  62. data/spec/associations/device_auth/details_sets_spec.rb +19 -0
  63. data/spec/associations/device_auth/playlist_items_spec.rb +42 -0
  64. data/spec/associations/device_auth/playlists_spec.rb +42 -0
  65. data/spec/associations/device_auth/ratings_spec.rb +30 -0
  66. data/spec/associations/device_auth/snippets_spec.rb +30 -0
  67. data/spec/associations/device_auth/subscriptions_spec.rb +27 -0
  68. data/spec/associations/device_auth/user_infos_spec.rb +10 -0
  69. data/spec/associations/device_auth/videos_spec.rb +22 -0
  70. data/spec/associations/no_auth/annotations_spec.rb +15 -0
  71. data/spec/associations/server_auth/channels_spec.rb +2 -0
  72. data/spec/associations/server_auth/details_sets_spec.rb +18 -0
  73. data/spec/associations/server_auth/playlist_items_spec.rb +17 -0
  74. data/spec/associations/server_auth/playlists_spec.rb +17 -0
  75. data/spec/associations/server_auth/ratings_spec.rb +2 -0
  76. data/spec/associations/server_auth/snippets_spec.rb +28 -0
  77. data/spec/associations/server_auth/subscriptions_spec.rb +2 -0
  78. data/spec/associations/server_auth/user_infos_spec.rb +2 -0
  79. data/spec/associations/server_auth/videos_spec.rb +20 -0
  80. data/spec/collections/annotations_spec.rb +6 -0
  81. data/spec/collections/channels_spec.rb +6 -0
  82. data/spec/collections/details_sets_spec.rb +6 -0
  83. data/spec/collections/playlist_items_spec.rb +23 -0
  84. data/spec/collections/playlists_spec.rb +26 -0
  85. data/spec/collections/ratings_spec.rb +6 -0
  86. data/spec/collections/snippets_spec.rb +6 -0
  87. data/spec/collections/subscriptions_spec.rb +30 -0
  88. data/spec/collections/user_infos_spec.rb +6 -0
  89. data/spec/collections/videos_spec.rb +6 -0
  90. data/spec/models/annotation_spec.rb +131 -0
  91. data/spec/models/channel_spec.rb +13 -0
  92. data/spec/models/description_spec.rb +94 -0
  93. data/spec/models/details_set_spec.rb +23 -0
  94. data/spec/models/playlist_item_spec.rb +32 -0
  95. data/spec/models/playlist_spec.rb +52 -0
  96. data/spec/models/rating_spec.rb +13 -0
  97. data/spec/models/snippet_spec.rb +66 -0
  98. data/spec/models/status_spec.rb +42 -0
  99. data/spec/models/subscription_spec.rb +37 -0
  100. data/spec/models/user_info_spec.rb +69 -0
  101. data/spec/models/video_spec.rb +13 -0
  102. data/spec/spec_helper.rb +15 -0
  103. data/spec/support/device_app.rb +16 -0
  104. data/spec/support/server_app.rb +10 -0
  105. data/yt.gemspec +30 -0
  106. metadata +209 -17
@@ -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
@@ -0,0 +1,2 @@
1
+ require 'yt/config'
2
+ require 'yt/models/account'
@@ -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