yt_analytics 0.0.1

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.
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: []