youtube 0.8.5 → 0.8.6

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 (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: