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,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,13 @@
1
+ require 'yt/actions/delete_all'
2
+ require 'yt/actions/insert'
3
+ require 'yt/actions/list'
4
+
5
+ module Yt
6
+ module Collections
7
+ class Base
8
+ include Actions::DeleteAll
9
+ include Actions::Insert
10
+ include Actions::List
11
+ end
12
+ end
13
+ 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