vibedeck-youtube_it 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +234 -0
- data/Rakefile +35 -0
- data/lib/youtube_it/chain_io.rb +76 -0
- data/lib/youtube_it/client.rb +367 -0
- data/lib/youtube_it/middleware/faraday_authheader.rb +24 -0
- data/lib/youtube_it/middleware/faraday_oauth.rb +21 -0
- data/lib/youtube_it/middleware/faraday_youtubeit.rb +30 -0
- data/lib/youtube_it/model/author.rb +13 -0
- data/lib/youtube_it/model/category.rb +11 -0
- data/lib/youtube_it/model/comment.rb +16 -0
- data/lib/youtube_it/model/contact.rb +16 -0
- data/lib/youtube_it/model/content.rb +18 -0
- data/lib/youtube_it/model/playlist.rb +11 -0
- data/lib/youtube_it/model/rating.rb +23 -0
- data/lib/youtube_it/model/subscription.rb +7 -0
- data/lib/youtube_it/model/thumbnail.rb +17 -0
- data/lib/youtube_it/model/user.rb +26 -0
- data/lib/youtube_it/model/video.rb +225 -0
- data/lib/youtube_it/parser.rb +357 -0
- data/lib/youtube_it/record.rb +12 -0
- data/lib/youtube_it/request/base_search.rb +72 -0
- data/lib/youtube_it/request/error.rb +15 -0
- data/lib/youtube_it/request/standard_search.rb +43 -0
- data/lib/youtube_it/request/user_search.rb +47 -0
- data/lib/youtube_it/request/video_search.rb +102 -0
- data/lib/youtube_it/request/video_upload.rb +415 -0
- data/lib/youtube_it/response/video_search.rb +41 -0
- data/lib/youtube_it/version.rb +4 -0
- data/lib/youtube_it.rb +75 -0
- data/test/helper.rb +10 -0
- data/test/test_chain_io.rb +63 -0
- data/test/test_client.rb +418 -0
- data/test/test_field_search.rb +48 -0
- data/test/test_video.rb +43 -0
- data/test/test_video_feed_parser.rb +271 -0
- data/test/test_video_search.rb +141 -0
- metadata +150 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
class YouTubeIt
|
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
|
+
@dev_key = options[:dev_key] if options[:dev_key]
|
15
|
+
if TYPES.include?(type)
|
16
|
+
@max_results, @order_by, @offset, @time = nil
|
17
|
+
set_instance_variables(options)
|
18
|
+
@url = base_url + type.to_s << build_query_params(to_youtube_params)
|
19
|
+
else
|
20
|
+
raise "Invalid type, must be one of: #{ TYPES.map { |t| t.to_s }.join(", ") }"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def base_url
|
27
|
+
super << "standardfeeds/"
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_youtube_params
|
31
|
+
{
|
32
|
+
'max-results' => @max_results,
|
33
|
+
'orderby' => @order_by,
|
34
|
+
'start-index' => @offset,
|
35
|
+
'time' => @time,
|
36
|
+
'v' => 2
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class YouTubeIt
|
2
|
+
module Request #:nodoc:
|
3
|
+
class UserSearch < BaseSearch #:nodoc:
|
4
|
+
include FieldSearch
|
5
|
+
attr_reader :max_results # max_results
|
6
|
+
attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
|
7
|
+
attr_reader :offset # start-index
|
8
|
+
|
9
|
+
def initialize(params, options={})
|
10
|
+
@max_results, @order_by, @offset = nil
|
11
|
+
@url = base_url
|
12
|
+
@dev_key = options[:dev_key] if options[:dev_key]
|
13
|
+
if params == :favorites
|
14
|
+
@url << "#{options[:user]}/favorites"
|
15
|
+
set_instance_variables(options)
|
16
|
+
elsif params[:user] && options[:favorites]
|
17
|
+
@url << "#{params[:user]}/favorites"
|
18
|
+
set_instance_variables(params)
|
19
|
+
return
|
20
|
+
elsif params[:user]
|
21
|
+
@url << "#{params[:user]}/uploads"
|
22
|
+
set_instance_variables(params)
|
23
|
+
end
|
24
|
+
|
25
|
+
@url << build_query_params(to_youtube_params)
|
26
|
+
@url << fields_to_params(params.delete(:fields)) if params != :favorites && params[:fields]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def base_url
|
32
|
+
super << "users/"
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_youtube_params
|
36
|
+
{
|
37
|
+
'max-results' => @max_results,
|
38
|
+
'orderby' => @order_by,
|
39
|
+
'start-index' => @offset,
|
40
|
+
'v' => 2
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class YouTubeIt
|
2
|
+
module Request #:nodoc:
|
3
|
+
class VideoSearch < BaseSearch #:nodoc:
|
4
|
+
include FieldSearch
|
5
|
+
|
6
|
+
# From here: http://code.google.com/apis/youtube/reference.html#yt_format
|
7
|
+
ONLY_EMBEDDABLE = 5
|
8
|
+
|
9
|
+
attr_reader :max_results # max_results
|
10
|
+
attr_reader :order_by # orderby, ([relevance], viewCount, published, rating)
|
11
|
+
attr_reader :offset # start-index
|
12
|
+
attr_reader :query # vq
|
13
|
+
attr_reader :response_format # alt, ([atom], rss, json)
|
14
|
+
attr_reader :tags # /-/tag1/tag2
|
15
|
+
attr_reader :categories # /-/Category1/Category2
|
16
|
+
attr_reader :video_format # format (1=mobile devices)
|
17
|
+
attr_reader :racy # racy ([exclude], include)
|
18
|
+
attr_reader :author
|
19
|
+
attr_reader :lang # lt
|
20
|
+
|
21
|
+
def initialize(params={})
|
22
|
+
# Initialize our various member data to avoid warnings and so we'll
|
23
|
+
# automatically fall back to the youtube api defaults
|
24
|
+
@max_results, @order_by,
|
25
|
+
@offset, @query,
|
26
|
+
@response_format, @video_format,
|
27
|
+
@racy, @author, @lang = nil
|
28
|
+
@url = base_url
|
29
|
+
@dev_key = params[:dev_key] if params[:dev_key]
|
30
|
+
|
31
|
+
# Return a single video (base_url + /T7YazwP8GtY)
|
32
|
+
return @url << "/" << params[:video_id] << "?v=2" if params[:video_id]
|
33
|
+
|
34
|
+
@url << "/-/" if (params[:categories] || params[:tags])
|
35
|
+
@url << categories_to_params(params.delete(:categories)) if params[:categories]
|
36
|
+
@url << tags_to_params(params.delete(:tags)) if params[:tags]
|
37
|
+
|
38
|
+
set_instance_variables(params)
|
39
|
+
|
40
|
+
if( params[ :only_embeddable ] )
|
41
|
+
@video_format = ONLY_EMBEDDABLE
|
42
|
+
end
|
43
|
+
|
44
|
+
@url << build_query_params(to_youtube_params)
|
45
|
+
@url << fields_to_params(params.delete(:fields)) if params[:fields]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def base_url
|
51
|
+
super << "videos"
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_youtube_params
|
55
|
+
{
|
56
|
+
'max-results' => @max_results,
|
57
|
+
'orderby' => @order_by,
|
58
|
+
'start-index' => @offset,
|
59
|
+
'v' => 2,
|
60
|
+
'q' => @query,
|
61
|
+
'alt' => @response_format,
|
62
|
+
'format' => @video_format,
|
63
|
+
'racy' => @racy,
|
64
|
+
'author' => @author,
|
65
|
+
'lr' => @lang
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
# Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
|
71
|
+
# Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
|
72
|
+
# or like: categories => [:news, :sports]
|
73
|
+
def categories_to_params(categories)
|
74
|
+
if categories.respond_to?(:keys) and categories.respond_to?(:[])
|
75
|
+
s = ""
|
76
|
+
s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
|
77
|
+
s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
|
78
|
+
s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
|
79
|
+
s
|
80
|
+
else
|
81
|
+
categories.map { |c| c.to_s.capitalize }.join("/") << '/'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
|
86
|
+
# or tags => [:football, :soccer]
|
87
|
+
def tags_to_params(tags)
|
88
|
+
if tags.respond_to?(:keys) and tags.respond_to?(:[])
|
89
|
+
s = ""
|
90
|
+
s << tags[:either].map { |t| YouTubeIt.esc(t.to_s) }.join("%7C") << '/' if tags[:either]
|
91
|
+
s << tags[:include].map { |t| YouTubeIt.esc(t.to_s) }.join("/") << '/' if tags[:include]
|
92
|
+
s << ("-" << tags[:exclude].map { |t| YouTubeIt.esc(t.to_s) }.join("/-")) << '/' if tags[:exclude]
|
93
|
+
s
|
94
|
+
else
|
95
|
+
tags.map { |t| YouTubeIt.esc(t.to_s) }.join("/") << '/'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
@@ -0,0 +1,415 @@
|
|
1
|
+
class YouTubeIt
|
2
|
+
module Upload
|
3
|
+
class VideoUpload
|
4
|
+
include YouTubeIt::Logging
|
5
|
+
|
6
|
+
def initialize *params
|
7
|
+
if params.first.is_a?(Hash)
|
8
|
+
hash_options = params.first
|
9
|
+
@user = hash_options[:username]
|
10
|
+
@password = hash_options[:password]
|
11
|
+
@dev_key = hash_options[:dev_key]
|
12
|
+
@access_token = hash_options[:access_token]
|
13
|
+
@authsub_token = hash_options[:authsub_token]
|
14
|
+
@client_id = hash_options[:client_id] || "youtube_it"
|
15
|
+
@config_token = hash_options[:config_token]
|
16
|
+
else
|
17
|
+
puts "* warning: the method YouTubeIt::Upload::VideoUpload.new(username, password, dev_key) is depricated, use YouTubeIt::Upload::VideoUpload.new(:username => 'user', :password => 'passwd', :dev_key => 'dev_key')"
|
18
|
+
@user = params.shift
|
19
|
+
@password = params.shift
|
20
|
+
@dev_key = params.shift
|
21
|
+
@access_token = params.shift
|
22
|
+
@authsub_token = params.shift
|
23
|
+
@client_id = params.shift || "youtube_it"
|
24
|
+
@config_token = params.shift
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def enable_http_debugging
|
30
|
+
@http_debugging = true
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Upload "data" to youtube, where data is either an IO object or
|
35
|
+
# raw file data.
|
36
|
+
# The hash keys for opts (which specify video info) are as follows:
|
37
|
+
# :mime_type
|
38
|
+
# :filename
|
39
|
+
# :title
|
40
|
+
# :description
|
41
|
+
# :category
|
42
|
+
# :keywords
|
43
|
+
# :private
|
44
|
+
# New V2 api hash keys for accessControl:
|
45
|
+
# :rate
|
46
|
+
# :comment
|
47
|
+
# :commentVote
|
48
|
+
# :videoRespond
|
49
|
+
# :list
|
50
|
+
# :embed
|
51
|
+
# :syndicate
|
52
|
+
# Specifying :private will make the video private, otherwise it will be public.
|
53
|
+
#
|
54
|
+
# When one of the fields is invalid according to YouTube,
|
55
|
+
# an UploadError will be raised. Its message contains a list of newline separated
|
56
|
+
# errors, containing the key and its error code.
|
57
|
+
#
|
58
|
+
# When the authentication credentials are incorrect, an AuthenticationError will be raised.
|
59
|
+
def upload(data, opts = {})
|
60
|
+
@opts = { :mime_type => 'video/mp4',
|
61
|
+
:title => '',
|
62
|
+
:description => '',
|
63
|
+
:category => '',
|
64
|
+
:keywords => [] }.merge(opts)
|
65
|
+
|
66
|
+
@opts[:filename] ||= generate_uniq_filename_from(data)
|
67
|
+
|
68
|
+
post_body_io = generate_upload_io(video_xml, data)
|
69
|
+
|
70
|
+
upload_header = {
|
71
|
+
"Slug" => "#{@opts[:filename]}",
|
72
|
+
"Content-Type" => "multipart/related; boundary=#{boundary}",
|
73
|
+
"Content-Length" => "#{post_body_io.expected_length}",
|
74
|
+
}
|
75
|
+
|
76
|
+
upload_url = "/feeds/api/users/default/uploads"
|
77
|
+
response = yt_session(uploads_url).post(upload_url, post_body_io, upload_header)
|
78
|
+
|
79
|
+
return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
|
80
|
+
end
|
81
|
+
|
82
|
+
# Updates a video in YouTube. Requires:
|
83
|
+
# :title
|
84
|
+
# :description
|
85
|
+
# :category
|
86
|
+
# :keywords
|
87
|
+
# The following are optional attributes:
|
88
|
+
# :private
|
89
|
+
# When the authentication credentials are incorrect, an AuthenticationError will be raised.
|
90
|
+
def update(video_id, options)
|
91
|
+
@opts = options
|
92
|
+
update_body = video_xml
|
93
|
+
update_url = "/feeds/api/users/default/uploads/%s" % video_id
|
94
|
+
response = yt_session.put(update_url, update_body)
|
95
|
+
|
96
|
+
return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
|
97
|
+
end
|
98
|
+
|
99
|
+
# Fetches the data of a video, which may be private. The video must be owned by this user.
|
100
|
+
# When the authentication credentials are incorrect, an AuthenticationError will be raised.
|
101
|
+
def get_my_video(video_id)
|
102
|
+
get_url = "/feeds/api/users/default/uploads/%s" % video_id
|
103
|
+
response = yt_session.get(get_url)
|
104
|
+
|
105
|
+
return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
|
106
|
+
end
|
107
|
+
|
108
|
+
# Fetches the data of the videos of the current user, which may be private.
|
109
|
+
# When the authentication credentials are incorrect, an AuthenticationError will be raised.
|
110
|
+
def get_my_videos(opts)
|
111
|
+
max_results = opts[:per_page] || 50
|
112
|
+
start_index = ((opts[:page] || 1) -1) * max_results +1
|
113
|
+
get_url = "/feeds/api/users/default/uploads?max-results=#{max_results}&start-index=#{start_index}"
|
114
|
+
response = yt_session.get(get_url)
|
115
|
+
|
116
|
+
return YouTubeIt::Parser::VideosFeedParser.new(response.body).parse
|
117
|
+
end
|
118
|
+
|
119
|
+
# Delete a video on YouTube
|
120
|
+
def delete(video_id)
|
121
|
+
delete_url = "/feeds/api/users/default/uploads/%s" % video_id
|
122
|
+
response = yt_session.delete(delete_url)
|
123
|
+
|
124
|
+
return true
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_upload_token(options, nexturl)
|
128
|
+
@opts = options
|
129
|
+
token_body = video_xml
|
130
|
+
token_url = "/action/GetUploadToken"
|
131
|
+
response = yt_session.post(token_url, token_body)
|
132
|
+
|
133
|
+
return {:url => "#{response.body[/<url>(.+)<\/url>/, 1]}?nexturl=#{nexturl}",
|
134
|
+
:token => response.body[/<token>(.+)<\/token>/, 1]}
|
135
|
+
end
|
136
|
+
|
137
|
+
def add_comment(video_id, comment)
|
138
|
+
comment_body = video_xml_for(:comment => comment)
|
139
|
+
comment_url = "/feeds/api/videos/%s/comments" % video_id
|
140
|
+
response = yt_session.post(comment_url, comment_body)
|
141
|
+
|
142
|
+
return {:code => response.status, :body => response.body}
|
143
|
+
end
|
144
|
+
|
145
|
+
def comments(video_id, opts = {})
|
146
|
+
comment_url = "/feeds/api/videos/%s/comments?" % video_id
|
147
|
+
comment_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
|
148
|
+
response = yt_session.get(comment_url)
|
149
|
+
|
150
|
+
return YouTubeIt::Parser::CommentsFeedParser.new(response).parse
|
151
|
+
end
|
152
|
+
|
153
|
+
def add_favorite(video_id)
|
154
|
+
favorite_body = video_xml_for(:favorite => video_id)
|
155
|
+
favorite_url = "/feeds/api/users/default/favorites"
|
156
|
+
response = yt_session.post(favorite_url, favorite_body)
|
157
|
+
|
158
|
+
return {:code => response.status, :body => response.body}
|
159
|
+
end
|
160
|
+
|
161
|
+
def delete_favorite(video_id)
|
162
|
+
favorite_header = {
|
163
|
+
"GData-Version" => "1",
|
164
|
+
}
|
165
|
+
favorite_url = "/feeds/api/users/default/favorites/%s" % video_id
|
166
|
+
response = yt_session.delete(favorite_url, favorite_header)
|
167
|
+
|
168
|
+
return true
|
169
|
+
end
|
170
|
+
|
171
|
+
def profile(user)
|
172
|
+
profile_url = "/feeds/api/users/%s?v=2" % (user ? user : "default")
|
173
|
+
response = yt_session.get(profile_url)
|
174
|
+
|
175
|
+
return YouTubeIt::Parser::ProfileFeedParser.new(response).parse
|
176
|
+
end
|
177
|
+
|
178
|
+
def playlist(playlist_id)
|
179
|
+
playlist_url = "/feeds/api/playlists/%s?v=2" % playlist_id
|
180
|
+
response = yt_session.get(playlist_url)
|
181
|
+
|
182
|
+
return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
|
183
|
+
end
|
184
|
+
|
185
|
+
def playlists(user)
|
186
|
+
playlist_url = "/feeds/api/users/%s/playlists?v=2" % (user ? user : "default")
|
187
|
+
response = yt_session.get(playlist_url)
|
188
|
+
|
189
|
+
return YouTubeIt::Parser::PlaylistsFeedParser.new(response).parse
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_playlist(options)
|
193
|
+
playlist_body = video_xml_for_playlist(options)
|
194
|
+
playlist_url = "/feeds/api/users/default/playlists"
|
195
|
+
response = yt_session.post(playlist_url, playlist_body)
|
196
|
+
|
197
|
+
return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
|
198
|
+
end
|
199
|
+
|
200
|
+
def add_video_to_playlist(playlist_id, video_id)
|
201
|
+
playlist_body = video_xml_for(:playlist => video_id)
|
202
|
+
playlist_url = "/feeds/api/playlists/%s" % playlist_id
|
203
|
+
response = yt_session.post(playlist_url, playlist_body)
|
204
|
+
|
205
|
+
return {:code => response.status, :body => response.body, :playlist_entry_id => playlist_entry_id_from_playlist(response.body)}
|
206
|
+
end
|
207
|
+
|
208
|
+
def update_playlist(playlist_id, options)
|
209
|
+
playlist_body = video_xml_for_playlist(options)
|
210
|
+
playlist_url = "/feeds/api/users/default/playlists/%s" % playlist_id
|
211
|
+
response = yt_session.put(playlist_url, playlist_body)
|
212
|
+
|
213
|
+
return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
|
214
|
+
end
|
215
|
+
|
216
|
+
def delete_video_from_playlist(playlist_id, playlist_entry_id)
|
217
|
+
playlist_url = "/feeds/api/playlists/%s/%s" % [playlist_id, playlist_entry_id]
|
218
|
+
response = yt_session.delete(playlist_url)
|
219
|
+
|
220
|
+
return true
|
221
|
+
end
|
222
|
+
|
223
|
+
def delete_playlist(playlist_id)
|
224
|
+
playlist_url = "/feeds/api/users/default/playlists/%s" % playlist_id
|
225
|
+
response = yt_session.delete(playlist_url)
|
226
|
+
|
227
|
+
return true
|
228
|
+
end
|
229
|
+
|
230
|
+
def rate_video(video_id, rating)
|
231
|
+
rating_body = video_xml_for(:rating => rating)
|
232
|
+
rating_url = "/feeds/api/videos/#{video_id}/ratings"
|
233
|
+
response = yt_session.post(rating_url, rating_body)
|
234
|
+
|
235
|
+
return {:code => response.status, :body => response.body}
|
236
|
+
end
|
237
|
+
|
238
|
+
def subscriptions(user)
|
239
|
+
subscription_url = "/feeds/api/users/%s/subscriptions?v=2" % (user ? user : "default")
|
240
|
+
response = yt_session.get(subscription_url)
|
241
|
+
|
242
|
+
return YouTubeIt::Parser::SubscriptionFeedParser.new(response).parse
|
243
|
+
end
|
244
|
+
|
245
|
+
def subscribe_channel(channel_name)
|
246
|
+
subscribe_body = video_xml_for(:subscribe => channel_name)
|
247
|
+
subscribe_url = "/feeds/api/users/default/subscriptions"
|
248
|
+
response = yt_session.post(subscribe_url, subscribe_body)
|
249
|
+
|
250
|
+
return {:code => response.status, :body => response.body}
|
251
|
+
end
|
252
|
+
|
253
|
+
def unsubscribe_channel(subscription_id)
|
254
|
+
unsubscribe_url = "/feeds/api/users/default/subscriptions/%s" % subscription_id
|
255
|
+
response = yt_session.delete(unsubscribe_url)
|
256
|
+
|
257
|
+
return {:code => response.status, :body => response.body}
|
258
|
+
end
|
259
|
+
|
260
|
+
def favorites(user, opts = {})
|
261
|
+
favorite_url = "/feeds/api/users/%s/favorites#{opts.empty? ? '' : '?#{opts.to_param}'}" % (user ? user : "default")
|
262
|
+
response = yt_session.get(favorite_url)
|
263
|
+
|
264
|
+
return YouTubeIt::Parser::VideosFeedParser.new(response.body).parse
|
265
|
+
end
|
266
|
+
|
267
|
+
def get_current_user
|
268
|
+
current_user_url = "/feeds/api/users/default"
|
269
|
+
response = yt_session.get(current_user_url, authorization_headers)
|
270
|
+
|
271
|
+
return REXML::Document.new(response.body).elements["entry"].elements['author'].elements['name'].text
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
def uploads_url
|
277
|
+
["http://uploads", base_url.sub("http://","")].join('.')
|
278
|
+
end
|
279
|
+
|
280
|
+
def base_url
|
281
|
+
"http://gdata.youtube.com"
|
282
|
+
end
|
283
|
+
|
284
|
+
def boundary
|
285
|
+
"An43094fu"
|
286
|
+
end
|
287
|
+
|
288
|
+
def authorization_headers
|
289
|
+
header = {
|
290
|
+
"X-GData-Client" => "#{@client_id}",
|
291
|
+
"X-GData-Key" => "key=#{@dev_key}",
|
292
|
+
}
|
293
|
+
if @authsub_token
|
294
|
+
header.merge!("Authorization" => "AuthSub token=#{@authsub_token}")
|
295
|
+
elsif @access_token.nil? && @authsub_token.nil?
|
296
|
+
header.merge!("Authorization" => "GoogleLogin auth=#{auth_token}")
|
297
|
+
end
|
298
|
+
header
|
299
|
+
end
|
300
|
+
|
301
|
+
def uploaded_video_id_from(string)
|
302
|
+
xml = REXML::Document.new(string)
|
303
|
+
xml.elements["//id"].text[/videos\/(.+)/, 1]
|
304
|
+
end
|
305
|
+
|
306
|
+
def playlist_id_from(string)
|
307
|
+
xml = REXML::Document.new(string)
|
308
|
+
entry = xml.elements["entry"]
|
309
|
+
entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':','')
|
310
|
+
end
|
311
|
+
|
312
|
+
# If data can be read, use the first 1024 bytes as filename. If data
|
313
|
+
# is a file, use path. If data is a string, checksum it
|
314
|
+
def generate_uniq_filename_from(data)
|
315
|
+
if data.respond_to?(:path)
|
316
|
+
Digest::MD5.hexdigest(data.path)
|
317
|
+
elsif data.respond_to?(:read)
|
318
|
+
chunk = data.read(1024)
|
319
|
+
data.rewind
|
320
|
+
Digest::MD5.hexdigest(chunk)
|
321
|
+
else
|
322
|
+
Digest::MD5.hexdigest(data)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def auth_token
|
327
|
+
@auth_token ||= begin
|
328
|
+
http = Faraday.new("https://www.google.com")
|
329
|
+
body = "Email=#{YouTubeIt.esc @user}&Passwd=#{YouTubeIt.esc @password}&service=youtube&source=#{YouTubeIt.esc @client_id}"
|
330
|
+
response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
|
331
|
+
raise ::AuthenticationError.new(response.body[/Error=(.+)/,1], response.status.to_i) if response.status.to_i != 200
|
332
|
+
@auth_token = response.body[/Auth=(.+)/, 1]
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# TODO: isn't there a cleaner way to output top-notch XML without requiring stuff all over the place?
|
337
|
+
def video_xml
|
338
|
+
b = Builder::XmlMarkup.new
|
339
|
+
b.instruct!
|
340
|
+
b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:media' => "http://search.yahoo.com/mrss/", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
|
341
|
+
m.tag!("media:group") do | mg |
|
342
|
+
mg.tag!("media:title", @opts[:title], :type => "plain")
|
343
|
+
mg.tag!("media:description", @opts[:description], :type => "plain")
|
344
|
+
mg.tag!("media:keywords", @opts[:keywords].join(","))
|
345
|
+
mg.tag!('media:category', @opts[:category], :scheme => "http://gdata.youtube.com/schemas/2007/categories.cat")
|
346
|
+
mg.tag!('yt:private') if @opts[:private]
|
347
|
+
mg.tag!('media:category', @opts[:dev_tag], :scheme => "http://gdata.youtube.com/schemas/2007/developertags.cat") if @opts[:dev_tag]
|
348
|
+
end
|
349
|
+
m.tag!("yt:accessControl", :action => "rate", :permission => @opts[:rate]) if @opts[:rate]
|
350
|
+
m.tag!("yt:accessControl", :action => "comment", :permission => @opts[:comment]) if @opts[:comment]
|
351
|
+
m.tag!("yt:accessControl", :action => "commentVote", :permission => @opts[:commentVote]) if @opts[:commentVote]
|
352
|
+
m.tag!("yt:accessControl", :action => "videoRespond", :permission => @opts[:videoRespond]) if @opts[:videoRespond]
|
353
|
+
m.tag!("yt:accessControl", :action => "list", :permission => @opts[:list]) if @opts[:list]
|
354
|
+
m.tag!("yt:accessControl", :action => "embed", :permission => @opts[:embed]) if @opts[:embed]
|
355
|
+
m.tag!("yt:accessControl", :action => "syndicate", :permission => @opts[:syndicate]) if @opts[:syndicate]
|
356
|
+
end.to_s
|
357
|
+
end
|
358
|
+
|
359
|
+
def video_xml_for(data)
|
360
|
+
b = Builder::XmlMarkup.new
|
361
|
+
b.instruct!
|
362
|
+
b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
|
363
|
+
m.content(data[:comment]) if data[:comment]
|
364
|
+
m.id(data[:favorite] || data[:playlist]) if data[:favorite] || data[:playlist]
|
365
|
+
m.tag!("yt:rating", :value => data[:rating]) if data[:rating]
|
366
|
+
if(data[:subscribe])
|
367
|
+
m.category(:scheme => "http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat", :term => "channel")
|
368
|
+
m.tag!("yt:username", data[:subscribe])
|
369
|
+
end
|
370
|
+
end.to_s
|
371
|
+
end
|
372
|
+
|
373
|
+
def video_xml_for_playlist(data)
|
374
|
+
b = Builder::XmlMarkup.new
|
375
|
+
b.instruct!
|
376
|
+
b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
|
377
|
+
m.title(data[:title]) if data[:title]
|
378
|
+
m.summary(data[:description] || data[:summary]) if data[:description] || data[:summary]
|
379
|
+
m.tag!('yt:private') if data[:private]
|
380
|
+
end.to_s
|
381
|
+
end
|
382
|
+
|
383
|
+
def generate_upload_io(video_xml, data)
|
384
|
+
post_body = [
|
385
|
+
"--#{boundary}\r\n",
|
386
|
+
"Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n",
|
387
|
+
video_xml,
|
388
|
+
"\r\n--#{boundary}\r\n",
|
389
|
+
"Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n",
|
390
|
+
data,
|
391
|
+
"\r\n--#{boundary}--\r\n",
|
392
|
+
]
|
393
|
+
|
394
|
+
# Use Greedy IO to not be limited by 1K chunks
|
395
|
+
YouTubeIt::GreedyChainIO.new(post_body)
|
396
|
+
end
|
397
|
+
|
398
|
+
def playlist_entry_id_from_playlist(string)
|
399
|
+
playlist_xml = REXML::Document.new(string)
|
400
|
+
playlist_xml.elements.each("/entry") do |item|
|
401
|
+
return item.elements["id"].text[/^.*:([^:]+)$/,1]
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def yt_session(url = nil)
|
406
|
+
Faraday.new(:url => url ? url : base_url) do |builder|
|
407
|
+
builder.use Faraday::Request::OAuth, @config_token if @config_token
|
408
|
+
builder.use Faraday::Request::AuthHeader, authorization_headers
|
409
|
+
builder.use Faraday::Response::YouTubeIt
|
410
|
+
builder.adapter Faraday.default_adapter
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class YouTubeIt
|
2
|
+
module Response
|
3
|
+
class VideoSearch < YouTubeIt::Record
|
4
|
+
# *String*:: Unique feed identifying url.
|
5
|
+
attr_reader :feed_id
|
6
|
+
|
7
|
+
# *Fixnum*:: Number of results per page.
|
8
|
+
attr_reader :max_result_count
|
9
|
+
|
10
|
+
# *Fixnum*:: 1-based offset index into the full result set.
|
11
|
+
attr_reader :offset
|
12
|
+
|
13
|
+
# *Fixnum*:: Total number of results available for the original request.
|
14
|
+
attr_reader :total_result_count
|
15
|
+
|
16
|
+
# *Time*:: Date and time at which the feed was last updated
|
17
|
+
attr_reader :updated_at
|
18
|
+
|
19
|
+
# *Array*:: Array of YouTubeIt::Model::Video records
|
20
|
+
attr_reader :videos
|
21
|
+
|
22
|
+
def current_page
|
23
|
+
((offset - 1) / max_result_count) + 1
|
24
|
+
end
|
25
|
+
|
26
|
+
# current_page + 1 or nil if there is no next page
|
27
|
+
def next_page
|
28
|
+
current_page < total_pages ? (current_page + 1) : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# current_page - 1 or nil if there is no previous page
|
32
|
+
def previous_page
|
33
|
+
current_page > 1 ? (current_page - 1) : nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def total_pages
|
37
|
+
(total_result_count / max_result_count.to_f).ceil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|