yt_analytics 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +347 -0
- data/lib/yt_analytics/client.rb +146 -0
- data/lib/yt_analytics/middleware/faraday_authheader.rb +24 -0
- data/lib/yt_analytics/middleware/faraday_oauth.rb +21 -0
- data/lib/yt_analytics/middleware/faraday_oauth2.rb +13 -0
- data/lib/yt_analytics/middleware/faraday_yt_analytics.rb +31 -0
- data/lib/yt_analytics/model/temporal_metrics.rb +25 -0
- data/lib/yt_analytics/parser.rb +77 -0
- data/lib/yt_analytics/request/authentication.rb +144 -0
- data/lib/yt_analytics/version.rb +4 -0
- data/lib/yt_analytics.rb +71 -0
- data/yt_analytics.gemspec +28 -0
- metadata +155 -0
data/README.rdoc
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
== YOUTUBE_IT {<img src="https://secure.travis-ci.org/kylejginavan/youtube_it.png"/>}[http://travis-ci.org/kylejginavan/youtube_it]
|
2
|
+
|
3
|
+
== DONATION
|
4
|
+
|
5
|
+
YOUTUBE_IT is developed by many contributors who are passioned about opensource projects
|
6
|
+
and selflessly donate their time and work. Following that spirit your donations to this project
|
7
|
+
will be destined to the Tuquito Libre Foundation(http://fundacion.tuquito.org.ar) for developing
|
8
|
+
technology projects intended to close the digital gap in Latin America.
|
9
|
+
|
10
|
+
{<img src=https://www.pledgie.com/campaigns/16746.png?skin_name=chrome>}[http://www.pledgie.com/campaigns/16746]
|
11
|
+
|
12
|
+
== DESCRIPTION
|
13
|
+
|
14
|
+
youtube_it is the most complete Ruby client for the YouTube GData API. It provides an easy
|
15
|
+
way to access the latest and most complete access to YouTube's video API.
|
16
|
+
In comparison with the earlier Youtube interfaces, this new API and
|
17
|
+
library offers much-improved flexibility around executing complex search
|
18
|
+
queries to obtain well-targeted video search results. In addition, standard video management
|
19
|
+
including but not limited to uploading, deleting, updating, like, dislike, ratings and
|
20
|
+
comments.
|
21
|
+
|
22
|
+
== INSTALLATION & SETUP:
|
23
|
+
* Create a youtube account.
|
24
|
+
* Create a developer key here http://code.google.com/apis/youtube/dashboard.
|
25
|
+
* sudo gem install youtube_it
|
26
|
+
|
27
|
+
Note: youtube_it supports ClientLogin(YouTube account), OAuth or AuthSub authentication methods.
|
28
|
+
|
29
|
+
== Example Rails 3 App
|
30
|
+
|
31
|
+
You can get an example how you can use youtube_it with Rails 3 here: http://github.com/chebyte/youtube_it_rails_app_example
|
32
|
+
|
33
|
+
== DEMO
|
34
|
+
|
35
|
+
You can see to youtube_it in action here: http://youtube-it.heroku.com
|
36
|
+
|
37
|
+
== ESTABLISHING A CLIENT
|
38
|
+
|
39
|
+
Important: The Account Authentication API for OAuth 1.0, AuthSub and Client Login has been officially deprecated as of April 20, 2012. It will continue to work as per our deprecation policy(https://developers.google.com/accounts/terms), but we encourage you to migrate to OAuth 2.0 authentication as soon as possible. If you are building a new application, you should use OAuth 2.0 authentication.
|
40
|
+
|
41
|
+
Creating a client:
|
42
|
+
$ require 'youtube_it'
|
43
|
+
$ client = YouTubeIt::Client.new
|
44
|
+
|
45
|
+
Client with developer key:
|
46
|
+
$ client = YouTubeIt::Client.new(:dev_key => "developer_key")
|
47
|
+
|
48
|
+
Client with youtube account and developer key:
|
49
|
+
$ client = YouTubeIt::Client.new(:username => "youtube_username", :password => "youtube_passwd", :dev_key => "developer_key")
|
50
|
+
|
51
|
+
Client with AuthSub:
|
52
|
+
$ client = YouTubeIt::AuthSubClient.new(:token => "token" , :dev_key => "developer_key")
|
53
|
+
|
54
|
+
Client with OAuth:
|
55
|
+
$ client = YouTubeIt::OAuthClient.new("consumer_key", "consumer_secret", "youtube_username", "developer_key")
|
56
|
+
$ client.authorize_from_access("access_token", "access_secret")
|
57
|
+
|
58
|
+
Client with OAuth2:
|
59
|
+
$ client = YouTubeIt::OAuth2Client.new(client_access_token: "access_token", client_refresh_token: "refresh_token", client_id: "client_id", client_secret: "client_secret", dev_key: "dev_key", expires_at: "expiration time")
|
60
|
+
|
61
|
+
If your access token is still valid (be careful, access tokens may only be valid for about 1 hour), you can use the client directly. If you want to refresh the access token using the refresh token just do:
|
62
|
+
|
63
|
+
$ client.refresh_access_token!
|
64
|
+
|
65
|
+
* You can see more about oauth 2 in the wiki: https://github.com/kylejginavan/youtube_it/wiki/How-To:-Use-OAuth-2
|
66
|
+
|
67
|
+
== PROFILES
|
68
|
+
you can use multiple profiles in the same account like that
|
69
|
+
|
70
|
+
$ profiles = client.profiles(['username1','username2'])
|
71
|
+
$ profiles['username1'].username, "username1"
|
72
|
+
|
73
|
+
== VIDEO QUERIES
|
74
|
+
|
75
|
+
Note: Each type of client enables searching capabilities.
|
76
|
+
|
77
|
+
Basic Queries:
|
78
|
+
$ client.videos_by(:query => "penguin")
|
79
|
+
$ client.videos_by(:query => "penguin", :page => 2, :per_page => 15)
|
80
|
+
$ client.videos_by(:query => "penguin", :restriction => "DE")
|
81
|
+
$ client.videos_by(:tags => ['tiger', 'leopard'])
|
82
|
+
$ client.videos_by(:categories => [:news, :sports])
|
83
|
+
$ client.videos_by(:categories => [:news, :sports], :tags => ['soccer', 'football'])
|
84
|
+
$ client.videos_by(:user => 'liz')
|
85
|
+
$ client.videos_by(:favorites, :user => 'liz')
|
86
|
+
$ client.video_by("FQK1URcxmb4")
|
87
|
+
$ client.video_by("https://www.youtube.com/watch?v=QsbmrCtiEUU")
|
88
|
+
$ client.video_by_user("chebyte","FQK1URcxmb4")
|
89
|
+
|
90
|
+
Standard Queries:
|
91
|
+
$ client.videos_by(:most_viewed)
|
92
|
+
$ client.videos_by(:most_linked, :page => 3)
|
93
|
+
$ client.videos_by(:top_rated, :time => :today)
|
94
|
+
|
95
|
+
Advanced Queries (with boolean operators OR (either), AND (include), NOT (exclude)):
|
96
|
+
$ client.videos_by(:categories => { :either => [:news, :sports], :exclude => [:comedy] }, :tags => { :include => ['football'], :exclude => ['soccer'] })
|
97
|
+
|
98
|
+
|
99
|
+
Custom Query Params
|
100
|
+
You can use custom query params like that:
|
101
|
+
|
102
|
+
$ client.videos_by(:query => "penguin", :safe_search => "strict")
|
103
|
+
$ client.videos_by(:query => "penguin", :duration => "long")
|
104
|
+
$ client.videos_by(:query => "penguin", :hd => "true")
|
105
|
+
$ client.videos_by(:query => "penguin", :region => "AR")
|
106
|
+
|
107
|
+
you can see more options here https://developers.google.com/youtube/2.0/reference#yt_format
|
108
|
+
|
109
|
+
Fields Parameter(experimental features):
|
110
|
+
Return videos more than 1000 views
|
111
|
+
$ client.videos_by(:fields => {:view_count => "1000"})
|
112
|
+
|
113
|
+
Filter by date
|
114
|
+
$ client.videos_by(:fields => {:published => ((Date.today)})
|
115
|
+
$ client.videos_by(:fields => {:recorded => ((Date.today)})
|
116
|
+
|
117
|
+
Filter by date with range
|
118
|
+
$ client.videos_by(:fields => {:published => ((Date.today - 30)..(Date.today))})
|
119
|
+
$ client.videos_by(:fields => {:recorded => ((Date.today - 30)..(Date.today))})
|
120
|
+
|
121
|
+
Note: These queries do not find private videos! Use these methods instead:
|
122
|
+
$ client.get_my_video("FQK1URcxmb4")
|
123
|
+
$ client.get_my_videos(:query => "penguin")
|
124
|
+
|
125
|
+
== VIDEO MANAGEMENT
|
126
|
+
|
127
|
+
Note: YouTube account, OAuth or AuthSub enables video management.
|
128
|
+
|
129
|
+
Upload Video:
|
130
|
+
$ client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test])
|
131
|
+
|
132
|
+
Upload Video With A Developer Tag (Note the tags are not immediately available):
|
133
|
+
$ client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test], :dev_tag => 'tagdev')
|
134
|
+
|
135
|
+
Upload Private Video:
|
136
|
+
$ client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test], :private => true)
|
137
|
+
|
138
|
+
|
139
|
+
Update Video:
|
140
|
+
$ client.video_update("FQK1URcxmb4", :title => "new test",:description => 'new description', :category => 'People',:keywords => %w[cool blah test])
|
141
|
+
|
142
|
+
Delete Video:
|
143
|
+
$ client.video_delete("FQK1URcxmb4")
|
144
|
+
|
145
|
+
My Videos:
|
146
|
+
$ client.my_videos
|
147
|
+
|
148
|
+
My Video:
|
149
|
+
$ client.my_video(video_id)
|
150
|
+
|
151
|
+
Profile Details:
|
152
|
+
$ client.profile(user) #default: current user
|
153
|
+
|
154
|
+
List Comments:
|
155
|
+
$ client.comments(video_id)
|
156
|
+
|
157
|
+
Add A Comment:
|
158
|
+
$ client.add_comment(video_id, "test comment!")
|
159
|
+
|
160
|
+
Add A Reply Comment:
|
161
|
+
$ client.add_comment(video_id, "test reply!", :reply_to => another_comment)
|
162
|
+
|
163
|
+
Delete A Comment:
|
164
|
+
$ client.delete_comment(video_id, comment_id)
|
165
|
+
|
166
|
+
List Favorites:
|
167
|
+
$ client.favorites(user) # default: current user
|
168
|
+
|
169
|
+
Add Favorite:
|
170
|
+
$ client.add_favorite(video_id)
|
171
|
+
|
172
|
+
Delete Favorite:
|
173
|
+
$ client.delete_favorite(favorite_entry_id)
|
174
|
+
|
175
|
+
Like A Video:
|
176
|
+
$ client.like_video(video_id)
|
177
|
+
|
178
|
+
Dislike A Video:
|
179
|
+
$ client.dislike_video(video_id)
|
180
|
+
|
181
|
+
List Subscriptions:
|
182
|
+
$ client.subscriptions(user) # default: current user
|
183
|
+
|
184
|
+
Subscribe To A Channel:
|
185
|
+
$ client.subscribe_channel(channel_name)
|
186
|
+
|
187
|
+
Unsubscribe To A Channel:
|
188
|
+
$ client.unsubscribe_channel(subscription_id)
|
189
|
+
|
190
|
+
List New Subscription Videos:
|
191
|
+
$ client.new_subscription_videos(user) # default: current user
|
192
|
+
|
193
|
+
List Playlists:
|
194
|
+
$ client.playlists(user, order_by) # default: current user, position
|
195
|
+
|
196
|
+
for example you can get the videos of your playlist ordered by title
|
197
|
+
|
198
|
+
$ client.playlists(user, "title")
|
199
|
+
|
200
|
+
you can see more about options for order_by here: https://developers.google.com/youtube/2.0/reference#orderbysp
|
201
|
+
|
202
|
+
Select Playlist:
|
203
|
+
$ client.playlist(playlist_id)
|
204
|
+
|
205
|
+
Select All Videos From A Playlist:
|
206
|
+
$ playlist = client.playlist(playlist_id)
|
207
|
+
$ playlist.videos
|
208
|
+
|
209
|
+
Create Playlist:
|
210
|
+
$ playlist = client.add_playlist(:title => "new playlist", :description => "playlist description")
|
211
|
+
|
212
|
+
Delete Playlist:
|
213
|
+
$ client.delete_playlist(playlist_id)
|
214
|
+
|
215
|
+
Add Video To Playlist:
|
216
|
+
$ client.add_video_to_playlist(playlist_id, video_id)
|
217
|
+
|
218
|
+
Remove Video From Playlist:
|
219
|
+
$ client.delete_video_from_playlist(playlist_id, playlist_entry_id)
|
220
|
+
|
221
|
+
Select All Videos From your Watch Later Playlist:
|
222
|
+
$ watcher_later = client.watcherlater(user) #default: current user
|
223
|
+
$ watcher_later.videos
|
224
|
+
|
225
|
+
Add Video To Watcher Later Playlist:
|
226
|
+
$ client.add_video_to_watchlater(video_id)
|
227
|
+
|
228
|
+
Remove Video From Watch Later Playlist:
|
229
|
+
$ client.delete_video_from_watchlater(watchlater_entry_id)
|
230
|
+
|
231
|
+
|
232
|
+
List Related Videos
|
233
|
+
$ video = client.video_by("https://www.youtube.com/watch?v=QsbmrCtiEUU&feature=player_embedded")
|
234
|
+
$ video.related.videos
|
235
|
+
|
236
|
+
Add Response Video
|
237
|
+
$ video.add_response(original_video_id, response_video_id)
|
238
|
+
|
239
|
+
Delete Response Video
|
240
|
+
$ video.delete_response(original_video_id, response_video_id)
|
241
|
+
|
242
|
+
List Response Videos
|
243
|
+
$ video = client.video_by("https://www.youtube.com/watch?v=QsbmrCtiEUU&feature=player_embedded")
|
244
|
+
$ video.responses.videos
|
245
|
+
|
246
|
+
|
247
|
+
== ACCESS CONTROL LIST
|
248
|
+
|
249
|
+
You can give permissions in your videos, for example denied comments, rate, etc...
|
250
|
+
you can read more there http://code.google.com/apis/youtube/2.0/reference.html#youtube_data_api_tag_yt:accessControl
|
251
|
+
you have available the followings options:
|
252
|
+
|
253
|
+
* :rate, :comment, :commentVote, :videoRespond, :list, :embed, :syndicate
|
254
|
+
|
255
|
+
with just two values:
|
256
|
+
* allowed or denied
|
257
|
+
|
258
|
+
Example
|
259
|
+
|
260
|
+
client = YouTubeIt::Client.new(:username => "youtube_username", :password => "youtube_passwd", :dev_key => "developer_key")
|
261
|
+
|
262
|
+
* upload video with denied comments
|
263
|
+
|
264
|
+
client.video_upload(File.open("test.mov"), :title => "test",:description => 'some description', :category => 'People',:keywords => %w[cool blah test], :comment => "denied")
|
265
|
+
|
266
|
+
== User Activity
|
267
|
+
You can get user activity with the followings params:
|
268
|
+
|
269
|
+
$ client.activity(user) #default current user
|
270
|
+
|
271
|
+
== Video Upload From Browser:
|
272
|
+
|
273
|
+
When uploading a video from your browser you need make a form upload with the followings params:
|
274
|
+
$ upload_token(params, nexturl)
|
275
|
+
params => params like :title => "title", :description => "description", :category => "People", :tags => ["test"]
|
276
|
+
nexturl => redirect to this url after upload
|
277
|
+
|
278
|
+
|
279
|
+
Controller
|
280
|
+
def upload
|
281
|
+
@upload_info = YouTubeIt::Client.new.upload_token(params, videos_url)
|
282
|
+
end
|
283
|
+
|
284
|
+
View (upload.html.erb)
|
285
|
+
<% form_tag @upload_info[:url], :multipart => true do %>
|
286
|
+
<%= hidden_field_tag :token, @upload_info[:token] %>
|
287
|
+
<%= label_tag :file %>
|
288
|
+
<%= file_field_tag :file %>
|
289
|
+
<%= submit_tag "Upload video" %>
|
290
|
+
<% end %>
|
291
|
+
|
292
|
+
== WIDESCREEN VIDEOS
|
293
|
+
|
294
|
+
If the videos has support for widescreen:
|
295
|
+
$ video.embed_html_with_width(1280)
|
296
|
+
|
297
|
+
Note: you can specify width or just use the default of 1280.
|
298
|
+
|
299
|
+
== USING HTML5
|
300
|
+
|
301
|
+
Now you can embed videos without use flash using html5, usefull for mobiles that not support flash but has html5 browser
|
302
|
+
|
303
|
+
You can specify these options
|
304
|
+
$ video.embed_html5({:class => 'video-player', :id => 'my-video', :width => '425', :height => '350', :frameborder => '1', :url_params => {:option_one => "value", :option_two => "value"}})
|
305
|
+
|
306
|
+
or just use with default options
|
307
|
+
$ video.embed_html5 #default: width: 425, height: 350, frameborder: 0
|
308
|
+
|
309
|
+
== LOGGING
|
310
|
+
|
311
|
+
YouTubeIt passes all logs through the logger variable on the class itself. In Rails context, assign the Rails logger to that variable to collect the messages
|
312
|
+
(don't forget to set the level to debug):
|
313
|
+
$ YouTubeIt.logger = RAILS_DEFAULT_LOGGER
|
314
|
+
$ RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
|
315
|
+
|
316
|
+
== CONTRIBUTORS:
|
317
|
+
|
318
|
+
* Kyle J. Ginavan.
|
319
|
+
* Mauro Torres - http://github.com/chebyte
|
320
|
+
* Marko Seppa - https://github.com/mseppae
|
321
|
+
* Walter Korman - https://github.com/shaper
|
322
|
+
* Shane Vitarana - https://github.com/shanev
|
323
|
+
|
324
|
+
== LICENSE:
|
325
|
+
|
326
|
+
MIT License
|
327
|
+
|
328
|
+
Copyright (c) 2010 Kyle J. Ginavan
|
329
|
+
|
330
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
331
|
+
a copy of this software and associated documentation files (the
|
332
|
+
'Software'), to deal in the Software without restriction, including
|
333
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
334
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
335
|
+
permit persons to whom the Software is furnished to do so, subject to
|
336
|
+
the following conditions:
|
337
|
+
|
338
|
+
The above copyright notice and this permission notice shall be
|
339
|
+
included in all copies or substantial portions of the Software.
|
340
|
+
|
341
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
342
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
343
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
344
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
345
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
346
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
347
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,146 @@
|
|
1
|
+
class YTAnalytics
|
2
|
+
class Client
|
3
|
+
include YTAnalytics::Logging
|
4
|
+
# Previously this was a logger instance but we now do it globally
|
5
|
+
|
6
|
+
def initialize *params
|
7
|
+
if params.first.is_a?(Hash)
|
8
|
+
hash_options = params.first
|
9
|
+
@user = hash_options[:username]
|
10
|
+
@pass = hash_options[:password]
|
11
|
+
@dev_key = hash_options[:dev_key]
|
12
|
+
@client_id = hash_options[:client_id] || "yt_analytics"
|
13
|
+
@legacy_debug_flag = hash_options[:debug]
|
14
|
+
elsif params.first
|
15
|
+
puts "* warning: the method YTAnalytics::Client.new(user, passwd, dev_key) is deprecated, use YTAnalytics::Client.new(:username => 'user', :password => 'passwd', :dev_key => 'dev_key')"
|
16
|
+
@user = params.shift
|
17
|
+
@pass = params.shift
|
18
|
+
@dev_key = params.shift
|
19
|
+
@client_id = params.shift || "youtube_it"
|
20
|
+
@legacy_debug_flag = params.shift
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def enable_http_debugging
|
25
|
+
client.enable_http_debugging
|
26
|
+
end
|
27
|
+
|
28
|
+
def current_user
|
29
|
+
client.get_current_user
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets the authenticated users video with the given ID. It may be private.
|
33
|
+
def my_video(video_id)
|
34
|
+
client.get_my_video(video_id)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Gets all videos
|
38
|
+
def my_videos(opts = {})
|
39
|
+
client.get_my_videos(opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Gets the user's watch history
|
43
|
+
def watch_history
|
44
|
+
client.get_watch_history
|
45
|
+
end
|
46
|
+
|
47
|
+
def analytics(opts = {})
|
48
|
+
client.get_analytics(opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
def seven_day_totals(start_date = 2.day.ago, end_date = 2.day.ago)
|
52
|
+
client.temporal_totals('7DayTotals',start_date, end_date, self.user_id)
|
53
|
+
end
|
54
|
+
|
55
|
+
def thirty_day_totals(start_date = 2.day.ago, end_date = 2.day.ago)
|
56
|
+
client.temporal_totals('30DayTotals',start_date, end_date, self.user_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
def day_totals(start_date = 2.day.ago, end_date = 2.day.ago)
|
60
|
+
client.temporal_totals('day',start_date, end_date, self.user_id)
|
61
|
+
end
|
62
|
+
|
63
|
+
def month_totals(start_date = 2.day.ago, end_date = 2.day.ago)
|
64
|
+
client.temporal_totals('month',start_date, end_date, self.user_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def client
|
70
|
+
@client ||= YTAnalytics::YTAuth::Authentication.new(:username => @user, :password => @pass, :dev_key => @dev_key)
|
71
|
+
end
|
72
|
+
|
73
|
+
def calculate_offset(page, per_page)
|
74
|
+
page == 1 ? 1 : ((per_page * page) - per_page + 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def integer_or_default(value, default)
|
78
|
+
value = value.to_i
|
79
|
+
value > 0 ? value : default
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class OAuth2Client < YTAnalytics::Client
|
84
|
+
def initialize(options)
|
85
|
+
@client_id = options[:client_id]
|
86
|
+
@client_secret = options[:client_secret]
|
87
|
+
@client_access_token = options[:client_access_token]
|
88
|
+
@client_refresh_token = options[:client_refresh_token]
|
89
|
+
@client_token_expires_at = options[:client_token_expires_at]
|
90
|
+
@dev_key = options[:dev_key]
|
91
|
+
@legacy_debug_flag = options[:debug]
|
92
|
+
end
|
93
|
+
|
94
|
+
def oauth_client
|
95
|
+
options = {:site => "https://accounts.google.com",
|
96
|
+
:authorize_url => '/o/oauth2/auth',
|
97
|
+
:token_url => '/o/oauth2/token'
|
98
|
+
}
|
99
|
+
options.merge(:connection_opts => @connection_opts) if @connection_opts
|
100
|
+
@oauth_client ||= ::OAuth2::Client.new(@client_id, @client_secret, options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def access_token
|
104
|
+
@access_token ||= ::OAuth2::AccessToken.new(oauth_client, @client_access_token, :refresh_token => @client_refresh_token, :expires_at => @client_token_expires_at)
|
105
|
+
end
|
106
|
+
|
107
|
+
def refresh_access_token!
|
108
|
+
new_access_token = access_token.refresh!
|
109
|
+
require 'thread' unless Thread.respond_to?(:exclusive)
|
110
|
+
Thread.exclusive do
|
111
|
+
@access_token = new_access_token
|
112
|
+
@client = nil
|
113
|
+
end
|
114
|
+
@access_token
|
115
|
+
end
|
116
|
+
|
117
|
+
def session_token_info
|
118
|
+
response = Faraday.get("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=#{@client_access_token}")
|
119
|
+
{:code => response.status, :body => response.body }
|
120
|
+
end
|
121
|
+
|
122
|
+
def current_user
|
123
|
+
profile = access_token.get("http://gdata.youtube.com/feeds/api/users/default")
|
124
|
+
response_code = profile.status
|
125
|
+
|
126
|
+
if response_code/10 == 20 # success
|
127
|
+
Nokogiri::XML(profile.body).at("entry/author/name").text
|
128
|
+
elsif response_code == 403 || response_code == 401 # auth failure
|
129
|
+
raise YTAnalytics::YTAuth::AuthenticationError.new(profile.inspect, response_code)
|
130
|
+
else
|
131
|
+
raise YTAnalytics::YTAuth::UploadError.new(profile.inspect, response_code)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def user_id
|
136
|
+
profile ||= access_token.get("http://gdata.youtube.com/feeds/api/users/default?v=2&alt=json").parsed
|
137
|
+
profile['entry']['author'][0]["yt$userId"]["$t"]
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def client
|
143
|
+
@client ||= YTAnalytics::YTAuth::Authentication.new(:username => current_user, :access_token => access_token, :dev_key => @dev_key)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Faraday
|
2
|
+
class Request::AuthHeader < Faraday::Middleware
|
3
|
+
|
4
|
+
def call(env)
|
5
|
+
req_headers = env[:request_headers]
|
6
|
+
req_headers.merge!(@headers)
|
7
|
+
unless req_headers.include?("GData-Version")
|
8
|
+
req_headers.merge!("GData-Version" => "2")
|
9
|
+
end
|
10
|
+
unless req_headers.include?("Content-Type")
|
11
|
+
req_headers.merge!("Content-Type" => "application/atom+xml; charset=UTF-8")
|
12
|
+
end
|
13
|
+
unless req_headers.include?("Content-Length")
|
14
|
+
req_headers.merge!("Content-Length" => env[:body] ? "#{env[:body].length}" : "0")
|
15
|
+
end
|
16
|
+
|
17
|
+
@app.call(env)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app, headers)
|
21
|
+
@app, @headers = app, headers
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Faraday
|
2
|
+
class Request::OAuth < Faraday::Middleware
|
3
|
+
dependency 'simple_oauth'
|
4
|
+
|
5
|
+
def call(env)
|
6
|
+
params = env[:body].is_a?(Hash) ? env[:body] : {}
|
7
|
+
|
8
|
+
signature_params = params.reject{ |k,v| v.respond_to?(:content_type) }
|
9
|
+
|
10
|
+
header = SimpleOAuth::Header.new(env[:method], env[:url], signature_params, @options)
|
11
|
+
|
12
|
+
env[:request_headers]['Authorization'] = header.to_s
|
13
|
+
|
14
|
+
@app.call(env)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(app, options)
|
18
|
+
@app, @options = app, options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Faraday
|
2
|
+
class Request::OAuth2 < Faraday::Middleware
|
3
|
+
def call(env)
|
4
|
+
env[:request_headers]['Authorization'] = "Bearer #{@access_token.token}"
|
5
|
+
|
6
|
+
@app.call(env)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(app, access_token)
|
10
|
+
@app, @access_token = app, access_token
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Faraday
|
2
|
+
class Response::YTAnalytics < Response::Middleware
|
3
|
+
def parse_upload_error_from(string)
|
4
|
+
xml = Nokogiri::XML(string).at('errors')
|
5
|
+
if xml
|
6
|
+
xml.css("error").inject('') do |all_faults, error|
|
7
|
+
if error.at("internalReason")
|
8
|
+
msg_error = error.at("internalReason").text
|
9
|
+
elsif error.at("location")
|
10
|
+
msg_error = error.at("location").text[/media:group\/media:(.*)\/text\(\)/,1]
|
11
|
+
else
|
12
|
+
msg_error = "Unspecified error"
|
13
|
+
end
|
14
|
+
code = error.at("code").text if error.at("code")
|
15
|
+
all_faults + sprintf("%s: %s\n", msg_error, code)
|
16
|
+
end
|
17
|
+
else
|
18
|
+
string[/<TITLE>(.+)<\/TITLE>/, 1] || string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_complete(env) #this method is called after finish request
|
23
|
+
msg = env[:body] ? parse_upload_error_from(env[:body].gsub(/\n/, '')) : ''
|
24
|
+
if env[:status] == 403 || env[:status] == 401
|
25
|
+
raise ::AuthenticationError.new(msg, env[:status])
|
26
|
+
elsif env[:status] / 10 != 20
|
27
|
+
raise ::UploadError.new(msg, env[:status])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class YTAnalytics
|
2
|
+
module Model #:nodoc:
|
3
|
+
class TemporalMetrics #:nodoc:
|
4
|
+
|
5
|
+
include YTAnalytics::Logging
|
6
|
+
attr_accessor :end_date, :views, :comments, :favorites_added, :favorites_removed, :likes, :dislikes, :shares, :subscribers_gained, :subscribers_lost, :uniques
|
7
|
+
|
8
|
+
|
9
|
+
def initialize params
|
10
|
+
@end_date = params[:endDate]
|
11
|
+
@views = params[:views]
|
12
|
+
@comments = params[:comments]
|
13
|
+
@favorites_added = params[:favoritesAdded]
|
14
|
+
@favorites_removed = params[:favoritesRemoved]
|
15
|
+
@likes = params[:likes]
|
16
|
+
@dislikes = params[:dislikes]
|
17
|
+
@shares = params[:shares]
|
18
|
+
@subscribers_gained = params[:subscribersGained]
|
19
|
+
@subscribers_lost = params[:subscribersLost]
|
20
|
+
@uniques = params[:uniques]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
class YTAnalytics
|
4
|
+
module Parser #:nodoc:
|
5
|
+
class FeedParser #:nodoc:
|
6
|
+
def initialize(content)
|
7
|
+
@content = (content =~ URI::regexp(%w(http https)) ? open(content).read : content)
|
8
|
+
|
9
|
+
rescue OpenURI::HTTPError => e
|
10
|
+
raise OpenURI::HTTPError.new(e.io.status[0],e)
|
11
|
+
rescue
|
12
|
+
@content = content
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
parse_content @content
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_single_entry
|
21
|
+
doc = Nokogiri::XML(@content)
|
22
|
+
parse_entry(doc.at("entry") || doc)
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_videos
|
26
|
+
doc = Nokogiri::XML(@content)
|
27
|
+
videos = []
|
28
|
+
doc.css("entry").each do |video|
|
29
|
+
videos << parse_entry(video)
|
30
|
+
end
|
31
|
+
videos
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_bom str
|
35
|
+
str.gsub /\xEF\xBB\xBF|/, ''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
class TemporalParser < FeedParser
|
41
|
+
|
42
|
+
private
|
43
|
+
def parse_content(content)
|
44
|
+
temporal_metrics = []
|
45
|
+
if content.is_a? Hash and content["rows"].is_a? Array and content["rows"].length > 0
|
46
|
+
|
47
|
+
headers = content["columnHeaders"]
|
48
|
+
|
49
|
+
content["rows"].each do |row|
|
50
|
+
metrics = {}
|
51
|
+
|
52
|
+
headers.each_with_index do |column,i|
|
53
|
+
if column["columnType"] == "DIMENSION"
|
54
|
+
metrics[:endDate] = Date.strptime row[i], "%Y-%m-%d"
|
55
|
+
elsif column["columnType"] == "METRIC"
|
56
|
+
metrics[eval(":" + column["name"])] = row[i]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
temporal_metrics.push(YTAnalytics::Model::TemporalMetrics.new(metrics))
|
60
|
+
end
|
61
|
+
|
62
|
+
temporal_metrics.sort { |a,b| a.end_date <=> b.end_date }
|
63
|
+
end
|
64
|
+
temporal_metrics
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class AnalyticsParser < FeedParser #:nodoc:
|
69
|
+
|
70
|
+
private
|
71
|
+
def parse_content(content)
|
72
|
+
entry = JSON.parse(content)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class YTAnalytics
|
2
|
+
module YTAuth
|
3
|
+
|
4
|
+
class UploadError < YTAnalytics::Error; end
|
5
|
+
|
6
|
+
class AuthenticationError < YTAnalytics::Error; end
|
7
|
+
|
8
|
+
class Authentication
|
9
|
+
include YTAnalytics::Logging
|
10
|
+
|
11
|
+
def initialize *params
|
12
|
+
if params.first.is_a?(Hash)
|
13
|
+
hash_options = params.first
|
14
|
+
@user = hash_options[:username]
|
15
|
+
@password = hash_options[:password]
|
16
|
+
@dev_key = hash_options[:dev_key]
|
17
|
+
@access_token = hash_options[:access_token]
|
18
|
+
@authsub_token = hash_options[:authsub_token]
|
19
|
+
@client_id = hash_options[:client_id] || "youtube_it"
|
20
|
+
@config_token = hash_options[:config_token]
|
21
|
+
else
|
22
|
+
puts "* warning: the method YTAnalytics::Auth::Authentication.new(username, password, dev_key) is deprecated, use YouTubeIt::Upload::VideoUpload.new(:username => 'user', :password => 'passwd', :dev_key => 'dev_key')"
|
23
|
+
@user = params.shift
|
24
|
+
@password = params.shift
|
25
|
+
@dev_key = params.shift
|
26
|
+
@access_token = params.shift
|
27
|
+
@authsub_token = params.shift
|
28
|
+
@client_id = params.shift || "youtube_it"
|
29
|
+
@config_token = params.shift
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def enable_http_debugging
|
35
|
+
@http_debugging = true
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
def get_current_user
|
41
|
+
current_user_url = "/feeds/api/users/default"
|
42
|
+
response = yt_session.get(current_user_url)
|
43
|
+
|
44
|
+
return Nokogiri::XML(response.body).at("entry/author/name").text
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_analytics(opts)
|
48
|
+
# max_results = opts[:per_page] || 50
|
49
|
+
# start_index = ((opts[:page] || 1) -1) * max_results +1
|
50
|
+
get_url = "/youtube/analytics/v1/reports?"
|
51
|
+
get_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
|
52
|
+
response = yt_session('https://www.googleapis.com').get(get_url)
|
53
|
+
|
54
|
+
return YTAnalytics::Parser::AnalyticsParser.new(response.body).parse
|
55
|
+
end
|
56
|
+
|
57
|
+
def temporal_totals(dimension, start_date, end_date, user_id)
|
58
|
+
#dimension is either day, 7DayTotals, 30DayTotals, or month
|
59
|
+
opts = {'end-date'=>end_date.strftime("%Y-%m-%d"),'ids' => "channel==#{user_id}", 'metrics' => 'views,comments,favoritesAdded,favoritesRemoved,likes,dislikes,shares,subscribersGained,subscribersLost,uniques','start-date' => start_date.strftime("%Y-%m-%d"),'dimensions' => dimension}
|
60
|
+
get_url = "/youtube/analytics/v1/reports?"
|
61
|
+
get_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
|
62
|
+
response = yt_session('https://www.googleapis.com').get(get_url)
|
63
|
+
content = JSON.parse(response.body)
|
64
|
+
return YTAnalytics::Parser::TemporalParser.new(content).parse
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
|
71
|
+
def base_url
|
72
|
+
"http://gdata.youtube.com"
|
73
|
+
end
|
74
|
+
|
75
|
+
def authorization_headers
|
76
|
+
header = {"X-GData-Client" => "#{@client_id}"}
|
77
|
+
header.merge!("X-GData-Key" => "key=#{@dev_key}") if @dev_key
|
78
|
+
if @authsub_token
|
79
|
+
header.merge!("Authorization" => "AuthSub token=#{@authsub_token}")
|
80
|
+
elsif @access_token.nil? && @authsub_token.nil? && @user
|
81
|
+
header.merge!("Authorization" => "GoogleLogin auth=#{auth_token}")
|
82
|
+
end
|
83
|
+
header
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_upload_error_from(string)
|
87
|
+
xml = Nokogiri::XML(string).at('errors')
|
88
|
+
if xml
|
89
|
+
xml.css("error").inject('') do |all_faults, error|
|
90
|
+
if error.at("internalReason")
|
91
|
+
msg_error = error.at("internalReason").text
|
92
|
+
elsif error.at("location")
|
93
|
+
msg_error = error.at("location").text[/media:group\/media:(.*)\/text\(\)/,1]
|
94
|
+
else
|
95
|
+
msg_error = "Unspecified error"
|
96
|
+
end
|
97
|
+
code = error.at("code").text if error.at("code")
|
98
|
+
all_faults + sprintf("%s: %s\n", msg_error, code)
|
99
|
+
end
|
100
|
+
else
|
101
|
+
string[/<TITLE>(.+)<\/TITLE>/, 1] || string
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# def raise_on_faulty_response(response)
|
106
|
+
# response_code = response.code.to_i
|
107
|
+
# msg = parse_upload_error_from(response.body.gsub(/\n/, ''))
|
108
|
+
|
109
|
+
# if response_code == 403 || response_code == 401
|
110
|
+
# #if response_code / 10 == 40
|
111
|
+
# raise AuthenticationError.new(msg, response_code)
|
112
|
+
# elsif response_code / 10 != 20 # Response in 20x means success
|
113
|
+
# raise UploadError.new(msg, response_code)
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
|
117
|
+
def auth_token
|
118
|
+
@auth_token ||= begin
|
119
|
+
http = Faraday.new("https://www.google.com", :ssl => {:verify => false})
|
120
|
+
body = "Email=#{YTAnalytics.esc @user}&Passwd=#{YTAnalytics.esc @password}&service=youtube&source=#{YTAnalytics.esc @client_id}"
|
121
|
+
response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
|
122
|
+
raise ::AuthenticationError.new(response.body[/Error=(.+)/,1], response.status.to_i) if response.status.to_i != 200
|
123
|
+
@auth_token = response.body[/Auth=(.+)/, 1]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def yt_session(url = nil)
|
128
|
+
Faraday.new(:url => (url ? url : base_url), :ssl => {:verify => false}) do |builder|
|
129
|
+
if @access_token
|
130
|
+
if @config_token
|
131
|
+
builder.use Faraday::Request::OAuth, @config_token
|
132
|
+
else
|
133
|
+
builder.use Faraday::Request::OAuth2, @access_token
|
134
|
+
end
|
135
|
+
end
|
136
|
+
builder.use Faraday::Request::AuthHeader, authorization_headers
|
137
|
+
builder.use Faraday::Response::YTAnalytics
|
138
|
+
builder.adapter YTAnalytics.adapter
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/yt_analytics.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'net/https'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'builder'
|
7
|
+
require 'oauth'
|
8
|
+
require 'oauth2'
|
9
|
+
require 'faraday'
|
10
|
+
|
11
|
+
class YTAnalytics
|
12
|
+
|
13
|
+
# Base error class for the extension
|
14
|
+
class Error < RuntimeError
|
15
|
+
attr_reader :code
|
16
|
+
def initialize(msg, code = 0)
|
17
|
+
super(msg)
|
18
|
+
@code = code
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.esc(s) #:nodoc:
|
23
|
+
CGI.escape(s.to_s)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the logger for the library
|
27
|
+
def self.logger=(any_logger)
|
28
|
+
@logger = any_logger
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the logger for the library (by default will log to STDOUT). TODO: this is where we grab the Rails logger too
|
32
|
+
def self.logger
|
33
|
+
@logger ||= create_default_logger
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.adapter=(faraday_adapter)
|
37
|
+
@adapter = faraday_adapter
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.adapter
|
41
|
+
@adapter ||= Faraday.default_adapter
|
42
|
+
end
|
43
|
+
|
44
|
+
# Gets mixed into the classes to provide the logger method
|
45
|
+
module Logging #:nodoc:
|
46
|
+
|
47
|
+
# Return the base logger set for the library
|
48
|
+
def logger
|
49
|
+
YTAnalytics.logger
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def self.create_default_logger
|
55
|
+
logger = Logger.new(STDOUT)
|
56
|
+
logger.level = Logger::DEBUG
|
57
|
+
logger
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
%w(
|
62
|
+
version
|
63
|
+
client
|
64
|
+
parser
|
65
|
+
model/temporal_metrics
|
66
|
+
request/authentication
|
67
|
+
middleware/faraday_authheader.rb
|
68
|
+
middleware/faraday_oauth.rb
|
69
|
+
middleware/faraday_oauth2.rb
|
70
|
+
middleware/faraday_yt_analytics.rb
|
71
|
+
).each{|m| require File.dirname(__FILE__) + '/yt_analytics/' + m }
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
|
5
|
+
# Maintain your gem's version:
|
6
|
+
require File.dirname(__FILE__) + "/lib/yt_analytics/version"
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = "yt_analytics"
|
10
|
+
s.version = YTAnalytics::VERSION
|
11
|
+
s.authors = %w(drewbaumann sstavrop)
|
12
|
+
s.email = %w(drewbaumann@gmail.com)
|
13
|
+
s.description = "Upload, delete, update, comment on youtube videos all from one gem."
|
14
|
+
s.summary = "The most complete Ruby wrapper for youtube api's"
|
15
|
+
s.homepage = "http://github.com/fullscreeninc/yt_analytics"
|
16
|
+
|
17
|
+
s.add_runtime_dependency("nokogiri", "~> 1.5.2")
|
18
|
+
s.add_runtime_dependency("oauth", "~> 0.4.4")
|
19
|
+
s.add_runtime_dependency("oauth2", "~> 0.6")
|
20
|
+
s.add_runtime_dependency("simple_oauth", "~> 0.1.5")
|
21
|
+
s.add_runtime_dependency("faraday", "~> 0.8")
|
22
|
+
s.add_runtime_dependency("builder", ">= 0")
|
23
|
+
|
24
|
+
s.files = Dir.glob("lib/**/*") + %w(README.rdoc yt_analytics.gemspec)
|
25
|
+
|
26
|
+
s.extra_rdoc_files = %w(README.rdoc)
|
27
|
+
end
|
28
|
+
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yt_analytics
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- drewbaumann
|
9
|
+
- sstavrop
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-11-11 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: nokogiri
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 1.5.2
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: oauth
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 0.4.4
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.4.4
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: oauth2
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.6'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0.6'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: simple_oauth
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ~>
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 0.1.5
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 0.1.5
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: faraday
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0.8'
|
87
|
+
type: :runtime
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0.8'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: builder
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Upload, delete, update, comment on youtube videos all from one gem.
|
112
|
+
email:
|
113
|
+
- drewbaumann@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files:
|
117
|
+
- README.rdoc
|
118
|
+
files:
|
119
|
+
- lib/yt_analytics/client.rb
|
120
|
+
- lib/yt_analytics/middleware/faraday_authheader.rb
|
121
|
+
- lib/yt_analytics/middleware/faraday_oauth.rb
|
122
|
+
- lib/yt_analytics/middleware/faraday_oauth2.rb
|
123
|
+
- lib/yt_analytics/middleware/faraday_yt_analytics.rb
|
124
|
+
- lib/yt_analytics/model/temporal_metrics.rb
|
125
|
+
- lib/yt_analytics/parser.rb
|
126
|
+
- lib/yt_analytics/request/authentication.rb
|
127
|
+
- lib/yt_analytics/version.rb
|
128
|
+
- lib/yt_analytics.rb
|
129
|
+
- README.rdoc
|
130
|
+
- yt_analytics.gemspec
|
131
|
+
homepage: http://github.com/fullscreeninc/yt_analytics
|
132
|
+
licenses: []
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 1.8.24
|
152
|
+
signing_key:
|
153
|
+
specification_version: 3
|
154
|
+
summary: The most complete Ruby wrapper for youtube api's
|
155
|
+
test_files: []
|