googol 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: