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 +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:
|