slainer68_youtube_it 2.1.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.
Files changed (48) hide show
  1. data/Gemfile +10 -0
  2. data/Gemfile.lock +27 -0
  3. data/Manifest.txt +37 -0
  4. data/README.rdoc +270 -0
  5. data/Rakefile +53 -0
  6. data/VERSION +1 -0
  7. data/lib/youtube_it/chain_io.rb +76 -0
  8. data/lib/youtube_it/client.rb +447 -0
  9. data/lib/youtube_it/middleware/faraday_authheader.rb +24 -0
  10. data/lib/youtube_it/middleware/faraday_oauth.rb +21 -0
  11. data/lib/youtube_it/middleware/faraday_oauth2.rb +13 -0
  12. data/lib/youtube_it/middleware/faraday_youtubeit.rb +30 -0
  13. data/lib/youtube_it/model/activity.rb +17 -0
  14. data/lib/youtube_it/model/author.rb +13 -0
  15. data/lib/youtube_it/model/category.rb +11 -0
  16. data/lib/youtube_it/model/comment.rb +16 -0
  17. data/lib/youtube_it/model/contact.rb +19 -0
  18. data/lib/youtube_it/model/content.rb +18 -0
  19. data/lib/youtube_it/model/message.rb +12 -0
  20. data/lib/youtube_it/model/playlist.rb +11 -0
  21. data/lib/youtube_it/model/rating.rb +23 -0
  22. data/lib/youtube_it/model/subscription.rb +7 -0
  23. data/lib/youtube_it/model/thumbnail.rb +17 -0
  24. data/lib/youtube_it/model/user.rb +27 -0
  25. data/lib/youtube_it/model/video.rb +239 -0
  26. data/lib/youtube_it/parser.rb +534 -0
  27. data/lib/youtube_it/record.rb +12 -0
  28. data/lib/youtube_it/request/base_search.rb +72 -0
  29. data/lib/youtube_it/request/error.rb +15 -0
  30. data/lib/youtube_it/request/standard_search.rb +43 -0
  31. data/lib/youtube_it/request/user_search.rb +47 -0
  32. data/lib/youtube_it/request/video_search.rb +102 -0
  33. data/lib/youtube_it/request/video_upload.rb +538 -0
  34. data/lib/youtube_it/response/video_search.rb +41 -0
  35. data/lib/youtube_it/version.rb +4 -0
  36. data/lib/youtube_it.rb +83 -0
  37. data/slainer68_youtube_it.gemspec +102 -0
  38. data/test/files/recorded_response.xml +1 -0
  39. data/test/files/youtube_video_response.xml +53 -0
  40. data/test/helper.rb +9 -0
  41. data/test/test.mov +0 -0
  42. data/test/test_chain_io.rb +63 -0
  43. data/test/test_client.rb +435 -0
  44. data/test/test_field_search.rb +48 -0
  45. data/test/test_video.rb +48 -0
  46. data/test/test_video_feed_parser.rb +271 -0
  47. data/test/test_video_search.rb +141 -0
  48. metadata +172 -0
@@ -0,0 +1,12 @@
1
+ class YouTubeIt
2
+ class Record #:nodoc:
3
+ def initialize (params)
4
+ return if params.nil?
5
+
6
+ params.each do |key, value|
7
+ name = key.to_s
8
+ instance_variable_set("@#{name}", value) if respond_to?(name)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,72 @@
1
+ class YouTubeIt
2
+ module Request #:nodoc:
3
+ class BaseSearch #:nodoc:
4
+ attr_reader :url
5
+
6
+ private
7
+
8
+ def base_url
9
+ "http://gdata.youtube.com/feeds/api/"
10
+ end
11
+
12
+ def set_instance_variables( variables )
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)
20
+ qs = params.to_a.map { | k, v | v.nil? ? nil : "#{YouTubeIt.esc(k)}=#{YouTubeIt.esc(v)}" }.compact.sort.join('&')
21
+ qs.empty? ? '' : (@dev_key ? "?#{qs}&key=#{@dev_key}" : "?#{qs}")
22
+ end
23
+ end
24
+
25
+
26
+ module FieldSearch
27
+ def default_fields
28
+ "id,updated,openSearch:totalResults,openSearch:startIndex,openSearch:itemsPerPage"
29
+ end
30
+
31
+ def fields_to_params(fields)
32
+ return "" unless fields
33
+
34
+ fields_param = [default_fields]
35
+
36
+ if fields[:recorded]
37
+ if fields[:recorded].is_a? Range
38
+ fields_param << "entry[xs:date(yt:recorded) > xs:date('#{formatted_date(fields[:recorded].first)}') and xs:date(yt:recorded) < xs:date('#{formatted_date(fields[:recorded].last)}')]"
39
+ else
40
+ fields_param << "entry[xs:date(yt:recorded) = xs:date('#{formatted_date(fields[:recorded])}')]"
41
+ end
42
+ end
43
+
44
+ if fields[:published]
45
+ if fields[:published].is_a? Range
46
+ fields_param << "entry[xs:dateTime(published) > xs:dateTime('#{formatted_date(fields[:published].first)}T00:00:00') and xs:dateTime(published) < xs:dateTime('#{formatted_date(fields[:published].last)}T00:00:00')]"
47
+ else
48
+ fields_param << "entry[xs:date(published) = xs:date('#{formatted_date(fields[:published])}')]"
49
+ end
50
+ end
51
+
52
+ if fields[:view_count]
53
+ fields_param << "entry[yt:statistics/@viewCount > #{fields[:view_count]}]"
54
+ end
55
+
56
+
57
+ return "&fields=#{URI.escape(fields_param.join(","))}"
58
+ end
59
+
60
+ #youtube taked dates that look like 'YYYY-MM-DD'
61
+ def formatted_date(date)
62
+ return date if date.is_a? String
63
+ if date.respond_to? :strftime
64
+ date.strftime("%Y-%m-%d")
65
+ else
66
+ ""
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,15 @@
1
+ class UploadError < YouTubeIt::Error
2
+ attr_reader :code
3
+ def initialize(msg, code = 0)
4
+ super(msg)
5
+ @code = code
6
+ end
7
+ end
8
+
9
+ class AuthenticationError < YouTubeIt::Error
10
+ attr_reader :code
11
+ def initialize(msg, code = 0)
12
+ super(msg)
13
+ @code = code
14
+ end
15
+ end
@@ -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,538 @@
1
+ class YouTubeIt
2
+ module Upload
3
+
4
+ class UploadError < YouTubeIt::Error; end
5
+
6
+ class AuthenticationError < YouTubeIt::Error; end
7
+
8
+ # Implements video uploads/updates/deletions
9
+ #
10
+ # require 'youtube_it'
11
+ #
12
+ # uploader = YouTubeIt::Upload::VideoUpload.new("user", "pass", "dev-key")
13
+ # uploader.upload File.open("test.m4v"), :title => 'test',
14
+ # :description => 'cool vid d00d',
15
+ # :category => 'People',
16
+ # :keywords => %w[cool blah test]
17
+ #
18
+ class VideoUpload
19
+ include YouTubeIt::Logging
20
+
21
+ def initialize *params
22
+ if params.first.is_a?(Hash)
23
+ hash_options = params.first
24
+ @user = hash_options[:username]
25
+ @password = hash_options[:password]
26
+ @dev_key = hash_options[:dev_key]
27
+ @access_token = hash_options[:access_token]
28
+ @authsub_token = hash_options[:authsub_token]
29
+ @client_id = hash_options[:client_id] || "youtube_it"
30
+ @config_token = hash_options[:config_token]
31
+ else
32
+ 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')"
33
+ @user = params.shift
34
+ @password = params.shift
35
+ @dev_key = params.shift
36
+ @access_token = params.shift
37
+ @authsub_token = params.shift
38
+ @client_id = params.shift || "youtube_it"
39
+ @config_token = params.shift
40
+ end
41
+ end
42
+
43
+
44
+ def enable_http_debugging
45
+ @http_debugging = true
46
+ end
47
+
48
+ #
49
+ # Upload "data" to youtube, where data is either an IO object or
50
+ # raw file data.
51
+ # The hash keys for opts (which specify video info) are as follows:
52
+ # :mime_type
53
+ # :filename
54
+ # :title
55
+ # :description
56
+ # :category
57
+ # :keywords
58
+ # :private
59
+ # New V2 api hash keys for accessControl:
60
+ # :rate
61
+ # :comment
62
+ # :commentVote
63
+ # :videoRespond
64
+ # :list
65
+ # :embed
66
+ # :syndicate
67
+ # Specifying :private will make the video private, otherwise it will be public.
68
+ #
69
+ # When one of the fields is invalid according to YouTube,
70
+ # an UploadError will be raised. Its message contains a list of newline separated
71
+ # errors, containing the key and its error code.
72
+ #
73
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
74
+ def upload(data, opts = {})
75
+ @opts = { :mime_type => 'video/mp4',
76
+ :title => '',
77
+ :description => '',
78
+ :category => 'People',
79
+ :keywords => [] }.merge(opts)
80
+
81
+ @opts[:filename] ||= generate_uniq_filename_from(data)
82
+
83
+ post_body_io = generate_upload_io(video_xml, data)
84
+
85
+ upload_header = {
86
+ "Slug" => "#{@opts[:filename]}",
87
+ "Content-Type" => "multipart/related; boundary=#{boundary}",
88
+ "Content-Length" => "#{post_body_io.expected_length}",
89
+ }
90
+
91
+ upload_url = "/feeds/api/users/default/uploads"
92
+ response = yt_session(uploads_url).post(upload_url, post_body_io, upload_header)
93
+
94
+ return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
95
+ end
96
+
97
+ # Updates a video in YouTube. Requires:
98
+ # :title
99
+ # :description
100
+ # :category
101
+ # :keywords
102
+ # The following are optional attributes:
103
+ # :private
104
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
105
+ def update(video_id, options)
106
+ @opts = { :title => '',
107
+ :description => '',
108
+ :category => 'People',
109
+ :keywords => [] }.merge(options)
110
+
111
+ update_body = video_xml
112
+ update_url = "/feeds/api/users/default/uploads/%s" % video_id
113
+ response = yt_session.put(update_url, update_body)
114
+
115
+ return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
116
+ end
117
+
118
+ # Fetches the currently authenticated user's contacts (i.e. friends).
119
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
120
+ def get_my_contacts(opts)
121
+ contacts_url = "/feeds/api/users/default/contacts?v=2"
122
+ contacts_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
123
+ response = yt_session.get(contacts_url)
124
+
125
+ return YouTubeIt::Parser::ContactsParser.new(response).parse
126
+ end
127
+
128
+ def send_message(opts)
129
+ message_body = message_xml_for(opts)
130
+ message_url = "/feeds/api/users/%s/inbox" % opts[:recipient_id]
131
+ response = yt_session.post(message_url, message_body)
132
+
133
+ return {:code => response.status, :body => response.body}
134
+ end
135
+
136
+ # Fetches the currently authenticated user's messages (i.e. inbox).
137
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
138
+ def get_my_messages(opts)
139
+ messages_url = "/feeds/api/users/default/inbox"
140
+ messages_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
141
+ response = yt_session.get(messages_url)
142
+
143
+ return YouTubeIt::Parser::MessagesParser.new(response).parse
144
+ end
145
+
146
+ # Fetches the data of a video, which may be private. The video must be owned by this user.
147
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
148
+ def get_my_video(video_id)
149
+ get_url = "/feeds/api/users/default/uploads/%s" % video_id
150
+ response = yt_session.get(get_url)
151
+
152
+ return YouTubeIt::Parser::VideoFeedParser.new(response.body).parse
153
+ end
154
+
155
+ # Fetches the data of the videos of the current user, which may be private.
156
+ # When the authentication credentials are incorrect, an AuthenticationError will be raised.
157
+ def get_my_videos(opts)
158
+ max_results = opts[:per_page] || 50
159
+ start_index = ((opts[:page] || 1) -1) * max_results +1
160
+ get_url = "/feeds/api/users/default/uploads?max-results=#{max_results}&start-index=#{start_index}"
161
+ response = yt_session.get(get_url)
162
+
163
+ return YouTubeIt::Parser::VideosFeedParser.new(response.body).parse
164
+ end
165
+
166
+ # Delete a video on YouTube
167
+ def delete(video_id)
168
+ delete_url = "/feeds/api/users/default/uploads/%s" % video_id
169
+ response = yt_session.delete(delete_url)
170
+
171
+ return true
172
+ end
173
+
174
+ # Delete a video message
175
+ def delete_message(message_id)
176
+ delete_url = "/feeds/api/users/default/inbox/%s" % message_id
177
+ response = yt_session.delete(delete_url)
178
+
179
+ return true
180
+ end
181
+
182
+ def get_upload_token(options, nexturl)
183
+ @opts = options
184
+ token_body = video_xml
185
+ token_url = "/action/GetUploadToken"
186
+ response = yt_session.post(token_url, token_body)
187
+
188
+ return {:url => "#{response.body[/<url>(.+)<\/url>/, 1]}?nexturl=#{nexturl}",
189
+ :token => response.body[/<token>(.+)<\/token>/, 1]}
190
+ end
191
+
192
+ def add_comment(video_id, comment)
193
+ comment_body = video_xml_for(:comment => comment)
194
+ comment_url = "/feeds/api/videos/%s/comments" % video_id
195
+ response = yt_session.post(comment_url, comment_body)
196
+
197
+ return {:code => response.status, :body => response.body}
198
+ end
199
+
200
+ def comments(video_id, opts = {})
201
+ comment_url = "/feeds/api/videos/%s/comments?" % video_id
202
+ comment_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
203
+ response = yt_session.get(comment_url)
204
+ return YouTubeIt::Parser::CommentsFeedParser.new(response).parse
205
+ end
206
+
207
+ def add_favorite(video_id)
208
+ favorite_body = video_xml_for(:favorite => video_id)
209
+ favorite_url = "/feeds/api/users/default/favorites"
210
+ response = yt_session.post(favorite_url, favorite_body)
211
+
212
+ return {:code => response.status, :body => response.body}
213
+ end
214
+
215
+ def delete_favorite(video_id)
216
+ favorite_header = {
217
+ "GData-Version" => "1",
218
+ }
219
+ favorite_url = "/feeds/api/users/default/favorites/%s" % video_id
220
+ response = yt_session.delete(favorite_url, favorite_header)
221
+
222
+ return true
223
+ end
224
+
225
+ def profile(user)
226
+ profile_url = "/feeds/api/users/%s?v=2" % (user ? user : "default")
227
+ response = yt_session.get(profile_url)
228
+
229
+ return YouTubeIt::Parser::ProfileFeedParser.new(response).parse
230
+ end
231
+
232
+ # Return's a user's activity feed.
233
+ def get_activity(user, opts)
234
+ activity_url = "/feeds/api/events?author=%s&v=2&" % (user ? user : "default")
235
+ activity_url << opts.collect { |k,p| [k,p].join '=' }.join('&')
236
+ response = yt_session.get(activity_url)
237
+
238
+ return YouTubeIt::Parser::ActivityParser.new(response).parse
239
+ end
240
+
241
+ def playlist(playlist_id)
242
+ playlist_url = "/feeds/api/playlists/%s?v=2" % playlist_id
243
+ response = yt_session.get(playlist_url)
244
+
245
+ return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
246
+ end
247
+
248
+ def playlists(user)
249
+ playlist_url = "/feeds/api/users/%s/playlists?v=2" % (user ? user : "default")
250
+ response = yt_session.get(playlist_url)
251
+
252
+ return YouTubeIt::Parser::PlaylistsFeedParser.new(response).parse
253
+ end
254
+
255
+ def add_playlist(options)
256
+ playlist_body = video_xml_for_playlist(options)
257
+ playlist_url = "/feeds/api/users/default/playlists"
258
+ response = yt_session.post(playlist_url, playlist_body)
259
+
260
+ return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
261
+ end
262
+
263
+ def add_video_to_playlist(playlist_id, video_id)
264
+ playlist_body = video_xml_for(:playlist => video_id)
265
+ playlist_url = "/feeds/api/playlists/%s" % playlist_id
266
+ response = yt_session.post(playlist_url, playlist_body)
267
+
268
+ return {:code => response.status, :body => response.body, :playlist_entry_id => playlist_entry_id_from_playlist(response.body)}
269
+ end
270
+
271
+ def update_playlist(playlist_id, options)
272
+ playlist_body = video_xml_for_playlist(options)
273
+ playlist_url = "/feeds/api/users/default/playlists/%s" % playlist_id
274
+ response = yt_session.put(playlist_url, playlist_body)
275
+
276
+ return YouTubeIt::Parser::PlaylistFeedParser.new(response).parse
277
+ end
278
+
279
+ def delete_video_from_playlist(playlist_id, playlist_entry_id)
280
+ playlist_url = "/feeds/api/playlists/%s/%s" % [playlist_id, playlist_entry_id]
281
+ response = yt_session.delete(playlist_url)
282
+
283
+ return true
284
+ end
285
+
286
+ def delete_playlist(playlist_id)
287
+ playlist_url = "/feeds/api/users/default/playlists/%s" % playlist_id
288
+ response = yt_session.delete(playlist_url)
289
+
290
+ return true
291
+ end
292
+
293
+ def rate_video(video_id, rating)
294
+ rating_body = video_xml_for(:rating => rating)
295
+ rating_url = "/feeds/api/videos/#{video_id}/ratings"
296
+ response = yt_session.post(rating_url, rating_body)
297
+
298
+ return {:code => response.status, :body => response.body}
299
+ end
300
+
301
+ def subscriptions(user)
302
+ subscription_url = "/feeds/api/users/%s/subscriptions?v=2" % (user ? user : "default")
303
+ response = yt_session.get(subscription_url)
304
+
305
+ return YouTubeIt::Parser::SubscriptionFeedParser.new(response).parse
306
+ end
307
+
308
+ def subscribe_channel(channel_name)
309
+ subscribe_body = video_xml_for(:subscribe => channel_name)
310
+ subscribe_url = "/feeds/api/users/default/subscriptions"
311
+ response = yt_session.post(subscribe_url, subscribe_body)
312
+
313
+ return {:code => response.status, :body => response.body}
314
+ end
315
+
316
+ def unsubscribe_channel(subscription_id)
317
+ unsubscribe_url = "/feeds/api/users/default/subscriptions/%s" % subscription_id
318
+ response = yt_session.delete(unsubscribe_url)
319
+
320
+ return {:code => response.status, :body => response.body}
321
+ end
322
+
323
+ def favorites(user, opts = {})
324
+ favorite_url = "/feeds/api/users/%s/favorites#{opts.empty? ? '' : '?#{opts.to_param}'}" % (user ? user : "default")
325
+ response = yt_session.get(favorite_url)
326
+
327
+ return YouTubeIt::Parser::VideosFeedParser.new(response.body).parse
328
+ end
329
+
330
+ def get_current_user
331
+ current_user_url = "/feeds/api/users/default"
332
+ response = yt_session.get(current_user_url)
333
+
334
+ return REXML::Document.new(response.body).elements["entry"].elements['author'].elements['name'].text
335
+ end
336
+
337
+ def add_response(original_video_id, response_video_id)
338
+ response_body = video_xml_for(:response => response_video_id)
339
+ response_url = "/feeds/api/videos/%s/responses" % original_video_id
340
+ response = yt_session.post(response_url, response_body)
341
+
342
+ return {:code => response.status, :body => response.body}
343
+ end
344
+
345
+ def delete_response(original_video_id, response_video_id)
346
+ response_url = "/feeds/api/videos/%s/responses/%s" % [original_video_id, response_video_id]
347
+ response = yt_session.delete(response_url)
348
+
349
+ return {:code => response.status, :body => response.body}
350
+ end
351
+
352
+
353
+ private
354
+
355
+ def uploads_url
356
+ ["http://uploads", base_url.sub("http://","")].join('.')
357
+ end
358
+
359
+ def base_url
360
+ "http://gdata.youtube.com"
361
+ end
362
+
363
+ def boundary
364
+ "An43094fu"
365
+ end
366
+
367
+ def authorization_headers
368
+ header = {"X-GData-Client" => "#{@client_id}"}
369
+ header.merge!("X-GData-Key" => "key=#{@dev_key}") if @dev_key
370
+ if @authsub_token
371
+ header.merge!("Authorization" => "AuthSub token=#{@authsub_token}")
372
+ elsif @access_token.nil? && @authsub_token.nil? && @user
373
+ header.merge!("Authorization" => "GoogleLogin auth=#{auth_token}")
374
+ end
375
+ header
376
+ end
377
+
378
+ def parse_upload_error_from(string)
379
+ begin
380
+ REXML::Document.new(string).elements["//errors"].inject('') do | all_faults, error|
381
+ if error.elements["internalReason"]
382
+ msg_error = error.elements["internalReason"].text
383
+ elsif error.elements["location"]
384
+ msg_error = error.elements["location"].text[/media:group\/media:(.*)\/text\(\)/,1]
385
+ else
386
+ msg_error = "Unspecified error"
387
+ end
388
+ code = error.elements["code"].text if error.elements["code"]
389
+ all_faults + sprintf("%s: %s\n", msg_error, code)
390
+ end
391
+ rescue
392
+ string[/<TITLE>(.+)<\/TITLE>/, 1] || string
393
+ end
394
+ end
395
+
396
+ def raise_on_faulty_response(response)
397
+ response_code = response.code.to_i
398
+ msg = parse_upload_error_from(response.body.gsub(/\n/, ''))
399
+
400
+ if response_code == 403 || response_code == 401
401
+ #if response_code / 10 == 40
402
+ raise AuthenticationError.new(msg, response_code)
403
+ elsif response_code / 10 != 20 # Response in 20x means success
404
+ raise UploadError.new(msg, response_code)
405
+ end
406
+ end
407
+
408
+ def uploaded_video_id_from(string)
409
+ xml = REXML::Document.new(string)
410
+ xml.elements["//id"].text[/videos\/(.+)/, 1]
411
+ end
412
+
413
+ def playlist_id_from(string)
414
+ xml = REXML::Document.new(string)
415
+ entry = xml.elements["entry"]
416
+ entry.elements["id"].text[/playlist([^<]+)/, 1].sub(':','')
417
+ end
418
+
419
+ # If data can be read, use the first 1024 bytes as filename. If data
420
+ # is a file, use path. If data is a string, checksum it
421
+ def generate_uniq_filename_from(data)
422
+ if data.respond_to?(:path)
423
+ Digest::MD5.hexdigest(data.path)
424
+ elsif data.respond_to?(:read)
425
+ chunk = data.read(1024)
426
+ data.rewind
427
+ Digest::MD5.hexdigest(chunk)
428
+ else
429
+ Digest::MD5.hexdigest(data)
430
+ end
431
+ end
432
+
433
+ def auth_token
434
+ @auth_token ||= begin
435
+ http = Faraday.new("https://www.google.com", :ssl => {:verify => false})
436
+ body = "Email=#{YouTubeIt.esc @user}&Passwd=#{YouTubeIt.esc @password}&service=youtube&source=#{YouTubeIt.esc @client_id}"
437
+ response = http.post("/youtube/accounts/ClientLogin", body, "Content-Type" => "application/x-www-form-urlencoded")
438
+ raise ::AuthenticationError.new(response.body[/Error=(.+)/,1], response.status.to_i) if response.status.to_i != 200
439
+ @auth_token = response.body[/Auth=(.+)/, 1]
440
+ end
441
+ end
442
+
443
+ # TODO: isn't there a cleaner way to output top-notch XML without requiring stuff all over the place?
444
+ def video_xml
445
+ b = Builder::XmlMarkup.new
446
+ b.instruct!
447
+ 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 |
448
+ m.tag!("media:group") do | mg |
449
+ mg.tag!("media:title", @opts[:title], :type => "plain")
450
+ mg.tag!("media:description", @opts[:description], :type => "plain")
451
+ mg.tag!("media:keywords", @opts[:keywords].join(","))
452
+ mg.tag!('media:category', @opts[:category], :scheme => "http://gdata.youtube.com/schemas/2007/categories.cat")
453
+ mg.tag!('yt:private') if @opts[:private]
454
+ mg.tag!('media:category', @opts[:dev_tag], :scheme => "http://gdata.youtube.com/schemas/2007/developertags.cat") if @opts[:dev_tag]
455
+ end
456
+ m.tag!("yt:accessControl", :action => "rate", :permission => @opts[:rate]) if @opts[:rate]
457
+ m.tag!("yt:accessControl", :action => "comment", :permission => @opts[:comment]) if @opts[:comment]
458
+ m.tag!("yt:accessControl", :action => "commentVote", :permission => @opts[:commentVote]) if @opts[:commentVote]
459
+ m.tag!("yt:accessControl", :action => "videoRespond", :permission => @opts[:videoRespond]) if @opts[:videoRespond]
460
+ m.tag!("yt:accessControl", :action => "list", :permission => @opts[:list]) if @opts[:list]
461
+ m.tag!("yt:accessControl", :action => "embed", :permission => @opts[:embed]) if @opts[:embed]
462
+ m.tag!("yt:accessControl", :action => "syndicate", :permission => @opts[:syndicate]) if @opts[:syndicate]
463
+ end.to_s
464
+ end
465
+
466
+ def video_xml_for(data)
467
+ b = Builder::XmlMarkup.new
468
+ b.instruct!
469
+ b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
470
+ m.content(data[:comment]) if data[:comment]
471
+ m.id(data[:favorite] || data[:playlist] || data[:response]) if data[:favorite] || data[:playlist] || data[:response]
472
+ m.tag!("yt:rating", :value => data[:rating]) if data[:rating]
473
+ if(data[:subscribe])
474
+ m.category(:scheme => "http://gdata.youtube.com/schemas/2007/subscriptiontypes.cat", :term => "channel")
475
+ m.tag!("yt:username", data[:subscribe])
476
+ end
477
+ end.to_s
478
+ end
479
+
480
+ def message_xml_for(data)
481
+ b = Builder::XmlMarkup.new
482
+ b.instruct!
483
+ b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
484
+ m.id(data[:vedio_id]) #if data[:vedio_id]
485
+ m.title(data[:title]) if data[:title]
486
+ m.summary(data[:message])
487
+ end.to_s
488
+ end
489
+
490
+ def video_xml_for_playlist(data)
491
+ b = Builder::XmlMarkup.new
492
+ b.instruct!
493
+ b.entry(:xmlns => "http://www.w3.org/2005/Atom", 'xmlns:yt' => "http://gdata.youtube.com/schemas/2007") do | m |
494
+ m.title(data[:title]) if data[:title]
495
+ m.summary(data[:description] || data[:summary]) if data[:description] || data[:summary]
496
+ m.tag!('yt:private') if data[:private]
497
+ end.to_s
498
+ end
499
+
500
+ def generate_upload_io(video_xml, data)
501
+ post_body = [
502
+ "--#{boundary}\r\n",
503
+ "Content-Type: application/atom+xml; charset=UTF-8\r\n\r\n",
504
+ video_xml,
505
+ "\r\n--#{boundary}\r\n",
506
+ "Content-Type: #{@opts[:mime_type]}\r\nContent-Transfer-Encoding: binary\r\n\r\n",
507
+ data,
508
+ "\r\n--#{boundary}--\r\n",
509
+ ]
510
+
511
+ # Use Greedy IO to not be limited by 1K chunks
512
+ YouTubeIt::GreedyChainIO.new(post_body)
513
+ end
514
+
515
+ def playlist_entry_id_from_playlist(string)
516
+ playlist_xml = REXML::Document.new(string)
517
+ playlist_xml.elements.each("/entry") do |item|
518
+ return item.elements["id"].text[/^.*:([^:]+)$/,1]
519
+ end
520
+ end
521
+
522
+ def yt_session(url = nil)
523
+ Faraday.new(:url => (url ? url : base_url), :ssl => {:verify => false}) do |builder|
524
+ if @access_token
525
+ if @config_token
526
+ builder.use Faraday::Request::OAuth, @config_token
527
+ else
528
+ builder.use Faraday::Request::OAuth2, @access_token
529
+ end
530
+ end
531
+ builder.use Faraday::Request::AuthHeader, authorization_headers
532
+ builder.use Faraday::Response::YouTubeIt
533
+ builder.adapter Faraday.default_adapter
534
+ end
535
+ end
536
+ end
537
+ end
538
+ end