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 +4 -4
- data/README.md +27 -7
- data/lib/googol/google_account.rb +0 -2
- data/lib/googol/requestable.rb +14 -7
- data/lib/googol/version.rb +1 -1
- data/lib/googol/youtube_account.rb +6 -118
- data/lib/googol/youtube_account/playlist_items.rb +92 -0
- data/lib/googol/youtube_account/playlists.rb +196 -0
- data/lib/googol/youtube_account/subscriptions.rb +145 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fded34de11c00d46011c9876c65ad8b334a0559a
|
4
|
+
data.tar.gz: aba9dbae4d56919578678e16ad8984c826ab6c12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
#
|
39
|
-
account.subscribe_to! channel_id: 'UCxO1tY8h1AhOz0T4ENwmpow'
|
38
|
+
# subscribe to Fullscreen’s channel
|
39
|
+
account.subscribe_to! channel_id: 'UCxO1tY8h1AhOz0T4ENwmpow'
|
40
40
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
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`,
|
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.
|
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
|
#
|
data/lib/googol/requestable.rb
CHANGED
@@ -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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
data/lib/googol/version.rb
CHANGED
@@ -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.
|
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-
|
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:
|