msp-youtube-g 0.4.7 → 0.4.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +3 -0
- data/README.txt +2 -0
- data/Rakefile +5 -0
- data/TODO.txt +0 -2
- data/integration-test/test_client.rb +256 -0
- data/integration-test/test_video.rb +42 -0
- data/integration-test/test_video_search.rb +128 -0
- data/lib/youtube_g.rb +13 -3
- data/lib/youtube_g/client.rb +58 -13
- data/lib/youtube_g/logger.rb +0 -2
- data/lib/youtube_g/model/author.rb +3 -0
- data/lib/youtube_g/model/category.rb +4 -1
- data/lib/youtube_g/model/contact.rb +8 -0
- data/lib/youtube_g/model/content.rb +5 -0
- data/lib/youtube_g/model/playlist.rb +1 -0
- data/lib/youtube_g/model/rating.rb +7 -0
- data/lib/youtube_g/model/thumbnail.rb +7 -0
- data/lib/youtube_g/model/upload_error.rb +13 -0
- data/lib/youtube_g/model/video.rb +106 -27
- data/lib/youtube_g/parser.rb +15 -19
- data/lib/youtube_g/record.rb +1 -1
- data/lib/youtube_g/request/base_search.rb +43 -0
- data/lib/youtube_g/request/standard_search.rb +40 -0
- data/lib/youtube_g/request/user_search.rb +18 -0
- data/lib/youtube_g/request/video_search.rb +40 -115
- data/lib/youtube_g/request/video_upload.rb +16 -21
- data/lib/youtube_g/response/video_search.rb +7 -7
- data/test/search.xml +66 -0
- data/test/test_parser.rb +172 -0
- data/test/test_upload.rb +79 -0
- data/test/upload.xml +51 -0
- metadata +15 -8
data/lib/youtube_g/parser.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
|
-
require 'cgi'
|
2
|
-
require 'open-uri'
|
3
|
-
require 'rexml/document'
|
4
|
-
|
5
1
|
class YouTubeG
|
6
|
-
module Parser
|
7
|
-
class FeedParserError < Exception; end
|
2
|
+
module Parser #:nodoc:
|
3
|
+
class FeedParserError < Exception; end #:nodoc:
|
8
4
|
|
9
|
-
class FeedParser
|
5
|
+
class FeedParser #:nodoc:
|
10
6
|
attr_reader :url_based
|
11
7
|
alias :url_based? :url_based
|
12
|
-
|
8
|
+
|
9
|
+
# Accept a URL or a string assumed to be XML.
|
13
10
|
def initialize(arg)
|
14
11
|
@url_based = assert_valid_url(arg)
|
15
12
|
@content = arg
|
@@ -32,7 +29,7 @@ class YouTubeG
|
|
32
29
|
end
|
33
30
|
end
|
34
31
|
|
35
|
-
class UploadErrorParser
|
32
|
+
class UploadErrorParser #:nodoc:
|
36
33
|
def initialize(xml)
|
37
34
|
raise YouTubeG::Parser::FeedParserError.new("You must pass some xml") if xml == ''
|
38
35
|
@doc = REXML::Document.new(xml)
|
@@ -51,24 +48,23 @@ class YouTubeG
|
|
51
48
|
return upload_errors
|
52
49
|
end
|
53
50
|
end
|
54
|
-
|
55
|
-
class VideoFeedParser < FeedParser
|
51
|
+
|
52
|
+
class VideoFeedParser < FeedParser #:nodoc:
|
56
53
|
|
57
54
|
def parse_content(content)
|
58
55
|
doc = REXML::Document.new(content)
|
59
56
|
entry = doc.elements["entry"]
|
60
|
-
|
61
57
|
parse_entry(entry)
|
62
58
|
end
|
63
59
|
|
64
60
|
protected
|
65
|
-
def parse_entry(entry)
|
61
|
+
def parse_entry(entry)
|
66
62
|
video_id = entry.elements["id"].text
|
67
63
|
published_at = Time.parse(entry.elements["published"].text)
|
68
64
|
updated_at = Time.parse(entry.elements["updated"].text)
|
69
65
|
|
70
66
|
app_control_element = entry.elements["app:control"]
|
71
|
-
|
67
|
+
app_control = nil
|
72
68
|
if app_control_element
|
73
69
|
app_control = YouTubeG::Model::Video::AppControl.new(
|
74
70
|
:draft => app_control_element.elements["app:draft"].text,
|
@@ -140,7 +136,7 @@ class YouTubeG
|
|
140
136
|
view_count = (el = entry.elements["yt:statistics"]) ? el.attributes["viewCount"].to_i : 0
|
141
137
|
|
142
138
|
noembed = entry.elements["yt:noembed"] ? true : false
|
143
|
-
racy = entry.elements["
|
139
|
+
racy = entry.elements["media:rating"] ? true : false
|
144
140
|
|
145
141
|
YouTubeG::Model::Video.new(
|
146
142
|
:video_id => video_id,
|
@@ -163,7 +159,7 @@ class YouTubeG
|
|
163
159
|
:racy => racy)
|
164
160
|
end
|
165
161
|
|
166
|
-
def parse_media_content (media_content_element)
|
162
|
+
def parse_media_content (media_content_element)
|
167
163
|
content_url = media_content_element.attributes["url"]
|
168
164
|
format_code = media_content_element.attributes["yt:format"].to_i
|
169
165
|
format = YouTubeG::Model::Video::Format.by_code(format_code)
|
@@ -180,10 +176,10 @@ class YouTubeG
|
|
180
176
|
end
|
181
177
|
end
|
182
178
|
|
183
|
-
class VideosFeedParser < VideoFeedParser
|
179
|
+
class VideosFeedParser < VideoFeedParser #:nodoc:
|
184
180
|
|
185
181
|
private
|
186
|
-
def parse_content(content)
|
182
|
+
def parse_content(content) #:nodoc:
|
187
183
|
doc = REXML::Document.new(content)
|
188
184
|
feed = doc.elements["feed"]
|
189
185
|
|
@@ -209,4 +205,4 @@ class YouTubeG
|
|
209
205
|
end
|
210
206
|
|
211
207
|
end
|
212
|
-
end
|
208
|
+
end
|
data/lib/youtube_g/record.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Request #:nodoc:
|
3
|
+
class BaseSearch #:nodoc:
|
4
|
+
attr_reader :url
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def base_url #:nodoc:
|
9
|
+
"http://gdata.youtube.com/feeds/api/"
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_instance_variables( variables ) #:nodoc:
|
13
|
+
variables.each do |key, value|
|
14
|
+
name = key.to_s
|
15
|
+
instance_variable_set("@#{name}", value) if respond_to?(name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_query_params(params) #:nodoc:
|
20
|
+
# nothing to do if there are no params
|
21
|
+
return '' if (!params || params.empty?)
|
22
|
+
|
23
|
+
# build up the query param string, tacking on every key/value
|
24
|
+
# pair for which the value is non-nil
|
25
|
+
u = '?'
|
26
|
+
item_count = 0
|
27
|
+
params.keys.each do |key|
|
28
|
+
value = params[key]
|
29
|
+
next if value.nil?
|
30
|
+
|
31
|
+
u << '&' if (item_count > 0)
|
32
|
+
u << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
33
|
+
item_count += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# if we found no non-nil values, we've got no params so just
|
37
|
+
# return an empty string
|
38
|
+
(item_count == 0) ? '' : u
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Request #:nodoc:
|
3
|
+
class StandardSearch < BaseSearch #:nodoc:
|
4
|
+
attr_reader :max_results # max_results
|
5
|
+
attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
|
6
|
+
attr_reader :offset # start-index
|
7
|
+
attr_reader :time # time
|
8
|
+
|
9
|
+
TYPES = [ :top_rated, :top_favorites, :most_viewed, :most_popular,
|
10
|
+
:most_recent, :most_discussed, :most_linked, :most_responded,
|
11
|
+
:recently_featured, :watch_on_mobile ]
|
12
|
+
|
13
|
+
def initialize(type, options={})
|
14
|
+
if TYPES.include?(type)
|
15
|
+
@max_results, @order_by, @offset, @time = nil
|
16
|
+
set_instance_variables(options)
|
17
|
+
@url = base_url + type.to_s << build_query_params(to_youtube_params)
|
18
|
+
else
|
19
|
+
raise "Invalid type, must be one of: #{ TYPES.map { |t| t.to_s }.join(", ") }"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def base_url #:nodoc:
|
26
|
+
super << "standardfeeds/"
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_youtube_params #:nodoc:
|
30
|
+
{
|
31
|
+
'max-results' => @max_results,
|
32
|
+
'orderby' => @order_by,
|
33
|
+
'start-index' => @offset,
|
34
|
+
'time' => @time
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class YouTubeG
|
2
|
+
module Request #:nodoc:
|
3
|
+
class UserSearch < BaseSearch #:nodoc:
|
4
|
+
def initialize(params, options={})
|
5
|
+
@url = base_url
|
6
|
+
return @url << "#{options[:user]}/favorites" if params == :favorites
|
7
|
+
@url << "#{params[:user]}/uploads" if params[:user]
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def base_url #:nodoc:
|
13
|
+
super << "users/"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -1,48 +1,6 @@
|
|
1
1
|
class YouTubeG
|
2
|
-
|
3
|
-
|
4
|
-
module Request
|
5
|
-
|
6
|
-
class BaseSearch
|
7
|
-
attr_reader :url
|
8
|
-
|
9
|
-
def base_url
|
10
|
-
"http://gdata.youtube.com/feeds/api/"
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class UserSearch < BaseSearch
|
15
|
-
|
16
|
-
def initialize(params, options={})
|
17
|
-
@url = base_url
|
18
|
-
return @url << "#{options[:user]}/favorites" if params == :favorites
|
19
|
-
@url << "#{params[:user]}/uploads" if params[:user]
|
20
|
-
end
|
21
|
-
|
22
|
-
def base_url
|
23
|
-
super << "users/"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
class StandardSearch < BaseSearch
|
28
|
-
TYPES = [ :most_viewed, :top_rated, :recently_featured, :watch_on_mobile ]
|
29
|
-
TIMES = [ :all_time, :today, :this_week, :this_month ]
|
30
|
-
|
31
|
-
def initialize(type, options={})
|
32
|
-
if TYPES.include?(type)
|
33
|
-
@url = base_url << type.to_s
|
34
|
-
@url << "?time=#{CGI.escape(options.delete(:time).to_s)}" if TIMES.include?(options[:time])
|
35
|
-
else
|
36
|
-
raise "Invalid type, must be one of: #{ TYPES.map { |t| t.to_s }.join(", ") }"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def base_url
|
41
|
-
super << "standardfeeds/"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class VideoSearch < BaseSearch
|
2
|
+
module Request #:nodoc:
|
3
|
+
class VideoSearch < BaseSearch #:nodoc:
|
46
4
|
# From here: http://code.google.com/apis/youtube/reference.html#yt_format
|
47
5
|
ONLY_EMBEDDABLE = 5
|
48
6
|
|
@@ -58,34 +16,22 @@ class YouTubeG
|
|
58
16
|
attr_reader :author
|
59
17
|
|
60
18
|
def initialize(params={})
|
61
|
-
#
|
62
|
-
return if params.nil?
|
63
|
-
|
64
|
-
# initialize our various member data to avoid warnings and so we'll
|
19
|
+
# Initialize our various member data to avoid warnings and so we'll
|
65
20
|
# automatically fall back to the youtube api defaults
|
66
|
-
@max_results
|
67
|
-
@
|
68
|
-
@
|
69
|
-
@
|
70
|
-
@response_format = nil
|
71
|
-
@video_format = nil
|
72
|
-
@racy = nil
|
73
|
-
@author = nil
|
74
|
-
|
75
|
-
# build up the url corresponding to this request
|
21
|
+
@max_results, @order_by,
|
22
|
+
@offset, @query,
|
23
|
+
@response_format, @video_format,
|
24
|
+
@racy, @author = nil
|
76
25
|
@url = base_url
|
77
26
|
|
78
|
-
#
|
27
|
+
# Return a single video (base_url + /T7YazwP8GtY)
|
79
28
|
return @url << "/" << params[:video_id] if params[:video_id]
|
80
29
|
|
81
30
|
@url << "/-/" if (params[:categories] || params[:tags])
|
82
31
|
@url << categories_to_params(params.delete(:categories)) if params[:categories]
|
83
32
|
@url << tags_to_params(params.delete(:tags)) if params[:tags]
|
84
33
|
|
85
|
-
params
|
86
|
-
name = key.to_s
|
87
|
-
instance_variable_set("@#{name}", value) if respond_to?(name)
|
88
|
-
end
|
34
|
+
set_instance_variables(params)
|
89
35
|
|
90
36
|
if( params[ :only_embeddable ] )
|
91
37
|
@video_format = ONLY_EMBEDDABLE
|
@@ -94,11 +40,13 @@ class YouTubeG
|
|
94
40
|
@url << build_query_params(to_youtube_params)
|
95
41
|
end
|
96
42
|
|
97
|
-
|
43
|
+
private
|
44
|
+
|
45
|
+
def base_url #:nodoc:
|
98
46
|
super << "videos"
|
99
47
|
end
|
100
48
|
|
101
|
-
def to_youtube_params
|
49
|
+
def to_youtube_params #:nodoc:
|
102
50
|
{
|
103
51
|
'max-results' => @max_results,
|
104
52
|
'orderby' => @order_by,
|
@@ -110,58 +58,35 @@ class YouTubeG
|
|
110
58
|
'author' => @author
|
111
59
|
}
|
112
60
|
end
|
113
|
-
|
114
|
-
private
|
115
|
-
# Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
|
116
|
-
# Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
|
117
|
-
# or like: categories => [:news, :sports]
|
118
|
-
def categories_to_params(categories)
|
119
|
-
if categories.respond_to?(:keys) and categories.respond_to?(:[])
|
120
|
-
s = ""
|
121
|
-
s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
|
122
|
-
s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
|
123
|
-
s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
|
124
|
-
s
|
125
|
-
else
|
126
|
-
categories.map { |c| c.to_s.capitalize }.join("/") << '/'
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
# Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
|
131
|
-
# or tags => [:football, :soccer]
|
132
|
-
def tags_to_params(tags)
|
133
|
-
if tags.respond_to?(:keys) and tags.respond_to?(:[])
|
134
|
-
s = ""
|
135
|
-
s << tags[:either].map { |t| CGI.escape(t.to_s) }.join("%7C") << '/' if tags[:either]
|
136
|
-
s << tags[:include].map { |t| CGI.escape(t.to_s) }.join("/") << '/' if tags[:include]
|
137
|
-
s << ("-" << tags[:exclude].map { |t| CGI.escape(t.to_s) }.join("/-")) << '/' if tags[:exclude]
|
138
|
-
s
|
139
|
-
else
|
140
|
-
tags.map { |t| CGI.escape(t.to_s) }.join("/") << '/'
|
141
|
-
end
|
142
|
-
end
|
143
61
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
u << '&' if (item_count > 0)
|
157
|
-
u << "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
158
|
-
item_count += 1
|
159
|
-
end
|
160
|
-
|
161
|
-
# if we found no non-nil values, we've got no params so just
|
162
|
-
# return an empty string
|
163
|
-
(item_count == 0) ? '' : u
|
62
|
+
# Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
|
63
|
+
# Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
|
64
|
+
# or like: categories => [:news, :sports]
|
65
|
+
def categories_to_params(categories) #:nodoc:
|
66
|
+
if categories.respond_to?(:keys) and categories.respond_to?(:[])
|
67
|
+
s = ""
|
68
|
+
s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
|
69
|
+
s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
|
70
|
+
s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
|
71
|
+
s
|
72
|
+
else
|
73
|
+
categories.map { |c| c.to_s.capitalize }.join("/") << '/'
|
164
74
|
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
|
78
|
+
# or tags => [:football, :soccer]
|
79
|
+
def tags_to_params(tags) #:nodoc:
|
80
|
+
if tags.respond_to?(:keys) and tags.respond_to?(:[])
|
81
|
+
s = ""
|
82
|
+
s << tags[:either].map { |t| CGI.escape(t.to_s) }.join("%7C") << '/' if tags[:either]
|
83
|
+
s << tags[:include].map { |t| CGI.escape(t.to_s) }.join("/") << '/' if tags[:include]
|
84
|
+
s << ("-" << tags[:exclude].map { |t| CGI.escape(t.to_s) }.join("/-")) << '/' if tags[:exclude]
|
85
|
+
s
|
86
|
+
else
|
87
|
+
tags.map { |t| CGI.escape(t.to_s) }.join("/") << '/'
|
88
|
+
end
|
89
|
+
end
|
165
90
|
|
166
91
|
end
|
167
92
|
end
|
@@ -1,8 +1,3 @@
|
|
1
|
-
require 'net/https'
|
2
|
-
require 'digest/md5'
|
3
|
-
require 'rexml/document'
|
4
|
-
require 'cgi'
|
5
|
-
|
6
1
|
class YouTubeG
|
7
2
|
|
8
3
|
module Upload
|
@@ -97,15 +92,15 @@ class YouTubeG
|
|
97
92
|
|
98
93
|
private
|
99
94
|
|
100
|
-
def base_url
|
95
|
+
def base_url #:nodoc:
|
101
96
|
"uploads.gdata.youtube.com"
|
102
97
|
end
|
103
98
|
|
104
|
-
def boundary
|
99
|
+
def boundary #:nodoc:
|
105
100
|
"An43094fu"
|
106
101
|
end
|
107
102
|
|
108
|
-
def derive_auth_token
|
103
|
+
def derive_auth_token #:nodoc:
|
109
104
|
unless @auth_token
|
110
105
|
http = Net::HTTP.new("www.google.com", 443)
|
111
106
|
http.use_ssl = true
|
@@ -121,7 +116,7 @@ class YouTubeG
|
|
121
116
|
@auth_token
|
122
117
|
end
|
123
118
|
|
124
|
-
def video_xml
|
119
|
+
def video_xml #:nodoc:
|
125
120
|
video_xml = ''
|
126
121
|
video_xml << '<?xml version="1.0"?>'
|
127
122
|
video_xml << '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">'
|
@@ -135,17 +130,17 @@ class YouTubeG
|
|
135
130
|
video_xml << '</entry>'
|
136
131
|
end
|
137
132
|
|
138
|
-
def generate_upload_body(boundary, video_xml, data)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
148
|
-
|
133
|
+
def generate_upload_body(boundary, video_xml, data) #:nodoc:
|
134
|
+
uploadBody = ""
|
135
|
+
uploadBody << "--#{boundary}\r\n"
|
136
|
+
uploadBody << "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n"
|
137
|
+
uploadBody << video_xml
|
138
|
+
uploadBody << "\r\n--#{boundary}\r\n"
|
139
|
+
uploadBody << "Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n"
|
140
|
+
uploadBody << data
|
141
|
+
uploadBody << "\r\n--#{boundary}--\r\n"
|
142
|
+
end
|
143
|
+
|
149
144
|
end
|
150
145
|
end
|
151
|
-
end
|
146
|
+
end
|