chocolate_rain 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/Rakefile +1 -0
  4. data/chocolate_rain.gemspec +26 -0
  5. data/generators/youtube_model/USAGE +3 -0
  6. data/generators/youtube_model/templates/config.yml +6 -0
  7. data/generators/youtube_model/templates/model.rb +17 -0
  8. data/generators/youtube_model/templates/unit_test.rb +7 -0
  9. data/generators/youtube_model/youtube_model_generator.rb +15 -0
  10. data/lib/.DS_Store +0 -0
  11. data/lib/chocolate_rain.rb +5 -0
  12. data/lib/chocolate_rain/version.rb +3 -0
  13. data/lib/gdata/auth/authsub.rb +161 -0
  14. data/lib/gdata/auth/clientlogin.rb +102 -0
  15. data/lib/gdata/client.rb +84 -0
  16. data/lib/gdata/client/apps.rb +27 -0
  17. data/lib/gdata/client/base.rb +182 -0
  18. data/lib/gdata/client/blogger.rb +28 -0
  19. data/lib/gdata/client/booksearch.rb +28 -0
  20. data/lib/gdata/client/calendar.rb +58 -0
  21. data/lib/gdata/client/contacts.rb +28 -0
  22. data/lib/gdata/client/doclist.rb +28 -0
  23. data/lib/gdata/client/finance.rb +28 -0
  24. data/lib/gdata/client/gbase.rb +28 -0
  25. data/lib/gdata/client/gmail.rb +28 -0
  26. data/lib/gdata/client/health.rb +28 -0
  27. data/lib/gdata/client/notebook.rb +28 -0
  28. data/lib/gdata/client/photos.rb +29 -0
  29. data/lib/gdata/client/spreadsheets.rb +28 -0
  30. data/lib/gdata/client/webmaster_tools.rb +28 -0
  31. data/lib/gdata/client/youtube.rb +47 -0
  32. data/lib/gdata/g_data.rb +22 -0
  33. data/lib/gdata/http.rb +18 -0
  34. data/lib/gdata/http/default_service.rb +82 -0
  35. data/lib/gdata/http/mime_body.rb +95 -0
  36. data/lib/gdata/http/request.rb +74 -0
  37. data/lib/gdata/http/response.rb +44 -0
  38. data/lib/youtube_helpers.rb +58 -0
  39. data/lib/youtube_model.rb +341 -0
  40. metadata +84 -0
@@ -0,0 +1,74 @@
1
+ # Copyright (C) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "rexml/document"
16
+
17
+ module GData
18
+ module HTTP
19
+
20
+ # Very simple class to hold everything about an HTTP request.
21
+ class Request
22
+
23
+ # The URL of the request.
24
+ attr_accessor :url
25
+ # The body of the request.
26
+ attr_accessor :body
27
+ # The HTTP method being used in the request.
28
+ attr_accessor :method
29
+ # The HTTP headers of the request.
30
+ attr_accessor :headers
31
+
32
+ # Only the URL itself is required, everything else is optional.
33
+ def initialize(url, options = {})
34
+ @url = url
35
+ options.each do |key, value|
36
+ self.send("#{key}=", value)
37
+ end
38
+
39
+ @method ||= :get
40
+ @headers ||= {}
41
+ end
42
+
43
+ # Returns whether or not a request is chunked.
44
+ def chunked?
45
+ if @headers['Transfer-Encoding'] == 'chunked'
46
+ return true
47
+ else
48
+ return false
49
+ end
50
+ end
51
+
52
+ # Sets if the request is using chunked transfer-encoding.
53
+ def chunked=(enabled)
54
+ if enabled
55
+ @headers['Transfer-Encoding'] = 'chunked'
56
+ else
57
+ @headers.delete('Transfer-Encoding')
58
+ end
59
+ end
60
+
61
+ # Calculates and sets the length of the body.
62
+ def calculate_length!
63
+ if not @headers['Content-Length'] and not chunked? \
64
+ and method != :get and method != :delete
65
+ if @body
66
+ @headers['Content-Length'] = @body.length
67
+ else
68
+ @headers['Content-Length'] = 0
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,44 @@
1
+ # Copyright (C) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'gdata/client'
16
+
17
+ module GData
18
+ module HTTP
19
+
20
+ # An extremely simple class to hold the values of an HTTP response.
21
+ class Response
22
+
23
+ # The HTTP response code.
24
+ attr_accessor :status_code
25
+ # The body of the HTTP response.
26
+ attr_accessor :body
27
+ # The headers of the HTTP response.
28
+ attr_accessor :headers
29
+
30
+ # Converts the response body into a REXML::Document
31
+ def to_xml
32
+ if @body
33
+ begin
34
+ return REXML::Document.new(@body).root
35
+ rescue
36
+ raise GData::Client::Error, "Response body not XML."
37
+ end
38
+ else
39
+ return nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+ module YouTubeModel # :nodoc:
2
+ module Helpers
3
+ # This helper returns the required html to embed a youtube video.
4
+ # You can customize it with the following options:
5
+ # * <tt>:border</tt> Specifies if the player is bordered or not.
6
+ # * <tt>:related</tt> Specifies if the player will include related videos.
7
+ # * <tt>:colors</tt> Array with the desired colors of the player.
8
+ # * <tt>:language</tt> Specifies the language of the player.
9
+ # * <tt>:width</tt> Specifies the player's width.
10
+ # * <tt>:height</tt> Specifies the player's height.
11
+ #
12
+ # Example:
13
+ # # in controller:
14
+ # @top_rated = YouTube.top_rated(:today)
15
+ #
16
+ # # in view:
17
+ # <% @top_rated.videos.each do |video| -%>
18
+ # <p><%= youtube_embed video, :border => true %></p>
19
+ # <% end -%>
20
+ def youtube_embed(video, options = {})
21
+ settings = {
22
+ :border => '0',
23
+ :rel => '0',
24
+ :color1 => '0x666666',
25
+ :color2 => '0x666666',
26
+ :hl => 'en',
27
+ :width => 425,
28
+ :height => 373
29
+ }.merge(options)
30
+
31
+ params = settings.to_query
32
+ %Q(
33
+ <object width="#{settings[:width]}" height="#{settings[:height]}">
34
+ <param name="movie" value="http://www.youtube.com/v/#{video.id}&#{params}"></param>
35
+ <param name="wmode" value="transparent"></param>
36
+ <embed src="http://www.youtube.com/v/#{video.id}&#{params}" type="application/x-shockwave-flash" wmode="transparent" width="#{settings[:width]}" height="#{settings[:height]}"></embed>
37
+ </object>
38
+ )
39
+ end
40
+
41
+ # Returns a link to the authentication Google page.
42
+ #
43
+ # Pass the new_<your-controller>_url or whatever url you use to the Upload's step 1
44
+ #
45
+ # Example:
46
+ # <%= link_to 'Upload a new video', youtube_auth_url(new_videos_url) %>
47
+ #
48
+ def youtube_auth_url(next_url)
49
+ params = {
50
+ :session => YT_CONFIG['auth_sub']['session'],
51
+ :secure => YT_CONFIG['auth_sub']['secure'],
52
+ :scope => 'http://gdata.youtube.com',
53
+ :next => next_url
54
+ }
55
+ "https://www.google.com/accounts/AuthSubRequest?#{params.to_query}"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,341 @@
1
+ include GData
2
+
3
+ module YouTubeModel # :nodoc:
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # Call this method to make an ActiveResource model ready to roll with YouTube
10
+ def acts_as_youtube_model
11
+ self.site = "http://gdata.youtube.com/feeds/api"
12
+ self.timeout = 5
13
+
14
+ extend YouTubeModel::SingletonMethods
15
+ include YouTubeModel::InstanceMethods
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ # Returns an array of +entry+, or an empty array when there's no such +entry+
21
+ def videos
22
+ if respond_to?(:entry)
23
+ entry.is_a?(Array) ? entry : [entry]
24
+ else
25
+ []
26
+ end
27
+ end
28
+ end
29
+
30
+ module SingletonMethods
31
+ # Retrieve the top rated videos for a time. Valid times are:
32
+ # * :today (1 day)
33
+ # * :this_week (7 days)
34
+ # * :this_month (1 month)
35
+ # * :all_time (default)
36
+ def top_rated(time = :all_time)
37
+ request("standardfeeds/top_rated#{query_string(:time => time)}")
38
+ end
39
+
40
+ # Retrieve the top favorited videos for a time. Valid times are:
41
+ # * :today (1 day)
42
+ # * :this_week (7 days)
43
+ # * :this_month (1 month)
44
+ # * :all_time (default)
45
+ def top_favorites(time = :all_time)
46
+ request("standardfeeds/top_favorites#{query_string(:time => time)}")
47
+ end
48
+
49
+ # Retrieve the most viewed videos for a time. Valid times are:
50
+ # * :today (1 day)
51
+ # * :this_week (7 days)
52
+ # * :this_month (1 month)
53
+ # * :all_time (default)
54
+ def most_viewed(time = :all_time)
55
+ request("standardfeeds/most_viewed#{query_string(:time => time)}")
56
+ end
57
+
58
+ # Retrieve the most recent videos for a time. Valid times are:
59
+ # * :today (1 day)
60
+ # * :this_week (7 days)
61
+ # * :this_month (1 month)
62
+ # * :all_time (default)
63
+ def most_recent(time = :all_time)
64
+ request("standardfeeds/most_recent#{query_string(:time => time)}")
65
+ end
66
+
67
+ # Retrieve the most discussed videos for a time. Valid times are:
68
+ # * :today (1 day)
69
+ # * :this_week (7 days)
70
+ # * :this_month (1 month)
71
+ # * :all_time (default)
72
+ def most_discussed(time = :all_time)
73
+ request("standardfeeds/most_discussed#{query_string(:time => time)}")
74
+ end
75
+
76
+ # Retrieve the most linked videos for a time. Valid times are:
77
+ # * :today (1 day)
78
+ # * :this_week (7 days)
79
+ # * :this_month (1 month)
80
+ # * :all_time (default)
81
+ def most_linked(time = :all_time)
82
+ request("standardfeeds/most_linked#{query_string(:time => time)}")
83
+ end
84
+
85
+ # Retrieve the most responded videos for a time. Valid times are:
86
+ # * :today (1 day)
87
+ # * :this_week (7 days)
88
+ # * :this_month (1 month)
89
+ # * :all_time (default)
90
+ def most_responded(time = :all_time)
91
+ request("standardfeeds/most_responded#{query_string(:time => time)}")
92
+ end
93
+
94
+ # Retrieve the recently featured videos.
95
+ def recently_featured
96
+ request("standardfeeds/recently_featured")
97
+ end
98
+
99
+ # Retrieve the videos watchables on mobile.
100
+ def watch_on_mobile
101
+ request("standardfeeds/watch_on_mobile")
102
+ end
103
+
104
+ # Finds videos by categories or keywords.
105
+ #
106
+ # Capitalize words if you refer to a category.
107
+ #
108
+ # You can use the operators +NOT+(-) and +OR+(|). For example:
109
+ # find_by_category_and_tag('cats|dogs', '-rats', 'Comedy')
110
+ def find_by_category_and_tag(*tags_and_cats)
111
+ request("videos/-/#{tags_and_cats.map{ |t| CGI::escape(t) }.join('/')}")
112
+ end
113
+
114
+ # Finds videos by tags (keywords).
115
+ #
116
+ # You can use the operators +NOT+(-) and +OR+(|). For example:
117
+ # find_by_tag('cats|dogs', '-rats')
118
+ def find_by_tag(*tags)
119
+ url = "videos/-/%7Bhttp%3A%2F%2Fgdata.youtube.com%2Fschemas%2F2007%2Fkeywords.cat%7D"
120
+ keywords = tags.map{ |t| CGI::escape(t) }.join('/')
121
+ request("#{url}#{keywords}")
122
+ end
123
+
124
+ # Finds videos by tags (keywords).
125
+ #
126
+ # You can use the operators +NOT+(-) and +OR+(|). For example:
127
+ # find_by_tag('cats|dogs', '-rats')
128
+ def find_by_category(*categories)
129
+ url = "videos/-/%7Bhttp%3A%2F%2Fgdata.youtube.com%2Fschemas%2F2007%2Fcategories.cat%7D"
130
+ keywords = categories.map{ |c| CGI::escape(c) }.join('/')
131
+ request("#{url}#{keywords}")
132
+ end
133
+
134
+ # Find uploaded videos by a user.
135
+ def uploaded_by(username)
136
+ request("users/#{username}/uploads")
137
+ end
138
+
139
+ # Comments for a video.
140
+ def comments_for(video)
141
+ request(video.comments.feedLink.href)
142
+ end
143
+
144
+ # Related videos for a video.
145
+ def related_to(video)
146
+ request(video.link[2].href)
147
+ end
148
+
149
+ # Responses videos for a video.
150
+ def responses_to(video)
151
+ request(video.link[1].href)
152
+ end
153
+
154
+ # Find uploaded videos by a user as default.
155
+ def uploaded_by_user(token)
156
+ get_request_with_user_as_default("users/default/uploads", token)
157
+ end
158
+
159
+ # Find a video through a search query. Options are:
160
+ # * :orderby (:relevance, :published, :viewCount, :rating)
161
+ # * :start_index
162
+ # * :max_results
163
+ # * :author
164
+ # * :lr
165
+ # * :racy (:include, :exclude)
166
+ # * :restriction
167
+ #
168
+ # See options details at {YouTube API}[http://code.google.com/apis/youtube/developers_guide_protocol.html#Searching_for_Videos]
169
+ #
170
+ # Note: +alt+ option is still in researching because it causes some errors.
171
+ def find(query, options = {})
172
+ options[:vq] = query
173
+ options[:orderby] ||= :relevance
174
+ options.delete(:alt)
175
+ params = Hash[*options.stringify_keys.collect{ |k, v|
176
+ [k.dasherize, v] }.flatten
177
+ ]
178
+ request("videos#{query_string(params)}")
179
+ end
180
+
181
+ # Search for a specific video by its ID.
182
+ # http://www.youtube.com/watch?v=JMDcOViViNY
183
+ # Here the id is: *JMDcOViViNY* NOTE: this method returns the video itself,
184
+ # no need to call @yt.video
185
+ def find_by_id(id)
186
+ request("videos/#{id}")
187
+ end
188
+
189
+ # Fetchs few YouTube categories
190
+ def video_categories
191
+ [["Film & Animation", "Film"], ["Autos & Vehicles", "Autos"], ["Music", "Music"], ["Pets & Animals", "Animals"], ["Sports", "Sports"],
192
+ ["Travel & Events", "Travel"], ["News & Politics", "News"], ["Howto & Style", "Howto"], ["Gaming", "Games"], ["Comedy", "Comedy"],
193
+ ["People & Blogs", "People"], ["Entertainment", "Entertainment"], ["Education", "Education"], ["Nonprofits & Activism", "Nonprofit"],
194
+ ["Science & Technology", "Tech"]]
195
+ end
196
+
197
+ # Fetchs all YouTube categories
198
+ def categories
199
+ connection.get('/schemas/2007/categories.cat')['category']
200
+ end
201
+
202
+ # Returns an array with only the +label+ and +term+ attributes of categories.
203
+ def categories_collection
204
+ categories.collect { |cat|
205
+ [cat['label'], cat['term']]
206
+ }
207
+ end
208
+
209
+ # Sends a POST to YouTube to get the upload url and token.
210
+ #
211
+ # Receives a hash with the following keys:
212
+ # * <tt>:title</tt> Title of the video.
213
+ # * <tt>:description</tt> Description of the video.
214
+ # * <tt>:category</tt> Category of the video.
215
+ # * <tt>:keywords</tt> Keywords for the video.
216
+ # * <tt>:auth_sub</tt> Authentication token.
217
+ # * <tt>:nexturl</tt> Url to redirect after the video has been uploaded. Leave it nil to go to http://www.youtube.com/my_videos
218
+ #
219
+ # Returns a hash with the keys:
220
+ # * <tt>:url</tt> url for upload the video to.
221
+ # * <tt>:token</tt> token hash necessary to upload.
222
+ # * <tt>:code</tt> response code of the POST.
223
+ def get_upload_url(meta)
224
+ xml_entry = build_xml_entry(meta)
225
+ headers = {
226
+ 'Authorization' => %Q(AuthSub token="#{meta[:auth_sub]}"),
227
+ 'X-GData-Client' => YT_CONFIG['auth_sub']['client_key'],
228
+ 'X-GData-Key' => "key=#{YT_CONFIG['auth_sub']['developer_key']}",
229
+ 'Content-Length' => xml_entry.length.to_s,
230
+ 'Content-Type' => "application/atom+xml; charset=UTF-8"
231
+ }
232
+ response = connection.post('/action/GetUploadToken', xml_entry, headers)
233
+ meta[:nexturl] ||= 'http://www.youtube.com/my_videos'
234
+ upload = {}
235
+ (Hpricot.XML(response.body)/:response).each do |elm|
236
+ upload[:url] = "#{(elm/:url).text}?#{{:nexturl => meta[:nexturl]}.to_query}"
237
+ upload[:token] = (elm/:token).text
238
+ end if response.code == "200"
239
+ upload[:code] = response.code
240
+
241
+ upload
242
+ end
243
+
244
+
245
+ def delete_video(video_id, token)
246
+ delete_request_with_user_as_default("users/default/uploads/#{video_id}", token)
247
+ end
248
+
249
+ def update_video(video_id, token, video_params)
250
+ xml_entry = build_xml_entry(video_params)
251
+ put_request_with_user_as_default("users/default/uploads/#{video_id}", token, xml_entry)
252
+ end
253
+
254
+ # Find status of video uploaded by a user.
255
+ def video_status(token, video_id)
256
+ get_request_with_user_as_default("users/default/uploads/#{video_id}", token)
257
+ end
258
+
259
+ def videos_with_user_as_default(token)
260
+ get_request_with_user_as_default("users/default/uploads", token)
261
+ end
262
+
263
+ protected
264
+
265
+ # Loads a response into a new Object of this class
266
+ def request(url)
267
+ url = "#{self.prefix}#{url}" unless url =~ /\Ahttp:/
268
+ new.load(extend_attributes(connection.get(url, 'Accept' => '*/*')))
269
+ end
270
+
271
+ def put_request_with_user_as_default(url, token, meta)
272
+ headers = {
273
+ 'Content-Type' => "application/atom+xml",
274
+ 'Content-Length' => meta.length.to_s,
275
+ 'Authorization' => %Q(AuthSub token="#{token}"),
276
+ 'X-GData-Client' => YT_CONFIG['auth_sub']['client_key'],
277
+ 'X-GData-Key' => "key=#{YT_CONFIG['auth_sub']['developer_key']}"
278
+ }
279
+ url = "#{self.prefix}#{url}" unless url =~ /\Ahttp:/
280
+ connection.put(url, meta, headers) rescue nil
281
+ end
282
+
283
+ def delete_request_with_user_as_default(url, token)
284
+ headers = {
285
+ 'Accept' => 'application/atom+xml',
286
+ 'Authorization' => %Q(AuthSub token="#{token}"),
287
+ 'X-GData-Client' => YT_CONFIG['auth_sub']['client_key'],
288
+ 'X-GData-Key' => "key=#{YT_CONFIG['auth_sub']['developer_key']}"
289
+ }
290
+ url = "#{self.prefix}#{url}" unless url =~ /\Ahttp:/
291
+ connection.delete(url, headers) rescue nil
292
+ end
293
+
294
+ def get_request_with_user_as_default(url, token)
295
+ headers = {
296
+ 'Accept' => '*/*',
297
+ 'Authorization' => %Q(AuthSub token="#{token}")
298
+ }
299
+ url = "#{self.prefix}#{url}" unless url =~ /\Ahttp:/
300
+ new.load(extend_attributes(connection.get(url, headers)))
301
+ end
302
+
303
+ private
304
+
305
+ # Adds some extra keys to the +attributes+ hash
306
+ def extend_attributes(yt)
307
+ unless yt['entry'].nil?
308
+ (yt['entry'].is_a?(Array) ? yt['entry'] : [yt['entry']]).each { |v| scan_id(v) }
309
+ else
310
+ scan_id(yt)
311
+ end
312
+ yt
313
+ end
314
+
315
+ # Renames the +id+ key to +api_id+ and leaves the simple video id on the
316
+ # +id+ key
317
+ def scan_id(attrs)
318
+ attrs['api_id'] = attrs['id']
319
+ attrs['id'] = attrs['api_id'].scan(/[\w-]+$/).to_s
320
+ attrs
321
+ end
322
+
323
+ # Builds the XML content to do the POST to obtain the upload url and token.
324
+ def build_xml_entry(attrs)
325
+ xml = Builder::XmlMarkup.new(:indent => 2)
326
+ xml.instruct! :xml, :version => '1.0', :encoding => nil
327
+ xml.entry :xmlns => 'http://www.w3.org/2005/Atom',
328
+ 'xmlns:media' => 'http://search.yahoo.com/mrss/',
329
+ 'xmlns:yt' => 'http://gdata.youtube.com/schemas/2007' do
330
+ xml.media :group do
331
+ xml.tag! 'media:title', attrs[:title]
332
+ xml.media :description, attrs[:content], :type => 'plain'
333
+ xml.media :category, attrs[:category], :scheme => 'http://gdata.youtube.com/schemas/2007/categories.cat'
334
+ xml.media :category, "ytm_#{YT_CONFIG['developer_tag']}", :scheme => 'http://gdata.youtube.com/schemas/2007/developertags.cat'
335
+ xml.tag! 'media:keywords', attrs[:keywords]
336
+ end
337
+ end
338
+ xml.target!
339
+ end
340
+ end
341
+ end