chocolate_rain 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.
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