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 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
@@ -0,0 +1,4 @@
1
+ class YTAnalytics
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -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: []