msp-youtube-g 0.4.7 → 0.4.8.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.
- 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
|