googol 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 020552842d5bed2faefa55f19aa03c79256f63e5
4
- data.tar.gz: 2e6b5289e68ee0a3d3549f6c8befec0130f1821d
3
+ metadata.gz: fded34de11c00d46011c9876c65ad8b334a0559a
4
+ data.tar.gz: aba9dbae4d56919578678e16ad8984c826ab6c12
5
5
  SHA512:
6
- metadata.gz: 3dad077e59925dd1265286f7c0abcf6524b5c6830720c92657d11ee59e0c5b33898236af2a84d56cbfad9f2111154984343d8b3b6b1b1712f485546d0983af32
7
- data.tar.gz: 863c175bd7987a5dc24067556bf3c1a3238ca64c81cbf3ed6cd06e87478a1ff8ea04e07c9b0659ffcff7afac6dfb7ab648d3414d6dba81083f95a2c1f8df6214
6
+ metadata.gz: 6edc7788c2df4fb07eaef376ae06d35d07d15fda5ab7a6dc677bdd071439ff761d2f036fc3ec7a1c5c6d816ce7de119a0940176e511bc0e417c77bcada868d19
7
+ data.tar.gz: a641b0baa915b6a46d1345afb0557f2053f6f7757eec4eef24818ae31506ecae7e2be46766ab1fd78dbe261bcc5b1f307cd2ab865ed89df9ce91ade4734c74a9
data/README.md CHANGED
@@ -35,12 +35,32 @@ account = Googol::YoutubeAccount.new auth_params
35
35
  # like the video 'Tongue' by R.E.M.
36
36
  account.like! video_id: 'Kd5M17e7Wek'
37
37
 
38
- # subscribes to Fullscreen’s channel
39
- account.subscribe_to! channel_id: 'UCxO1tY8h1AhOz0T4ENwmpow' # => subscribes to Fullscreen’s channel
38
+ # subscribe to Fullscreen’s channel
39
+ account.subscribe_to! channel_id: 'UCxO1tY8h1AhOz0T4ENwmpow'
40
40
 
41
- # add the video 'Tongue' to your 'Favorite Music Videos' playlist
42
- playlist_id = account.find_or_create_playlist_by title: 'Favorite Music Videos'
43
- account.add_to! playlist_id: playlist_id, video_id: 'Kd5M17e7Wek' # => adds 'Tongue' to your 'Favorite Music Videos' playlist
41
+ # unsubscribe from Fullscreen’s channel
42
+ account.unsubscribe_from! channel_id: 'UCxO1tY8h1AhOz0T4ENwmpow'
43
+
44
+ # create a playlist called "Fullscreen"
45
+ account.create_playlist! title: "Fullscreen"
46
+
47
+ # find the first playlist with "Fullscreen" in the title
48
+ account.find_playlist_by title: /Fullscreen/i
49
+
50
+ # delete all playlists with "Fullscreen" in the description
51
+ account.delete_playlists! description: /Fullscreen/i
52
+
53
+ # add the video 'Tongue' by R.E.M. to your 'Fullscreen' playlist
54
+ playlist_id = account.find_or_create_playlist_by title: 'Fullscreen'
55
+ account.add_item_to! playlist_id, video_id: 'Kd5M17e7Wek'
56
+
57
+ # add a list of 3 videos by R.E.M. to your 'Fullscreen' playlist
58
+ playlist_id = account.find_or_create_playlist_by title: 'Fullscreen'
59
+ account.add_item_to! playlist_id, ['Kd5M17e7Wek', '3D1zWC1hs0', 'G2BCrByfZ74']
60
+
61
+ # remove all the items from your 'Fullscreen' playlist
62
+ playlist_id = account.find_or_create_playlist_by title: 'Fullscreen'
63
+ account.remove_all_items_from! playlist_id
44
64
  ```
45
65
 
46
66
  The full documentation is available at [rubydoc.info](http://rubydoc.info/github/fullscreeninc/googol/master/frames).
@@ -58,7 +78,7 @@ Use `Googol::GoogleAccount` to send and retrieve data to Google,
58
78
  impersonating an existing Google account.
59
79
 
60
80
  Available instance methods are `id`, `email`, `verified_email`, `name`,
61
- `given_name`, `family_name`, `link`, `picture`, `gender`, `locale`, and `hd`.
81
+ `given_name`, `family_name`, `link`, `picture`, `gender`, and `locale`.
62
82
 
63
83
  These methods require user authentication (see below).
64
84
 
@@ -145,7 +165,7 @@ To install on your system, run
145
165
 
146
166
  To use inside a bundled Ruby project, add this line to the Gemfile:
147
167
 
148
- gem 'googol', '~> 0.2.1'
168
+ gem 'googol', '~> 0.3.0'
149
169
 
150
170
  Since the gem follows [Semantic Versioning](http://semver.org),
151
171
  indicating the full version in your Gemfile (~> *major*.*minor*.*patch*)
@@ -33,7 +33,6 @@ module Googol
33
33
  # * :picture [String] The URL of the account’s profile picture.
34
34
  # * :gender [String] The account’s gender
35
35
  # * :locale [String] The account’s preferred locale.
36
- # * :hd [String] The hosted domain name for the accounts’s Google Apps.
37
36
  def info
38
37
  @info ||= request! auth: credentials[:access_token],
39
38
  host: 'https://www.googleapis.com', path: '/oauth2/v2/userinfo'
@@ -59,7 +58,6 @@ module Googol
59
58
  attribute :picture
60
59
  attribute :gender
61
60
  attribute :locale
62
- attribute :hd
63
61
 
64
62
  # Set the scopes to grant access to Google user profile and email
65
63
  #
@@ -20,13 +20,20 @@ module Googol
20
20
  http = Net::HTTP.new url.host, url.port
21
21
  http.use_ssl = true
22
22
  request = case params[:method]
23
- when :post then
24
- if params[:json]
25
- Net::HTTP::Post.new params[:path], initheader = {'Content-Type' =>'application/json'}
26
- else
27
- Net::HTTP::Post.new params[:path]
28
- end
29
- else Net::HTTP::Get.new params[:path]
23
+ when :post
24
+ if params[:json]
25
+ Net::HTTP::Post.new params[:path], initheader = {'Content-Type' =>'application/json'}
26
+ else
27
+ Net::HTTP::Post.new params[:path]
28
+ end
29
+ when :delete
30
+ Net::HTTP::Delete.new params[:path]
31
+ when :put
32
+ if params[:json]
33
+ Net::HTTP::Put.new params[:path], initheader = {'Content-Type' =>'application/json'}
34
+ end
35
+ else
36
+ Net::HTTP::Get.new params[:path]
30
37
  end
31
38
  if params[:json]
32
39
  request.body = params[:body].to_json
@@ -1,3 +1,3 @@
1
1
  module Googol
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -1,5 +1,8 @@
1
1
  require 'googol/authenticable'
2
2
  require 'googol/readable'
3
+ require 'googol/youtube_account/playlists'
4
+ require 'googol/youtube_account/playlist_items'
5
+ require 'googol/youtube_account/subscriptions'
3
6
 
4
7
  module Googol
5
8
  # Provides read & write access to a Youtube account (also known as Channel).
@@ -19,6 +22,9 @@ module Googol
19
22
  class YoutubeAccount
20
23
  include Authenticable
21
24
  include Readable
25
+ include Playlists
26
+ include PlaylistItems
27
+ include Subscriptions
22
28
  # Return the profile info of a Youtube account/channel.
23
29
  #
24
30
  # @see https://developers.google.com/youtube/v3/docs/channels#resource
@@ -55,126 +61,8 @@ module Googol
55
61
  youtube_request! path: path, method: :post, code: 204
56
62
  end
57
63
 
58
- # Subscribe a Youtube account to a channel
59
- #
60
- # @param [Hash] target The target of the 'subscribe' activity
61
- # @option target [String] :channel_id The ID of the channel to subscribe to
62
- #
63
- # @see https://developers.google.com/youtube/v3/docs/subscriptions/insert
64
- #
65
- def subscribe_to!(target = {})
66
- channel_id = fetch! target, :channel_id
67
- youtube_request! path: '/subscriptions?part=snippet', json: true,
68
- method: :post, body: {snippet: {resourceId: {channelId: channel_id}}}
69
- rescue Googol::RequestError => e
70
- raise e unless e.to_s =~ /subscriptionDuplicate/
71
- end
72
-
73
- # Return the first playlist of the Youtube account matching the attributes.
74
- # If such a playlist is not found, it gets created and yielded to the block.
75
- #
76
- # @note Inspired by Ruby on Rails’ find_or_create_by
77
- #
78
- # @param [Hash] target The target of the 'add_to' activity
79
- # @option target [String] :video_id The ID of the video to add
80
- # @option target [String] :playlist_id The ID of the playlist to add to
81
- #
82
- # @see https://developers.google.com/youtube/v3/docs/playlistItems/insert
83
- #
84
- def find_or_create_playlist_by(attributes = {}, &block)
85
- find_playlist_by(attributes) || create_playlist!(attributes, &block)
86
- end
87
-
88
- # Adds a video to a playlist as a Youtube account
89
- #
90
- # @param [Hash] target The target of the 'add_to' activity
91
- # @option target [String] :video_id The ID of the video to add
92
- # @option target [String] :playlist_id The ID of the playlist to add to
93
- #
94
- # @see https://developers.google.com/youtube/v3/docs/playlistItems/insert
95
- #
96
- def add_to!(target = {})
97
- video_id, playlist_id = fetch! target, :video_id, :playlist_id
98
- resource = {videoId: video_id, kind: 'youtube#video'}
99
- youtube_request! method: :post, json: true,
100
- path: '/playlistItems?part=snippet',
101
- body: {snippet: {playlistId: playlist_id, resourceId: resource}}
102
- end
103
-
104
64
  private
105
65
 
106
- # Creates a playlist for a Youtube account and yield it to the block
107
- #
108
- # @param [Hash] attributes The attributes of the new playlist
109
- # @option attributes [String] :title Title of the playlist
110
- # @option attributes [String] :description Description of the playlist
111
- #
112
- # @return [String] The ID of the playlist
113
- #
114
- # @see https://developers.google.com/youtube/v3/docs/playlists/insert
115
- #
116
- def create_playlist!(attrs = {}, &block)
117
- snippet = {title: attrs[:title], description: attrs[:description]}
118
- playlist = youtube_request! json: true, method: :post,
119
- path: '/playlists?part=snippet,status',
120
- body: {snippet: snippet, status: {privacyStatus: :public}}
121
- yield playlist if block_given?
122
- playlist[:id]
123
- end
124
-
125
- # Return the first playlist of the Youtube account matching the attributes.
126
- #
127
- # @param [Hash] filters The filter to query the list of playlists with.
128
- # @option title [String or Regex] Filter on the title
129
- # @option description [String or Regex] Filter on the description
130
- #
131
- # @example account.find_playlist_by title: 'Title' # => 'e45fXDsdsdsd'
132
- # @example account.find_playlist_by description: 'xxxx' # => nil
133
- #
134
- # @note Google API does not have a "search" endpoint, therefore we have
135
- # to scan the list of playlist page by page, limiting at 10 pages to
136
- # prevent this function from running forever (50 playlists per page).
137
- #
138
- # @return [String or nil] The ID of the playlist (or nil if not found)
139
- def find_playlist_by(filters = {})
140
- page = filters.delete(:page) || 1
141
-
142
- path = "/playlists?part=id,snippet&mine=true"
143
- path << "&maxResults=#{filters.delete(:max) || 50 }"
144
- path << "&pageToken=#{filters.delete :token}" if filters[:token]
145
-
146
- response = youtube_request! path: path
147
-
148
- if playlist = response[:items].find{|p| playlist_matches? p, filters}
149
- playlist[:id]
150
- elsif page < 10 && token = response[:nextPageToken]
151
- find_playlist_by filters.merge page: page, token: token
152
- end
153
- end
154
-
155
- # Checks if the playlist matches the given filters.
156
- #
157
- # @param [Hash] filters The filter to query the list of playlists with.
158
- # @option title [String or Regex] Filter on the title
159
- # @option description [String or Regex] Filter on the description
160
- #
161
- # @note String filters match when they are included in the specified field
162
- # Regex filters match when they match as a Regex against the field
163
- #
164
- # @example Given a playlist x with title: 'A title', description: 'Example'
165
- # playlist_matches? x, title: 'tit' # => true
166
- # playlist_matches? x, title: 'TIT' # => false
167
- # playlist_matches? x, title: /^TIT/i # => true
168
- # playlist_matches? x, title: /^TIT/i, description: 'Ex' # => true
169
- # playlist_matches? x, title: /^TIT/i, description: 'xxx' # => false
170
- #
171
- # @return [Boolean] Whether the playlist matches the filters
172
- def playlist_matches?(playlist, filters = {})
173
- filters.all? do |attribute, string_or_regex|
174
- playlist[:snippet].fetch(attribute, '')[string_or_regex]
175
- end
176
- end
177
-
178
66
  # Fetch keys from a hash, raising a custom exception when not found
179
67
  def fetch!(hash, *keys)
180
68
  values = keys.map do |key|
@@ -0,0 +1,92 @@
1
+ module Googol
2
+ # Separate module to group YoutubeAccount methods related to playlist items.
3
+ module PlaylistItems
4
+ # Add an item to a Youtube playlist.
5
+ #
6
+ # @param [String] playlist_id The ID of the playlist to add videos to
7
+ # @param [Hash] target The item to add to the playlist
8
+ # @option target [String] :video_id The ID of the item (when item is video)
9
+ #
10
+ # @note Google API allows other types of items to be added to a playlist
11
+ # but for now we are just using videos. If you need other types,
12
+ # we can just expand this method.
13
+ #
14
+ # @see https://developers.google.com/youtube/v3/docs/playlistItems/insert
15
+ #
16
+ def add_item_to!(playlist_id, item = {})
17
+ resource = {videoId: fetch!(item, :video_id), kind: 'youtube#video'}
18
+ youtube_request! method: :post, json: true,
19
+ path: '/playlistItems?part=snippet',
20
+ body: {snippet: {playlistId: playlist_id, resourceId: resource}}
21
+ end
22
+
23
+ # Remove all items from a Youtube playlist.
24
+ #
25
+ # @param [String] playlist_id The ID of the playlist to add videos to
26
+ # @param [Hash] filters The filter to remove the playlist items by.
27
+ #
28
+ # @see https://developers.google.com/youtube/v3/docs/playlistItems/delete
29
+ #
30
+ def remove_all_items_from!(playlist_id, filters = {})
31
+ items_of(playlist_id, filters).map do |item_id|
32
+ remove_item_from! playlist_id, item_id
33
+ end
34
+ end
35
+
36
+ # Adds an array of videos to a Youtube playlist.
37
+ #
38
+ # @param [String] playlist_id The ID of the playlist to add videos to
39
+ # @param [Array of String] video_ids The IDs of the videos to add
40
+ #
41
+ # @see https://developers.google.com/youtube/v3/docs/playlistItems/insert
42
+ #
43
+ def add_videos_to!(playlist_id, video_ids = [])
44
+ video_ids.map{|video_id| add_item_to! playlist_id, video_id: video_id}
45
+ end
46
+
47
+ private
48
+
49
+ # List all items of a Youtube playlist.
50
+ #
51
+ # @param [String] playlist_id The ID of the playlist to add videos to
52
+ #
53
+ # @return [Array of Hashes] Items of the playlist
54
+ #
55
+ # @note Google API does not have a "search" endpoint, therefore we have
56
+ # to scan the list of playlist items page by page, limiting at 10 pages to
57
+ # prevent this function from running forever (50 items per page).
58
+ #
59
+ # @see https://developers.google.com/youtube/v3/docs/playlistItems/list
60
+ #
61
+ def items_of(playlist_id, filters = {})
62
+ page = filters.delete(:page) || 1
63
+
64
+ path = "/playlistItems?part=id,snippet&playlistId=#{playlist_id}"
65
+ path << "&maxResults=#{filters.delete(:max) || 50 }"
66
+ path << "&pageToken=#{filters.delete :token}" if filters[:token]
67
+
68
+ response = youtube_request! path: path
69
+ items = response[:items].map{|item| item[:id]}
70
+
71
+ more_items = if page < 10 && token = response[:nextPageToken]
72
+ items_of playlist_id, page: page + 1, token: token
73
+ else
74
+ []
75
+ end
76
+
77
+ items + more_items
78
+ end
79
+
80
+ # Remove an item from a playlist.
81
+ #
82
+ # @param [String] playlist_id The ID of the playlist to remove items from
83
+ # @param [String] playlist_item_id The ID of the item to remove
84
+ #
85
+ # @see https://developers.google.com/youtube/v3/docs/playlistItems/delete
86
+ #
87
+ def remove_item_from!(playlist_id, item_id)
88
+ youtube_request! method: :delete, code: 204,
89
+ path: "/playlistItems?id=#{item_id}"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,196 @@
1
+ module Googol
2
+ # Separate module to group YoutubeAccount methods related to playlists.
3
+ module Playlists
4
+ # Creates a playlist for a Youtube account
5
+ #
6
+ # @param [Hash] attributes The attributes of the new playlist
7
+ # @option attributes [String] :title Title of the playlist
8
+ # @option attributes [String] :description Description of the playlist
9
+ #
10
+ # @return [String] The ID of the playlist
11
+ #
12
+ # @see https://developers.google.com/youtube/v3/docs/playlists/insert
13
+ #
14
+ def create_playlist!(attrs = {})
15
+ snippet = {title: attrs[:title], description: attrs[:description]}
16
+ playlist = youtube_request! json: true, method: :post,
17
+ path: '/playlists?part=snippet,status',
18
+ body: {snippet: snippet, status: {privacyStatus: :public}}
19
+ playlist[:id]
20
+ end
21
+
22
+ # Return the first playlist of the Youtube account matching the attributes.
23
+ #
24
+ # @param [Hash] filters The filter to query the list of playlists with.
25
+ # @option title [String or Regex] Filter on the title
26
+ # @option description [String or Regex] Filter on the description
27
+ #
28
+ # @example account.find_playlist_by title: 'Title' # => 'e45fXDsdsdsd'
29
+ # @example account.find_playlist_by description: 'xxxx' # => nil
30
+ #
31
+ # @note Google API does not have a "search" endpoint, therefore we have
32
+ # to scan the list of playlist page by page, limiting at 10 pages to
33
+ # prevent this function from running forever (50 playlists per page).
34
+ #
35
+ # @return [String or nil] The ID of the playlist (or nil if not found)
36
+ def find_playlist_by(filters = {})
37
+ page = filters.delete(:page) || 1
38
+
39
+ path = "/playlists?part=id,snippet&mine=true"
40
+ path << "&maxResults=#{filters.delete(:max) || 50 }"
41
+ path << "&pageToken=#{filters.delete :token}" if filters[:token]
42
+
43
+ response = youtube_request! path: path
44
+
45
+ if playlist = response[:items].find{|p| playlist_matches? p, filters}
46
+ playlist[:id]
47
+ elsif page < 10 && token = response[:nextPageToken]
48
+ find_playlist_by filters.merge page: page + 1, token: token
49
+ end
50
+ end
51
+
52
+ # Return the first playlist of the Youtube account matching the attributes
53
+ # or creates one if none is found
54
+ #
55
+ # @param [Hash] filters The filter to query the list of playlists with.
56
+ # @option title [String or Regex] Filter on the title
57
+ # @option description [String or Regex] Filter on the description
58
+ #
59
+ # @example account.find_playlist_by title: 'Title' # => 'e45fXDsdsdsd'
60
+ # @example account.find_playlist_by description: 'xxxx' # => nil
61
+ #
62
+ # @note Google API does not have a "search" endpoint, therefore we have
63
+ # to scan the list of playlist page by page, limiting at 10 pages to
64
+ # prevent this function from running forever (50 playlists per page).
65
+ #
66
+ # @return [String or nil] The ID of the playlist (or nil if not found)
67
+ def find_or_create_playlist_by(filters = {})
68
+ find_playlist_by(filters) || create_playlist!(filters)
69
+ end
70
+
71
+ # Updates a playlist for a Youtube account
72
+ #
73
+ # @param [String] playlist_id The ID of the playlist to update
74
+ # @param [Hash] attributes The attributes to update in the playlist
75
+ # @option attributes [String] :title Title of the playlist
76
+ # @option attributes [String] :description Description of the playlist
77
+ # @option attributes [Boolean] :public Should the playlist be public
78
+ #
79
+ # @note For some unknown reason, Google API *requires* to pass the new
80
+ # title, even if it does not change. Therefore the attrs[:title]
81
+ # *is required*, otherwise Google API fails (sigh)
82
+
83
+ # @return [String] The ID of the playlist
84
+ #
85
+ # @see https://developers.google.com/youtube/v3/docs/playlists/update
86
+ #
87
+ def update_playlist!(playlist_id, attrs = {})
88
+ body = {id: playlist_id}
89
+ parts = []
90
+
91
+ if attrs.key? :title
92
+ body[:snippet] = {title: attrs[:title]}
93
+ parts << 'snippet'
94
+ else
95
+ raise RequestError, "Cannot update a playlist without a title"
96
+ end
97
+
98
+ if attrs.key? :description
99
+ body[:snippet][:description] = attrs[:description]
100
+ end
101
+
102
+ if attrs.key? :public
103
+ body[:status] = {privacyStatus: attrs[:public] ? 'public' : 'private'}
104
+ parts << 'status'
105
+ end
106
+
107
+ playlist = youtube_request! json: true, method: :put,
108
+ path: "/playlists?part=#{parts.join ','}", body: body
109
+ playlist[:id]
110
+ end
111
+
112
+ # Delete all the playlists of the Youtube account matching the filters.
113
+ #
114
+ # @param [Hash] filters The filter to query the list of playlists with.
115
+ # @option title [String or Regex] Filter on the title
116
+ # @option description [String or Regex] Filter on the description
117
+ #
118
+ # @example account.delete_playlists! title: 'Title'
119
+ # @example account.delete_playlists! description: /desc/
120
+ #
121
+ def delete_playlists!(filters = {})
122
+ playlists(filters).map{|playlist_id| delete_playlist! playlist_id}
123
+ end
124
+
125
+ private
126
+
127
+ # List all the playlists of the Youtube account matching the filters.
128
+ #
129
+ # @param [Hash] filters The filter to query the list of playlists with.
130
+ # @option title [String or Regex] Filter on the title
131
+ # @option description [String or Regex] Filter on the description
132
+ #
133
+ # @note Google API does not have a "search" endpoint, therefore we have
134
+ # to scan the list of playlist page by page, limiting at 10 pages to
135
+ # prevent this function from running forever (50 playlists per page).
136
+ #
137
+ # @example account.playlists title: 'Title'
138
+ # @example account.playlists description: /desc/
139
+ #
140
+ # @return [Array of Strings] The IDs of the playlists
141
+ def playlists(filters = {})
142
+ page = filters.delete(:page) || 1
143
+
144
+ path = "/playlists?part=id,snippet&mine=true"
145
+ path << "&maxResults=#{filters.delete(:max) || 50 }"
146
+ path << "&pageToken=#{filters.delete :token}" if filters[:token]
147
+
148
+ response = youtube_request! path: path
149
+ playlists = response[:items].select do |p|
150
+ playlist_matches? p, filters
151
+ end.map{|p| p[:id]}
152
+
153
+ more_playlists = if page < 10 && token = response[:nextPageToken]
154
+ playlists filters.merge(page: page + 1, token: token)
155
+ else
156
+ []
157
+ end
158
+
159
+ playlists + more_playlists
160
+ end
161
+
162
+ # Deletes a playlist of a Youtube account.
163
+ #
164
+ # @param [String] playlist_id The ID of the playlist to delete
165
+ #
166
+ # @see https://developers.google.com/youtube/v3/docs/playlists/delete
167
+ #
168
+ def delete_playlist!(playlist_id)
169
+ youtube_request! method: :delete, code: 204,
170
+ path: "/playlists?id=#{playlist_id}"
171
+ end
172
+
173
+ # Checks if the playlist matches the given filters.
174
+ #
175
+ # @param [Hash] filters The filter to query the list of playlists with.
176
+ # @option title [String or Regex] Filter on the title
177
+ # @option description [String or Regex] Filter on the description
178
+ #
179
+ # @note String filters match when they are included in the specified field
180
+ # Regex filters match when they match as a Regex against the field
181
+ #
182
+ # @example Given a playlist x with title: 'A title', description: 'Example'
183
+ # playlist_matches? x, title: 'tit' # => true
184
+ # playlist_matches? x, title: 'TIT' # => false
185
+ # playlist_matches? x, title: /^TIT/i # => true
186
+ # playlist_matches? x, title: /^TIT/i, description: 'Ex' # => true
187
+ # playlist_matches? x, title: /^TIT/i, description: 'xxx' # => false
188
+ #
189
+ # @return [Boolean] Whether the playlist matches the filters
190
+ def playlist_matches?(playlist, filters = {})
191
+ filters.all? do |attribute, string_or_regex|
192
+ playlist[:snippet].fetch(attribute, '')[string_or_regex]
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,145 @@
1
+ module Googol
2
+ # Separate module to group YoutubeAccount methods related to subscriptions.
3
+ module Subscriptions
4
+ # Subscribe a Youtube account to a channel. If the account is already
5
+ # subscribed, raise an error.
6
+ #
7
+ # @param [Hash] channel The channel for the 'subscribe' activity
8
+ # @option channel [String] :channel_id The ID of the channel to subscribe to
9
+ #
10
+ # @return [String] The ID of the new channel subscription
11
+ #
12
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/insert
13
+ #
14
+ def subscribe_to!(channel = {})
15
+ channel_id = fetch! channel, :channel_id
16
+ youtube_request! path: '/subscriptions?part=snippet', json: true,
17
+ method: :post, body: {snippet: {resourceId: {channelId: channel_id}}}
18
+ end
19
+
20
+ # Subscribe a Youtube account to a channel. If the account is already
21
+ # subscribed, return the existing channel subscription ID.
22
+ #
23
+ # @param [Hash] channel The channel for the 'subscribe' activity
24
+ # @option channel [String] :channel_id The ID of the channel to subscribe to
25
+ #
26
+ # @return [String] The ID of the new or existing channel subscription
27
+ #
28
+ # @raise [Googol::RequestError] if the subscription does not go through,
29
+ # unless the error is that the user is already subscribed to the channel.
30
+ #
31
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/insert
32
+ #
33
+ def subscribe_to(channel = {})
34
+ subscribe_to! channel
35
+ rescue Googol::RequestError => e
36
+ if e.to_s =~ /subscriptionDuplicate/
37
+ find_subscription_by channel
38
+ else
39
+ raise e
40
+ end
41
+ end
42
+
43
+ # Unsubscribe a Youtube account from a channel. If the account is not
44
+ # subscribed, raise an error.
45
+ #
46
+ # @param [Hash] channel The channel for the 'subscribe' activity
47
+ # @option channel [String] :channel_id The ID of the channel to unsubscribe from
48
+ #
49
+ # @raise [Googol::RequestError] if the unsubscription does not go through
50
+ #
51
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/delete
52
+ #
53
+ def unsubscribe_from!(channel = {})
54
+ delete_subscription!(find_subscription_by channel)
55
+ end
56
+
57
+ # Unsubscribe a Youtube account from a channel. If the account is not
58
+ # subscribed, ignore the request.
59
+ #
60
+ # @param [Hash] channel The channel for the 'subscribe' activity
61
+ # @option channel [String] :channel_id The ID of the channel to unsubscribe from
62
+ #
63
+ # @raise [Googol::RequestError] if the unsubscription does not go through,
64
+ # unless the error is that the user was not subscribed to the channel.
65
+ #
66
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/delete
67
+ #
68
+ def unsubscribe_from(channel = {})
69
+ unsubscribe_from! channel
70
+ rescue Googol::RequestError => e
71
+ if e.to_s =~ /subscriptionNotFound/
72
+ true
73
+ else
74
+ raise e
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ # Return the first subscription of the Youtube account for the given filters.
81
+ #
82
+ # @param [Hash] filters The channel subscribed to (plus private options)
83
+ # @option filters [String] :channel_id The ID of the channel subscribed to
84
+ # @option filters [Integer] :retries Number of retries is subscription not found
85
+ #
86
+ # @note Google API does not have a "search" endpoint, therefore we have
87
+ # to scan the list of subscriptions page by page, limiting at 10 pages to
88
+ # prevent this function from running forever (50 subscriptions per page).
89
+ #
90
+ # @note Google API must have some caching layer by which if we try to
91
+ # delete a subscription that we just created, we encounter an error.
92
+ # To overcome this, if we don't find a playlist, we can pass a +retries+
93
+ # parameter to try the same request at intervals of 5 seconds.
94
+ #
95
+ # @return [String or nil] The ID of the subscription (or nil if not found)
96
+ def find_subscription_by(filters = {})
97
+ page = filters.delete(:page) || 1
98
+
99
+ path = "/subscriptions?part=id,snippet&mine=true"
100
+ path << "&maxResults=#{filters.delete(:max) || 50 }"
101
+ path << "&pageToken=#{filters.delete :token}" if filters[:token]
102
+
103
+ response = youtube_request! path: path
104
+ items = response[:items]
105
+
106
+ if subscription = items.find{|s| subscription_belongs_to? s, filters}
107
+ subscription[:id]
108
+ elsif page < 10 && token = response[:nextPageToken]
109
+ find_subscription_by filters.merge page: page + 1, token: token
110
+ elsif filters.fetch(:retries, 0) > 0
111
+ sleep 5 * filters[:retries]
112
+ find_subscription_by filters.merge retries: filters[:retries] - 1
113
+ end
114
+ end
115
+
116
+ # Checks if the subscription belongs to the given channel.
117
+ #
118
+ # @param [Hash] channel The channel the subscription might belong to
119
+ # @option channel_id [String] The ID of the channel
120
+ #
121
+ # @example Given a subscription x with channel_id: 'ABCDE'
122
+ # subscription_belongs_to? x, channel_id: 'ABCDE' # => true
123
+ # subscription_belongs_to? x, channel_id: '12345' # => false
124
+ #
125
+ # @return [Boolean] Whether the subscription belongs to the channel
126
+ def subscription_belongs_to?(subscription, channel = {})
127
+ channel_id = fetch! channel, :channel_id
128
+ subscription[:snippet][:resourceId][:channelId] == channel_id
129
+ end
130
+
131
+ # Delete a channel subscription for the Youtube account. If the
132
+ # subscription does not exist, raise an error.
133
+ #
134
+ # @param [String] subscription_id The channel subscription ID to delete
135
+ #
136
+ # @raise [Googol::RequestError] if the unsubscription does not go through
137
+ #
138
+ # @see https://developers.google.com/youtube/v3/docs/subscriptions/delete
139
+ #
140
+ def delete_subscription!(subscription_id, retries = 5)
141
+ youtube_request! method: :delete, code: 204,
142
+ path: "/subscriptions?id=#{subscription_id}"
143
+ end
144
+ end
145
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: googol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-10 00:00:00.000000000 Z
11
+ date: 2014-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -98,6 +98,9 @@ files:
98
98
  - lib/googol/server_tokens.rb
99
99
  - lib/googol/version.rb
100
100
  - lib/googol/youtube_account.rb
101
+ - lib/googol/youtube_account/playlist_items.rb
102
+ - lib/googol/youtube_account/playlists.rb
103
+ - lib/googol/youtube_account/subscriptions.rb
101
104
  - lib/googol/youtube_resource.rb
102
105
  homepage: https://github.com/fullscreeninc/googol
103
106
  licenses: