youtube 0.8.5 → 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/CHANGELOG +17 -0
  2. data/Rakefile +1 -1
  3. data/TODO +0 -3
  4. data/lib/youtube.rb +83 -50
  5. data/test/test_api.rb +131 -54
  6. metadata +4 -3
data/CHANGELOG CHANGED
@@ -1,3 +1,20 @@
1
+ * 2007/06/12
2
+
3
+ - [shaper] Fixed typo in VideoDetails, recoding -> recording
4
+ - [cardmagic] Changed URI.encode to CGI.escape to properly encode &
5
+ - [shaper] Test for character encoding
6
+ - [drummr77] Force XML parsed empty strings to be Strings and not Hashes
7
+ Discovered by shaper.
8
+ - [Rob Tsuk] Added paging support to videos_by_user
9
+ - [Rob Tsuk] Created test YouTube account for running tests
10
+ - [Thomas Cox] Added support for the embed_status tag
11
+
12
+ * 2007/06/11
13
+
14
+ - [drummr77] Removed videos_by_category since it was removed by YouTube.
15
+ - [drummr77] Renamed videos_by_category_and_tag to videos_by_category_id_and_tag.
16
+ - [drummr77] Added videos_by_category_and_tag taking in a category name and tag
17
+
1
18
  * 2007/02/12
2
19
 
3
20
  - [drummr77] Added fix to check if returned video response contains an
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ require 'rake/gempackagetask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'youtube'
9
- s.version = '0.8.5'
9
+ s.version = '0.8.6'
10
10
  s.author = 'Shane Vitarana'
11
11
  s.email = 'shanev@gmail.com'
12
12
  s.platform = Gem::Platform::RUBY
data/TODO CHANGED
@@ -6,9 +6,6 @@
6
6
  we're doing the Right/Best Thing. It looks like we've got hashes with
7
7
  one key e.g. "comments" that then map to a list of actual comments.
8
8
 
9
- - look at what we store for fields with no actual value; appears we're
10
- storing empty hashes in at least some cases.
11
-
12
9
  - add unit tests for client.videos_by_tag page and per_page params.
13
10
 
14
11
  - add unit tests for specifying alternate api host/path in client
@@ -22,11 +22,26 @@
22
22
  #++
23
23
 
24
24
  require 'net/http'
25
- require 'uri'
26
25
  require 'xmlsimple'
26
+ require 'cgi'
27
27
 
28
28
  module YouTube
29
29
 
30
+ class Category
31
+ FILMS_ANIMATION = 1
32
+ AUTOS_VEHICLES = 2
33
+ COMEDY = 23
34
+ ENTERTAINMENT = 24
35
+ MUSIC = 10
36
+ NEWS_POLITICS = 25
37
+ PEOPLE_BLOGS = 22
38
+ PETS_ANIMALS = 15
39
+ HOWTO_DIY = 26
40
+ SPORTS = 17
41
+ TRAVEL_PLACES = 19
42
+ GADGETS_GAMES = 20
43
+ end
44
+
30
45
  # Main client class managing all interaction with the YouTube server.
31
46
  # Server communication is handled via method_missing() emulating an
32
47
  # RPC-like call and performing all of the work to send out the HTTP
@@ -37,7 +52,7 @@ module YouTube
37
52
  DEFAULT_HOST = 'http://www.youtube.com'
38
53
  # the default api path to the YouTube API
39
54
  DEFAULT_API_PATH = '/api2_rest'
40
-
55
+
41
56
  def initialize(dev_id = nil, host = DEFAULT_HOST, api_path = DEFAULT_API_PATH)
42
57
  raise "developer id required" unless dev_id
43
58
 
@@ -79,8 +94,8 @@ module YouTube
79
94
  _parse_video_response(response)
80
95
  end
81
96
 
82
- # Returns a list of YouTube::Video objects that match any of the
83
- # specified +tag+s.
97
+ # Returns a list of YouTube::Video objects that match the
98
+ # specified +tag+.
84
99
  #
85
100
  # Optional parameters are:
86
101
  # +page+ = the "page" of results to retrieve (e.g. 1, 2, 3)
@@ -91,7 +106,7 @@ module YouTube
91
106
  end
92
107
 
93
108
  # Returns a list of YouTube::Video objects with the specified
94
- # +playlist id+.
109
+ # playlist +id+.
95
110
  #
96
111
  # Optional parameters are:
97
112
  # +page+ = the "page" of results to retrieve (e.g. 1, 2, 3)
@@ -101,35 +116,52 @@ module YouTube
101
116
  _parse_video_response(response)
102
117
  end
103
118
 
104
- # Returns a list of YouTube::Video objects with the specified
105
- # +category id+.
119
+ # Returns a list of YouTube::Video objects detailing the videos
120
+ # matching the supplied category +id+ and +tag+.
106
121
  #
107
122
  # Optional parameters are:
108
123
  # +page+ = the "page" of results to retrieve (e.g. 1, 2, 3)
109
124
  # +per_page+ = the number of results per page (default: 20, max 100).
110
- def videos_by_category(id, page = 1, per_page = 20)
111
- response = videos_list_by_category(:category_id => id, :page => page, :per_page => per_page)
125
+ def videos_by_category_id_and_tag(id, tag, page = 1, per_page = 20)
126
+ response = videos_list_by_category_and_tag(:category_id => id, :tag => tag, :page => page, :per_page => per_page)
112
127
  _parse_video_response(response)
113
128
  end
114
129
 
115
130
  # Returns a list of YouTube::Video objects detailing the videos
116
- # matching the supplied +category id+ and +tag+.
131
+ # matching the supplied +category+ and +tag+.
117
132
  #
133
+ # Available categories:
134
+ # YouTube::Category::FILMS_ANIMATION
135
+ # YouTube::Category::AUTOS_VEHICLES
136
+ # YouTube::Category::COMEDY
137
+ # YouTube::Category::ENTERTAINMENT
138
+ # YouTube::Category::MUSIC
139
+ # YouTube::Category::NEWS_POLITICS
140
+ # YouTube::Category::PEOPLE_BLOGS
141
+ # YouTube::Category::PETS_ANIMALS
142
+ # YouTube::Category::HOWTO_DIY
143
+ # YouTube::Category::SPORTS
144
+ # YouTube::Category::TRAVEL_PLACES
145
+ # YouTube::Category::GADGETS_GAMES
146
+ #
118
147
  # Optional parameters are:
119
148
  # +page+ = the "page" of results to retrieve (e.g. 1, 2, 3)
120
149
  # +per_page+ = the number of results per page (default: 20, max 100).
121
- def videos_by_category_and_tag(id, tag, page = 1, per_page = 20)
122
- response = videos_list_by_category_and_tag(:category_id => id, :tag => tag, :page => page, :per_page => per_page)
123
- _parse_video_response(response)
150
+ def videos_by_category_and_tag(category, tag, page = 1, per_page = 20)
151
+ videos_by_category_id_and_tag(category, tag, page, per_page)
124
152
  end
125
153
 
126
154
  # Returns a list of YouTube::Video objects detailing the videos
127
155
  # uploaded by the specified +username+.
128
- def videos_by_user(username)
129
- response = videos_list_by_user(:user => username)
130
- _parse_video_response(response)
131
- end
132
-
156
+ #
157
+ # Optional parameters are:
158
+ # +page+ = the "page" of results to retrieve (e.g. 1, 2, 3)
159
+ # +per_page+ = the number of results per page (default: 20, max 100).
160
+ def videos_by_user(username, page = 1, per_page = 20)
161
+ response = videos_list_by_user(:user => username, :page => page, :per_page => per_page)
162
+ _parse_video_response(response)
163
+ end
164
+
133
165
  # Returns a list of YouTube::Video objects detailing the current
134
166
  # global set of featured videos on YouTube.
135
167
  def featured_videos
@@ -157,18 +189,15 @@ module YouTube
157
189
 
158
190
  def _request(method, *params)
159
191
  url = _request_url(method, *params)
160
- response = XmlSimple.xml_in(_http_get(url),
161
- { 'ForceArray' => [ 'video', 'friend' ] })
162
- unless response['status'] == 'ok'
163
- raise response['error']['description'] + " : url=#{url}"
164
- end
192
+ response = XmlSimple.xml_in(_http_get(url), { 'ForceArray' => [ 'video', 'friend' ] })
193
+ raise response['error']['description'] + " : url=#{url}" unless response['status'] == 'ok'
165
194
  response
166
195
  end
167
196
 
168
197
  def _request_url(method, *params)
169
198
  param_list = String.new
170
199
  unless params.empty?
171
- params.first.each_pair { |k, v| param_list << "&#{k.to_s}=#{URI.encode(v.to_s)}" }
200
+ params.first.each_pair { |k, v| param_list << "&#{k.to_s}=#{CGI.escape(v.to_s)}" }
172
201
  end
173
202
  url = "#{@host}#{@api_path}?method=youtube.#{method}&dev_id=#{@dev_id}#{param_list}"
174
203
  end
@@ -192,7 +221,7 @@ module YouTube
192
221
  def initialize(payload)
193
222
  @favorite_count = payload['favorite_count'].to_i
194
223
  @friend_count = payload['friend_count'].to_i
195
- @user = payload['user']
224
+ @user = payload['user'].to_s
196
225
  @video_upload_count = payload['video_upload_count'].to_i
197
226
  end
198
227
  end
@@ -220,24 +249,24 @@ module YouTube
220
249
  attr_reader :video_watch_count
221
250
 
222
251
  def initialize(payload)
223
- @about_me = payload['about_me']
252
+ @about_me = payload['about_me'].to_s
224
253
  @age = payload['age'].to_i
225
- @books = payload['books']
226
- @city = payload['city']
227
- @companies = payload['companies']
228
- @country = payload['country']
254
+ @books = payload['books'].to_s
255
+ @city = payload['city'].to_s
256
+ @companies = payload['companies'].to_s
257
+ @country = payload['country'].to_s
229
258
  @currently_on = YouTube._string_to_boolean(payload['currently_on'])
230
259
  @favorite_video_count = payload['favorite_video_count'].to_i
231
- @first_name = payload['first_name']
260
+ @first_name = payload['first_name'].to_s
232
261
  @friend_count = payload['friend_count'].to_i
233
- @gender = payload['gender']
234
- @hobbies = payload['hobbies']
235
- @homepage = payload['homepage']
236
- @hometown = payload['hometown']
237
- @last_name = payload['last_name']
238
- @movies = payload['movies']
239
- @occupations = payload['occupations']
240
- @relationship = payload['relationship']
262
+ @gender = payload['gender'].to_s
263
+ @hobbies = payload['hobbies'].to_s
264
+ @homepage = payload['homepage'].to_s
265
+ @hometown = payload['hometown'].to_s
266
+ @last_name = payload['last_name'].to_s
267
+ @movies = payload['movies'].to_s
268
+ @occupations = payload['occupations'].to_s
269
+ @relationship = payload['relationship'].to_s
241
270
  @video_upload_count = payload['video_upload_count'].to_i
242
271
  @video_watch_count = payload['video_watch_count'].to_i
243
272
  end
@@ -260,16 +289,16 @@ module YouTube
260
289
  attr_reader :view_count
261
290
 
262
291
  def initialize(payload)
263
- @author = payload['author']
292
+ @author = payload['author'].to_s
264
293
  @comment_count = payload['comment_count'].to_i
265
- @description = payload['description']
294
+ @description = payload['description'].to_s
266
295
  @id = payload['id']
267
296
  @length_seconds = payload['length_seconds'].to_i
268
297
  @rating_avg = payload['rating_avg'].to_f
269
298
  @rating_count = payload['rating_count'].to_i
270
299
  @tags = payload['tags']
271
300
  @thumbnail_url = payload['thumbnail_url']
272
- @title = payload['title']
301
+ @title = payload['title'].to_s
273
302
  @upload_time = YouTube._string_to_time(payload['upload_time'])
274
303
  @url = payload['url']
275
304
  @view_count = payload['view_count'].to_i
@@ -303,7 +332,7 @@ edoc
303
332
  attr_reader :length_seconds
304
333
  attr_reader :rating_avg
305
334
  attr_reader :rating_count
306
- attr_reader :recoding_location
335
+ attr_reader :recording_location
307
336
  attr_reader :recording_country
308
337
  attr_reader :recording_date
309
338
  attr_reader :tags
@@ -312,24 +341,28 @@ edoc
312
341
  attr_reader :update_time
313
342
  attr_reader :upload_time
314
343
  attr_reader :view_count
344
+ attr_reader :embed_status
345
+ attr_reader :embed_allowed
315
346
 
316
347
  def initialize(payload)
317
- @author = payload['author']
348
+ @author = payload['author'].to_s
318
349
  @channel_list = payload['channel_list']
319
350
  @comment_list = payload['comment_list']
320
- @description = payload['description']
351
+ @description = payload['description'].to_s
321
352
  @length_seconds = payload['length_seconds'].to_i
322
353
  @rating_avg = payload['rating_avg'].to_f
323
354
  @rating_count = payload['rating_count'].to_i
324
- @recording_country = payload['recording_country']
325
- @recording_date = payload['recording_date']
326
- @recording_location = payload['recording_location']
355
+ @recording_country = payload['recording_country'].to_s
356
+ @recording_date = payload['recording_date'].to_s
357
+ @recording_location = payload['recording_location'].to_s
327
358
  @tags = payload['tags']
328
359
  @thumbnail_url = payload['thumbnail_url']
329
- @title = payload['title']
360
+ @title = payload['title'].to_s
330
361
  @update_time = YouTube._string_to_time(payload['update_time'])
331
362
  @upload_time = YouTube._string_to_time(payload['upload_time'])
332
363
  @view_count = payload['view_count'].to_i
364
+ @embed_status = payload['embed_status']
365
+ @embed_allowed = ( payload['embed_status'] == "ok" )
333
366
  end
334
367
  end
335
368
 
@@ -345,5 +378,5 @@ edoc
345
378
  # representing seconds since the epoch, or nil if the string is nil.
346
379
  def self._string_to_time(time_str)
347
380
  (time_str) ? Time.at(time_str.to_i) : nil
348
- end
381
+ end
349
382
  end
@@ -2,15 +2,25 @@ require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'youtube'
4
4
  require 'pp'
5
+ require 'yaml'
6
+ require 'set'
5
7
 
6
8
  # This test class assumes an active internet connection
7
9
  class TestAPI < Test::Unit::TestCase
8
- @@DEVELOPER_API_KEY = 'DEVELOPER_ID'
10
+ @@DEVELOPER_API_KEY = 'RCofu-vAmeY'
11
+ YOUTUBE_LIB_USERID = 'ytlibtest'
9
12
 
10
13
  def setup
11
14
  @client = YouTube::Client.new @@DEVELOPER_API_KEY
12
15
  end
13
16
 
17
+ # test to ensure escaping strange characters like ampersands works
18
+ # properly as we've been bitten by finding that they don't
19
+ def test_videos_by_tag_escape
20
+ videos = @client.videos_by_tag('cindy & margolis')
21
+ _test_video_list(videos)
22
+ end
23
+
14
24
  def test_profile
15
25
  profile = @client.profile('nutria42')
16
26
 
@@ -70,11 +80,34 @@ class TestAPI < Test::Unit::TestCase
70
80
  def test_videos_by_user
71
81
  # test case where videos exist
72
82
  videos = @client.videos_by_user('whytheluckystiff')
83
+ _test_video_list(videos)
84
+ end
85
+
86
+ def test_should_return_10_videos_for_user
87
+ videos = @client.videos_by_user(YOUTUBE_LIB_USERID)
73
88
  _test_video_list(videos)
74
-
75
- # test case where videos don't exist
76
- videos = @client.videos_by_user('br0wnpunk')
77
- assert_nil videos
89
+
90
+ assert_equal 10, videos.length
91
+ videos_seen = Set.new
92
+ videos.each do |video|
93
+ _validate_test_video(video, videos_seen)
94
+ end
95
+ end
96
+
97
+ def test_should_paginate_videos_by_user
98
+ total_videos = 10
99
+ expected_page_size = 3
100
+ videos_seen = Set.new
101
+ 1.upto(4) do |index|
102
+ videos = @client.videos_by_user(YOUTUBE_LIB_USERID, index, expected_page_size)
103
+ _test_video_list(videos)
104
+ expected_video_count = total_videos > expected_page_size ? expected_page_size : total_videos
105
+ assert_equal expected_video_count, videos.length
106
+ videos.each do |video|
107
+ _validate_test_video(video, videos_seen)
108
+ end
109
+ total_videos -= expected_page_size
110
+ end
78
111
  end
79
112
 
80
113
  def test_videos_by_related
@@ -87,16 +120,27 @@ class TestAPI < Test::Unit::TestCase
87
120
  _test_video_list(videos)
88
121
  end
89
122
 
90
- # def test_videos_by_category
91
- # videos = @client.videos_by_category('10')
92
- # _test_video_list(videos)
93
- # end
94
- #
95
- # def test_videos_by_category_and_tag
96
- # videos = @client.videos_by_category_and_tag('10', 'punk')
97
- # _test_video_list(videos)
98
- # end
123
+ def test_videos_by_category_id_and_tag
124
+ videos = @client.videos_by_category_id_and_tag(17, 'bench')
125
+ _test_video_list(videos)
126
+ end
127
+
128
+ def test_videos_by_category_and_tag
129
+ videos = @client.videos_by_category_and_tag(YouTube::Category::SPORTS, 'bench')
130
+ _test_video_list(videos)
131
+ end
132
+
133
+ def test_should_not_return_any_videos_for_videos_by_category_and_tag
134
+ videos = @client.videos_by_category_and_tag(YouTube::Category::SPORTS, 'somerandomtagthatihopedoesntexist')
135
+ assert_nil videos
136
+ end
99
137
 
138
+ def test_should_get_comment_count_for_video
139
+ videos = @client.videos_by_tag('imogen heap hide and seek')
140
+ video = videos.sort{ |a,b| b.view_count <=> a.view_count }.first
141
+ assert video.comment_count > 0
142
+ end
143
+
100
144
  def test_featured_videos
101
145
  videos = @client.featured_videos
102
146
  _test_video_list(videos)
@@ -114,7 +158,9 @@ class TestAPI < Test::Unit::TestCase
114
158
  assert (details.author && details.author.length > 0)
115
159
  assert (details.length_seconds > 0)
116
160
  assert (details.title && details.title.length > 0)
117
- assert (details.description && details.description.length > 0)
161
+ assert_instance_of String, details.description
162
+ assert details.embed_allowed if details.embed_status == "ok"
163
+ assert !details.embed_allowed if details.embed_status == "not allowed"
118
164
  end
119
165
 
120
166
  # make sure parameter validation is operating correctly
@@ -147,48 +193,79 @@ class TestAPI < Test::Unit::TestCase
147
193
  end
148
194
 
149
195
  private
150
-
151
- # Returns the number of times +substr+ exists within +text+.
152
- def _match_count (substr, text)
153
- return 0 if (!text || !substr)
154
-
155
- count = 0
156
- offset = 0
157
- while (result = text.index(substr, offset))
158
- count += 1
159
- offset = result + 1
196
+ # Returns the number of times +substr+ exists within +text+.
197
+ def _match_count (substr, text)
198
+ return 0 if (!text || !substr)
199
+
200
+ count = 0
201
+ offset = 0
202
+ while (result = text.index(substr, offset))
203
+ count += 1
204
+ offset = result + 1
205
+ end
206
+ count
160
207
  end
161
- count
162
- end
163
208
 
164
- def _assert_youtube_url (url)
165
- (url =~ /^http:\/\/www.youtube.com\//)
166
- end
167
-
168
- def _test_video_list (videos)
169
- # make sure we got some videos
170
- assert_not_nil videos
171
- assert (videos.length > 0)
172
-
173
- # make sure all video records look good
174
- videos.each { |video| _test_video(video) }
175
- end
209
+ def _assert_youtube_url (url)
210
+ (url =~ /^http:\/\/www.youtube.com\//)
211
+ end
176
212
 
177
- def _test_video (video)
178
- assert_kind_of YouTube::Video, video
213
+ def _test_video_list (videos)
214
+ # make sure we got some videos
215
+ assert_not_nil videos
216
+ assert (videos.length > 0)
179
217
 
180
- # sanity-check embed url to make sure it looks ok
181
- assert_not_nil video.embed_url
182
- _assert_youtube_url video.embed_url
218
+ # make sure all video records look good
219
+ videos.each { |video| _test_video(video) }
220
+ end
183
221
 
184
- # check other attributes
185
- assert (video.thumbnail_url =~ /\.jpg$/)
186
- assert (video.title && video.title.length > 0)
187
- assert (video.length_seconds > 0)
188
- assert (video.upload_time && video.upload_time.is_a?(Time))
189
- _assert_youtube_url video.url
190
- assert (video.view_count >= 0)
191
- assert (video.tags && video.tags.length > 0)
192
- assert (video.author && video.author.length > 0)
193
- end
222
+ def _test_video (video)
223
+ assert_kind_of YouTube::Video, video
224
+
225
+ # sanity-check embed url to make sure it looks ok
226
+ assert_not_nil video.embed_url
227
+ _assert_youtube_url video.embed_url
228
+
229
+ # check other attributes
230
+ assert (video.thumbnail_url =~ /\.jpg$/)
231
+ assert (video.title && video.title.length > 0)
232
+ assert (video.length_seconds > 0)
233
+ # make sure description came through as a string, as a single check
234
+ # that we feel good about covering other fields since the xml parsing
235
+ # of empty elements defaults to providing an empty hash but we want
236
+ # a proper type everywhere.
237
+ assert_instance_of String, video.description
238
+ assert (video.upload_time && video.upload_time.is_a?(Time))
239
+ _assert_youtube_url video.url
240
+ assert (video.view_count >= 0)
241
+ assert (video.tags && video.tags.length > 0)
242
+ assert (video.author && video.author.length > 0)
243
+ end
244
+
245
+ def _validate_test_video(video, videos_seen = nil)
246
+ values = YAML.load(video.description)
247
+ index = values["index"]
248
+
249
+ # make sure we only see a particular video once
250
+ # if the particular test cares about that
251
+ if videos_seen
252
+ assert !videos_seen.include?(index)
253
+ videos_seen.add(index)
254
+ end
255
+
256
+ title = sprintf("Test Video %02d", index)
257
+ assert_equal title, video.title
258
+ assert_equal values["length"], video.length_seconds
259
+
260
+ tags = Set.new(video.tags.split)
261
+ if index.modulo(2) == 1
262
+ assert tags.include?("ytlodd")
263
+ else
264
+ assert tags.include?("ytleven")
265
+ end
266
+
267
+ if [2,3,5,7].to_set.include?(index)
268
+ assert tags.include?("ytlprime")
269
+ end
270
+ end
194
271
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.8.11
2
+ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: youtube
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.8.5
7
- date: 2007-02-12 00:00:00 -06:00
6
+ version: 0.8.6
7
+ date: 2007-06-12 00:00:00 -04:00
8
8
  summary: A Ruby object-oriented interface to the YouTube REST API.
9
9
  require_paths:
10
10
  - lib
@@ -25,6 +25,7 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
+ post_install_message:
28
29
  authors:
29
30
  - Shane Vitarana
30
31
  files: