tubeclip 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/tubeclip/chain_io.rb +86 -0
- data/lib/tubeclip/client.rb +541 -0
- data/lib/tubeclip/middleware/faraday_authheader.rb +24 -0
- data/lib/tubeclip/middleware/faraday_oauth.rb +21 -0
- data/lib/tubeclip/middleware/faraday_oauth2.rb +13 -0
- data/lib/tubeclip/middleware/faraday_tubeclip.rb +38 -0
- data/lib/tubeclip/model/activity.rb +17 -0
- data/lib/tubeclip/model/author.rb +13 -0
- data/lib/tubeclip/model/caption.rb +7 -0
- data/lib/tubeclip/model/category.rb +11 -0
- data/lib/tubeclip/model/comment.rb +18 -0
- data/lib/tubeclip/model/contact.rb +19 -0
- data/lib/tubeclip/model/content.rb +18 -0
- data/lib/tubeclip/model/message.rb +12 -0
- data/lib/tubeclip/model/playlist.rb +11 -0
- data/lib/tubeclip/model/rating.rb +23 -0
- data/lib/tubeclip/model/subscription.rb +7 -0
- data/lib/tubeclip/model/thumbnail.rb +20 -0
- data/lib/tubeclip/model/user.rb +35 -0
- data/lib/tubeclip/model/video.rb +302 -0
- data/lib/tubeclip/parser.rb +643 -0
- data/lib/tubeclip/record.rb +12 -0
- data/lib/tubeclip/request/base_search.rb +76 -0
- data/lib/tubeclip/request/error.rb +21 -0
- data/lib/tubeclip/request/remote_file.rb +70 -0
- data/lib/tubeclip/request/standard_search.rb +49 -0
- data/lib/tubeclip/request/user_search.rb +47 -0
- data/lib/tubeclip/request/video_search.rb +125 -0
- data/lib/tubeclip/request/video_upload.rb +762 -0
- data/lib/tubeclip/response/video_search.rb +41 -0
- data/lib/tubeclip/version.rb +3 -0
- data/lib/tubeclip.rb +85 -0
- data/tubeclip.gemspec +44 -0
- metadata +259 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
class Tubeclip
|
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 : "#{Tubeclip.esc(k)}=#{Tubeclip.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
|
+
if fields[:entry]
|
57
|
+
fields_param << "entry[#{fields[:entry]}]"
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
return "&fields=#{URI.escape(fields_param.join(","))}"
|
62
|
+
end
|
63
|
+
|
64
|
+
#youtube taked dates that look like 'YYYY-MM-DD'
|
65
|
+
def formatted_date(date)
|
66
|
+
return date if date.is_a? String
|
67
|
+
if date.respond_to? :strftime
|
68
|
+
date.strftime("%Y-%m-%d")
|
69
|
+
else
|
70
|
+
""
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Tubeclip
|
2
|
+
class Error < RuntimeError
|
3
|
+
attr_reader :code
|
4
|
+
def initialize(msg, code = 0)
|
5
|
+
super(msg)
|
6
|
+
@code = code
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class ResourceNotFoundError < Error
|
11
|
+
def initialize(msg)
|
12
|
+
super(msg, 404)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class UploadError < Error
|
17
|
+
end
|
18
|
+
|
19
|
+
class AuthenticationError < Error
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'fiber'
|
4
|
+
class Tubeclip
|
5
|
+
module Upload
|
6
|
+
|
7
|
+
|
8
|
+
class RemoteFile
|
9
|
+
def initialize(url, opts)
|
10
|
+
@pos = 0
|
11
|
+
@url = url
|
12
|
+
@uri = URI(@url)
|
13
|
+
|
14
|
+
@content_length = opts[:content_length]
|
15
|
+
|
16
|
+
@fiber = Fiber.new do |first|
|
17
|
+
|
18
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
19
|
+
request = Net::HTTP::Get.new @uri.request_uri
|
20
|
+
http.request request do |response|
|
21
|
+
response.read_body do |chunk|
|
22
|
+
@pos += chunk.bytesize
|
23
|
+
Fiber.yield chunk
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def ping?
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def pos
|
36
|
+
@pos
|
37
|
+
end
|
38
|
+
|
39
|
+
def head
|
40
|
+
@head_result || Net::HTTP.start(@uri.host, @uri.port) do |http|
|
41
|
+
@head_result = http.request(Net::HTTP::Head.new(@uri.request_uri))
|
42
|
+
end
|
43
|
+
@head_result
|
44
|
+
end
|
45
|
+
|
46
|
+
def filename
|
47
|
+
File.basename(@url)
|
48
|
+
end
|
49
|
+
|
50
|
+
def path
|
51
|
+
@url
|
52
|
+
end
|
53
|
+
|
54
|
+
def length
|
55
|
+
@content_length ||= head.content_length
|
56
|
+
return @content_length
|
57
|
+
end
|
58
|
+
|
59
|
+
def read(buf_size = 524288)
|
60
|
+
buf = ""
|
61
|
+
while (buf.bytesize < buf_size.to_i) && @fiber.alive?
|
62
|
+
_chunk = @fiber.resume
|
63
|
+
buf << _chunk if _chunk.is_a? String
|
64
|
+
end
|
65
|
+
buf
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Tubeclip
|
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
|
+
attr_reader :region # region
|
9
|
+
attr_reader :category # category
|
10
|
+
|
11
|
+
TYPES = [ :top_rated, :top_favorites, :most_viewed, :most_popular,
|
12
|
+
:most_recent, :most_discussed, :most_linked, :most_responded,
|
13
|
+
:recently_featured, :watch_on_mobile ]
|
14
|
+
|
15
|
+
def initialize(type, options={})
|
16
|
+
@dev_key = options[:dev_key] if options[:dev_key]
|
17
|
+
if TYPES.include?(type)
|
18
|
+
@max_results, @order_by, @offset, @time = nil
|
19
|
+
set_instance_variables(options)
|
20
|
+
@url = base_url
|
21
|
+
@url << @region << "/" if @region
|
22
|
+
@url << type.to_s
|
23
|
+
@url << "_" << @category if @category
|
24
|
+
@url << build_query_params(to_youtube_params)
|
25
|
+
else
|
26
|
+
raise "Invalid type, must be one of: #{ TYPES.map { |t| t.to_s }.join(", ") }"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def base_url
|
33
|
+
super << "standardfeeds/"
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_youtube_params
|
37
|
+
{
|
38
|
+
'max-results' => @max_results,
|
39
|
+
'orderby' => @order_by,
|
40
|
+
'start-index' => @offset,
|
41
|
+
'time' => @time,
|
42
|
+
'v' => Tubeclip::API_VERSION
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Tubeclip
|
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' => Tubeclip::API_VERSION
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,125 @@
|
|
1
|
+
class Tubeclip
|
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 :safe_search # safeSearch (none, [moderate], strict)
|
18
|
+
attr_reader :author
|
19
|
+
attr_reader :lang # lt
|
20
|
+
attr_reader :restriction
|
21
|
+
attr_reader :duration
|
22
|
+
attr_reader :time
|
23
|
+
attr_reader :hd
|
24
|
+
attr_reader :caption
|
25
|
+
attr_reader :uploader
|
26
|
+
attr_reader :region
|
27
|
+
attr_reader :paid_content
|
28
|
+
attr_reader :location
|
29
|
+
attr_reader :location_radius
|
30
|
+
|
31
|
+
|
32
|
+
def initialize(params={})
|
33
|
+
# Initialize our various member data to avoid warnings and so we'll
|
34
|
+
# automatically fall back to the youtube api defaults
|
35
|
+
@max_results, @order_by,
|
36
|
+
@offset, @query,
|
37
|
+
@response_format, @video_format,
|
38
|
+
@safe_search, @author, @lang,
|
39
|
+
@duration, @time, @hd, @caption,
|
40
|
+
@uploader, @region, @location, @location_radius, @paid_content = nil
|
41
|
+
@url = base_url
|
42
|
+
@dev_key = params[:dev_key] if params[:dev_key]
|
43
|
+
|
44
|
+
# Return a single video (base_url + /T7YazwP8GtY)
|
45
|
+
return @url << "/" << params[:video_id] << "?v=#{Tubeclip::API_VERSION}" if params[:video_id]
|
46
|
+
|
47
|
+
@url << "/-/" if (params[:categories] || params[:tags])
|
48
|
+
@url << categories_to_params(params.delete(:categories)) if params[:categories]
|
49
|
+
@url << tags_to_params(params.delete(:tags)) if params[:tags]
|
50
|
+
|
51
|
+
set_instance_variables(params)
|
52
|
+
|
53
|
+
if( params[ :only_embeddable ] )
|
54
|
+
@video_format = ONLY_EMBEDDABLE
|
55
|
+
end
|
56
|
+
|
57
|
+
@url << build_query_params(to_youtube_params)
|
58
|
+
@url << fields_to_params(params.delete(:fields)) if params[:fields]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def base_url
|
64
|
+
super << "videos"
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_youtube_params
|
68
|
+
{
|
69
|
+
'max-results' => @max_results,
|
70
|
+
'orderby' => @order_by,
|
71
|
+
'start-index' => @offset,
|
72
|
+
'v' => Tubeclip::API_VERSION,
|
73
|
+
'q' => @query,
|
74
|
+
'alt' => @response_format,
|
75
|
+
'format' => @video_format,
|
76
|
+
'safeSearch' => @safe_search,
|
77
|
+
'author' => @author,
|
78
|
+
'restriction' => @restriction,
|
79
|
+
'lr' => @lang,
|
80
|
+
'duration' => @duration,
|
81
|
+
'time' => @time,
|
82
|
+
'hd' => @hd,
|
83
|
+
'caption' => @caption,
|
84
|
+
'region' => @region,
|
85
|
+
'location' => @location,
|
86
|
+
'location-radius' => @location_radius,
|
87
|
+
'paid-content' => @paid_content,
|
88
|
+
'uploader' => @uploader
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Convert category symbols into strings and build the URL. GData requires categories to be capitalized.
|
94
|
+
# Categories defined like: categories => { :include => [:news], :exclude => [:sports], :either => [..] }
|
95
|
+
# or like: categories => [:news, :sports]
|
96
|
+
def categories_to_params(categories)
|
97
|
+
if categories.respond_to?(:keys) and categories.respond_to?(:[])
|
98
|
+
s = ""
|
99
|
+
s << categories[:either].map { |c| c.to_s.capitalize }.join("%7C") << '/' if categories[:either]
|
100
|
+
s << categories[:include].map { |c| c.to_s.capitalize }.join("/") << '/' if categories[:include]
|
101
|
+
s << ("-" << categories[:exclude].map { |c| c.to_s.capitalize }.join("/-")) << '/' if categories[:exclude]
|
102
|
+
s
|
103
|
+
else
|
104
|
+
categories.map { |c| c.to_s.capitalize }.join("/") << '/'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Tags defined like: tags => { :include => [:football], :exclude => [:soccer], :either => [:polo, :tennis] }
|
109
|
+
# or tags => [:football, :soccer]
|
110
|
+
def tags_to_params(tags)
|
111
|
+
if tags.respond_to?(:keys) and tags.respond_to?(:[])
|
112
|
+
s = ""
|
113
|
+
s << tags[:either].map { |t| Tubeclip.esc(t.to_s) }.join("%7C") << '/' if tags[:either]
|
114
|
+
s << tags[:include].map { |t| Tubeclip.esc(t.to_s) }.join("/") << '/' if tags[:include]
|
115
|
+
s << ("-" << tags[:exclude].map { |t| Tubeclip.esc(t.to_s) }.join("/-")) << '/' if tags[:exclude]
|
116
|
+
s
|
117
|
+
else
|
118
|
+
tags.map { |t| Tubeclip.esc(t.to_s) }.join("/") << '/'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|