active_public_resources 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +24 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +18 -0
- data/README.md +326 -0
- data/Rakefile +7 -0
- data/active_public_resources.gemspec +27 -0
- data/active_public_resources_config.yml.example +10 -0
- data/lib/active_public_resources/base_response_type.rb +11 -0
- data/lib/active_public_resources/base_return_type.rb +17 -0
- data/lib/active_public_resources/client.rb +22 -0
- data/lib/active_public_resources/driver.rb +21 -0
- data/lib/active_public_resources/driver_response.rb +15 -0
- data/lib/active_public_resources/drivers/khan_academy.rb +143 -0
- data/lib/active_public_resources/drivers/quizlet.rb +92 -0
- data/lib/active_public_resources/drivers/schooltube.rb +99 -0
- data/lib/active_public_resources/drivers/vimeo.rb +181 -0
- data/lib/active_public_resources/drivers/youtube.rb +113 -0
- data/lib/active_public_resources/request_criteria.rb +51 -0
- data/lib/active_public_resources/response_types/exercise.rb +11 -0
- data/lib/active_public_resources/response_types/folder.rb +18 -0
- data/lib/active_public_resources/response_types/image.rb +11 -0
- data/lib/active_public_resources/response_types/quiz.rb +11 -0
- data/lib/active_public_resources/response_types/video.rb +12 -0
- data/lib/active_public_resources/return_types/file.rb +11 -0
- data/lib/active_public_resources/return_types/iframe.rb +11 -0
- data/lib/active_public_resources/return_types/image_url.rb +11 -0
- data/lib/active_public_resources/return_types/oembed.rb +11 -0
- data/lib/active_public_resources/return_types/url.rb +11 -0
- data/lib/active_public_resources/version.rb +3 -0
- data/lib/active_public_resources.rb +55 -0
- data/spec/lib/active_public_resources/client_spec.rb +75 -0
- data/spec/lib/active_public_resources/driver_spec.rb +48 -0
- data/spec/lib/active_public_resources/drivers/khan_academy_spec.rb +106 -0
- data/spec/lib/active_public_resources/drivers/quizlet_spec.rb +50 -0
- data/spec/lib/active_public_resources/drivers/schooltube_spec.rb +50 -0
- data/spec/lib/active_public_resources/drivers/vimeo_spec.rb +71 -0
- data/spec/lib/active_public_resources/drivers/youtube_spec.rb +52 -0
- data/spec/lib/active_public_resources/live_client_spec.rb +91 -0
- data/spec/lib/active_public_resources/request_criteria_spec.rb +53 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/vcr/active_public_resources/client/khan_academy_should_traverse_folders.yml +1039 -0
- data/spec/vcr/active_public_resources/client/quizlet_should_perform_request.yml +70 -0
- data/spec/vcr/active_public_resources/client/shooltube_should_perform_request.yml +815 -0
- data/spec/vcr/active_public_resources/client/vimeo_should_perform_request.yml +70 -0
- data/spec/vcr/active_public_resources/client/youtube_should_perform_request.yml +212 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_driver_perform_request/should_get_folder_cs/programming.yml +117 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_driver_perform_request/should_get_folder_cs.yml +72 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_driver_perform_request/should_get_folder_science/mcat/society_and_culture/social_structures.yml +578 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_driver_perform_request/should_get_root_folders.yml +122 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_perform_request/should_get_folder_cs/programming.yml +120 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_perform_request/should_get_folder_cs.yml +73 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_perform_request/should_get_folder_science/mcat/society_and_culture/social_structures.yml +577 -0
- data/spec/vcr/active_public_resources/drivers/khan_academy_perform_request/should_get_root_folders.yml +119 -0
- data/spec/vcr/active_public_resources/drivers/quizlet_driver_perform_request/should_perform_request.yml +75 -0
- data/spec/vcr/active_public_resources/drivers/quizlet_perform_request/should_perform_request.yml +72 -0
- data/spec/vcr/active_public_resources/drivers/schooltube_driver_perform_request/should_perform_request.yml +815 -0
- data/spec/vcr/active_public_resources/drivers/schooltube_perform_request/should_perform_request.yml +810 -0
- data/spec/vcr/active_public_resources/drivers/vimeo_driver_perform_request/should_perform_request.yml +279 -0
- data/spec/vcr/active_public_resources/drivers/vimeo_perform_request/should_perform_request.yml +70 -0
- data/spec/vcr/active_public_resources/drivers/youtube_driver_perform_request/should_perform_request.yml +212 -0
- data/spec/vcr/active_public_resources/drivers/youtube_perform_request/should_perform_request.yml +209 -0
- metadata +182 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module ActivePublicResources
|
4
|
+
module Drivers
|
5
|
+
class KhanAcademy < Driver
|
6
|
+
|
7
|
+
BASE_URL = "https://www.khanacademy.org/api/v1/"
|
8
|
+
|
9
|
+
def perform_request(request_criteria)
|
10
|
+
request_criteria.folder ||= 'root'
|
11
|
+
results, videos, exercises = get_folder(request_criteria.folder)
|
12
|
+
parse_results(request_criteria, results, videos, exercises)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def perform_rest_request(path)
|
18
|
+
uri = URI( BASE_URL + path )
|
19
|
+
res = Net::HTTP.get_response(uri)
|
20
|
+
JSON.parse(res.body)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_folder(folder_id)
|
24
|
+
results = perform_rest_request("topic/#{folder_id}")
|
25
|
+
child_data_types = results['child_data'].map { |item| item['kind'] }.uniq
|
26
|
+
videos = []
|
27
|
+
if child_data_types.include? "Video"
|
28
|
+
videos = get_videos(folder_id)
|
29
|
+
end
|
30
|
+
exercises = []
|
31
|
+
if child_data_types.include? "Exercise"
|
32
|
+
exercises = get_exercises(folder_id)
|
33
|
+
end
|
34
|
+
[results, videos, exercises]
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_videos(folder_id)
|
38
|
+
perform_rest_request("topic/#{folder_id}/videos")
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_exercises(folder_id)
|
42
|
+
perform_rest_request("topic/#{folder_id}/exercises")
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_results(request_criteria, results, videos=[], exercises=[])
|
46
|
+
parent_id = parse_parent_id(results['slug'], results['extended_slug'])
|
47
|
+
|
48
|
+
driver_response = DriverResponse.new(
|
49
|
+
:criteria => request_criteria,
|
50
|
+
:next_criteria => nil,
|
51
|
+
:total_items => results['children'].length
|
52
|
+
)
|
53
|
+
|
54
|
+
topics = results['children'].find_all { |item| item['kind'] == 'Topic' }
|
55
|
+
topics.each do |topic|
|
56
|
+
driver_response.items << parse_folder(topic, parent_id)
|
57
|
+
end
|
58
|
+
|
59
|
+
videos.each do |video_data|
|
60
|
+
driver_response.items << parse_video(video_data)
|
61
|
+
end
|
62
|
+
|
63
|
+
exercises.each do |exercise_data|
|
64
|
+
driver_response.items << parse_exercise(exercise_data)
|
65
|
+
end
|
66
|
+
|
67
|
+
driver_response
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_folder(data, parent_id)
|
71
|
+
folder = ActivePublicResources::ResponseTypes::Folder.new
|
72
|
+
folder.id = data['id']
|
73
|
+
folder.title = data['title']
|
74
|
+
folder.parent_id = parent_id
|
75
|
+
folder
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_video(data)
|
79
|
+
video = ActivePublicResources::ResponseTypes::Video.new
|
80
|
+
video.id = data['readable_id']
|
81
|
+
video.title = data['title']
|
82
|
+
video.description = data['description']
|
83
|
+
video.thumbnail_url = data['image_url']
|
84
|
+
video.url = data['url']
|
85
|
+
video.duration = data['duration'] ? data['duration'].to_i : 0
|
86
|
+
video.num_views = 0
|
87
|
+
video.num_likes = 0
|
88
|
+
video.num_comments = 0
|
89
|
+
video.created_date = data['date_added'].present? ? Date.parse(data['date_added']) : nil
|
90
|
+
video.username = data['author_names'].present? ? data['author_names'].first : ''
|
91
|
+
video.width = 640
|
92
|
+
video.height = 360
|
93
|
+
|
94
|
+
# Return Types
|
95
|
+
video.return_types << APR::ReturnTypes::Url.new(
|
96
|
+
:url => video.url,
|
97
|
+
:text => video.title,
|
98
|
+
:title => video.title
|
99
|
+
)
|
100
|
+
video.return_types << APR::ReturnTypes::Iframe.new(
|
101
|
+
:url => "//www.youtube.com/embed/#{data['youtube_id']}?feature=oembed",
|
102
|
+
:text => video.title,
|
103
|
+
:title => video.title,
|
104
|
+
:width => 640,
|
105
|
+
:height => 360
|
106
|
+
)
|
107
|
+
video
|
108
|
+
end
|
109
|
+
|
110
|
+
def parse_exercise(data)
|
111
|
+
exercise = ActivePublicResources::ResponseTypes::Exercise.new
|
112
|
+
exercise.id = data['global_id']
|
113
|
+
exercise.title = data['display_name']
|
114
|
+
exercise.description = data['description']
|
115
|
+
exercise.thumbnail_url = data['image_url_256']
|
116
|
+
exercise.url = data['ka_url']
|
117
|
+
|
118
|
+
exercise.return_types << APR::ReturnTypes::Url.new(
|
119
|
+
:url => exercise.url,
|
120
|
+
:text => exercise.title,
|
121
|
+
:title => exercise.title
|
122
|
+
)
|
123
|
+
|
124
|
+
exercise
|
125
|
+
end
|
126
|
+
|
127
|
+
def parse_parent_id(slug, extended_slug)
|
128
|
+
if extended_slug.present?
|
129
|
+
if extended_slug != slug
|
130
|
+
parts = extended_slug.split("/")
|
131
|
+
parts.pop
|
132
|
+
parts.last
|
133
|
+
else
|
134
|
+
'root'
|
135
|
+
end
|
136
|
+
else
|
137
|
+
nil
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module ActivePublicResources
|
4
|
+
module Drivers
|
5
|
+
class Quizlet < Driver
|
6
|
+
|
7
|
+
def initialize(config_options={})
|
8
|
+
validate_options(config_options, [:client_id])
|
9
|
+
@client_id = config_options[:client_id]
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform_request(request_criteria)
|
13
|
+
raise StandardError.new("driver has not been initialized properly") unless @client_id.present?
|
14
|
+
request_criteria.validate_presence!([:query])
|
15
|
+
|
16
|
+
uri = URI('https://api.quizlet.com/2.0/search/sets')
|
17
|
+
params = {
|
18
|
+
'q' => request_criteria.query,
|
19
|
+
'page' => request_criteria.page || 1,
|
20
|
+
'per_page' => request_criteria.per_page || 25,
|
21
|
+
'client_id' => @client_id
|
22
|
+
}
|
23
|
+
uri.query = URI.encode_www_form(params)
|
24
|
+
res = Net::HTTP.get_response(uri)
|
25
|
+
results = JSON.parse(res.body)
|
26
|
+
|
27
|
+
return parse_results(request_criteria, results)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def parse_results(request_criteria, results)
|
33
|
+
@driver_response = DriverResponse.new(
|
34
|
+
:criteria => request_criteria,
|
35
|
+
:next_criteria => next_criteria(request_criteria, results),
|
36
|
+
:total_items => results['total_results'],
|
37
|
+
:items => results['sets'].map { |data| parse_quiz(data) }
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def next_criteria(request_criteria, results)
|
42
|
+
if results['total_pages'] > results['page']
|
43
|
+
return RequestCriteria.new({
|
44
|
+
:query => request_criteria.query,
|
45
|
+
:page => (request_criteria.page || 1) + 1,
|
46
|
+
:per_page => (request_criteria.per_page || 25)
|
47
|
+
})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_quiz(data)
|
52
|
+
quiz = ActivePublicResources::ResponseTypes::Quiz.new
|
53
|
+
quiz.id = data['id']
|
54
|
+
quiz.title = data['title']
|
55
|
+
quiz.description = data['description']
|
56
|
+
quiz.url = data['url']
|
57
|
+
quiz.term_count = data['term_count']
|
58
|
+
quiz.created_date = Time.at(data['created_date']).utc.to_date
|
59
|
+
quiz.has_images = data['has_images']
|
60
|
+
quiz.subjects = data['subjects']
|
61
|
+
|
62
|
+
# Return Types
|
63
|
+
|
64
|
+
quiz.return_types << APR::ReturnTypes::Url.new(
|
65
|
+
:url => quiz.url,
|
66
|
+
:text => quiz.title,
|
67
|
+
:title => quiz.title
|
68
|
+
)
|
69
|
+
|
70
|
+
# See http://quizlet.com/help/can-i-embed-quizlet-on-my-website
|
71
|
+
{ flashcards: "Flashcards",
|
72
|
+
learn: "Learn",
|
73
|
+
scatter: "Scatter",
|
74
|
+
speller: "Speller",
|
75
|
+
test: "Test",
|
76
|
+
spacerace: "Space Race" }.each do |k,v|
|
77
|
+
|
78
|
+
quiz.return_types << APR::ReturnTypes::Iframe.new(
|
79
|
+
:url => "https://quizlet.com/#{data['id']}/#{k}/embedv2",
|
80
|
+
:text => v,
|
81
|
+
:title => quiz.title,
|
82
|
+
:width => "100%",
|
83
|
+
:height => 410
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
quiz
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module ActivePublicResources
|
4
|
+
module Drivers
|
5
|
+
class Schooltube < Driver
|
6
|
+
|
7
|
+
def perform_request(request_criteria)
|
8
|
+
request_criteria.validate_presence!([:query])
|
9
|
+
uri = URI('http://www.schooltube.com/api/v1/video/search/')
|
10
|
+
params = {
|
11
|
+
'term' => request_criteria.query,
|
12
|
+
'orderby' => normalize_request_criteria(request_criteria, 'sort') || '-view_count',
|
13
|
+
'offset' => offset(request_criteria.page, request_criteria.per_page),
|
14
|
+
'limit' => request_criteria.per_page || 25
|
15
|
+
}
|
16
|
+
uri.query = URI.encode_www_form(params)
|
17
|
+
res = Net::HTTP.get_response(uri)
|
18
|
+
results = JSON.parse(res.body)
|
19
|
+
|
20
|
+
return parse_results(request_criteria, results)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def offset(page, per_page)
|
26
|
+
p = page || 1
|
27
|
+
pp = per_page || 25
|
28
|
+
p * pp - pp
|
29
|
+
end
|
30
|
+
|
31
|
+
def normalize_request_criteria(request_criteria, field_name)
|
32
|
+
case field_name
|
33
|
+
when 'sort'
|
34
|
+
case request_criteria.instance_variable_get("@#{field_name}")
|
35
|
+
when 'views'
|
36
|
+
return '-view_count'
|
37
|
+
else
|
38
|
+
return '-view_count'
|
39
|
+
end
|
40
|
+
else
|
41
|
+
request_criteria.instance_variable_get("@#{field_name}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_results(request_criteria, results)
|
46
|
+
@driver_response = DriverResponse.new(
|
47
|
+
:criteria => request_criteria,
|
48
|
+
:next_criteria => next_criteria(request_criteria, results),
|
49
|
+
:total_items => nil,
|
50
|
+
:items => results['objects'].map { |data| parse_video(data) }
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def next_criteria(request_criteria, results)
|
55
|
+
if results['meta']['has_next']
|
56
|
+
return RequestCriteria.new({
|
57
|
+
:query => request_criteria.query,
|
58
|
+
:page => (request_criteria.page || 1) + 1,
|
59
|
+
:per_page => results['meta']['limit'].to_i
|
60
|
+
})
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_video(data)
|
65
|
+
video = ActivePublicResources::ResponseTypes::Video.new
|
66
|
+
video.id = data['vkey']
|
67
|
+
video.title = data['title']
|
68
|
+
video.description = data['description']
|
69
|
+
video.thumbnail_url = data['thumbnail_url']
|
70
|
+
video.url = data['short_url']
|
71
|
+
video.duration = data['duration'] ? data['duration'].to_i : 0
|
72
|
+
video.num_views = data['view_count'] ? data['view_count'].to_i : 0
|
73
|
+
video.num_likes = data['thumbs_up'] ? data['thumbs_up'].to_i : 0
|
74
|
+
video.num_comments = 0
|
75
|
+
video.created_date = Date.parse(data['create_date'])
|
76
|
+
video.username = data['username']
|
77
|
+
video.width = 640
|
78
|
+
video.height = 360
|
79
|
+
|
80
|
+
# Return Types
|
81
|
+
video.return_types << APR::ReturnTypes::Url.new(
|
82
|
+
:url => video.url,
|
83
|
+
:text => video.title,
|
84
|
+
:title => video.title
|
85
|
+
)
|
86
|
+
video.return_types << APR::ReturnTypes::Iframe.new(
|
87
|
+
:url => "//www.schooltube.com/embed/#{data['vkey']}",
|
88
|
+
:text => video.title,
|
89
|
+
:title => video.title,
|
90
|
+
:width => 640,
|
91
|
+
:height => 360
|
92
|
+
)
|
93
|
+
|
94
|
+
video
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'vimeo'
|
2
|
+
|
3
|
+
module ActivePublicResources
|
4
|
+
module Drivers
|
5
|
+
class Vimeo < Driver
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
DEFAULT_CRITERIA = {
|
9
|
+
:page => 1,
|
10
|
+
:per_page => 25,
|
11
|
+
:sort => 'relevant',
|
12
|
+
:content_filter => 'safe'
|
13
|
+
}
|
14
|
+
|
15
|
+
# Constructor
|
16
|
+
#
|
17
|
+
# @param [Hash] config_options the options which the vimeo gem requires
|
18
|
+
# @option config_options [String] :consumer_key Vimeo consumer key (required)
|
19
|
+
# @option config_options [String] :consumer_secret Vimeo consumer secret (required)
|
20
|
+
# @option config_options [String] :access_token Vimeo access token (required)
|
21
|
+
# @option config_options [String] :access_token_secret Vimeo access token secret (required)
|
22
|
+
def initialize(config_options={})
|
23
|
+
validate_options(config_options,
|
24
|
+
[:consumer_key, :consumer_secret, :access_token, :access_token_secret])
|
25
|
+
|
26
|
+
@client = ::Vimeo::Advanced::Video.new(
|
27
|
+
config_options[:consumer_key],
|
28
|
+
config_options[:consumer_secret],
|
29
|
+
token: config_options[:access_token],
|
30
|
+
secret: config_options[:access_token_secret]
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Perform search request to Vimeo with search criteria
|
35
|
+
#
|
36
|
+
# @param [Hash] criteria the criteria used to perform the search
|
37
|
+
# @option criteria [String] :query The query text to search for (required)
|
38
|
+
# @option criteria [Integer] :page The page number
|
39
|
+
# @option criteria [Integer] :per_page The number of items per page
|
40
|
+
# @option criteria [String] :sort The sort filter
|
41
|
+
# @option criteria [String] :content_filter The content filter of which to apply
|
42
|
+
# this is not supported yet - https://developer.vimeo.com/api/docs/spec
|
43
|
+
#
|
44
|
+
# @example Request
|
45
|
+
# driver = ActivePublicResources::Drivers::Vimeo.new({ .. config options .. })
|
46
|
+
# results = driver.perform_request({ query: 'education' })
|
47
|
+
#
|
48
|
+
# @example Returns
|
49
|
+
# {
|
50
|
+
# 'items': [
|
51
|
+
# {
|
52
|
+
# 'kind' : 'video',
|
53
|
+
# 'id' : '1',
|
54
|
+
# 'title' : '',
|
55
|
+
# 'description' : '',
|
56
|
+
# 'thumbnail_url' : '',
|
57
|
+
# 'url' : '',
|
58
|
+
# 'duration' : 150,
|
59
|
+
# 'num_views' : 13,
|
60
|
+
# 'num_likes' : 1,
|
61
|
+
# 'num_comments' : 2,
|
62
|
+
# 'created_date' : '',
|
63
|
+
# 'username' : '',
|
64
|
+
# 'embed_html' : '',
|
65
|
+
# 'width' : 640,
|
66
|
+
# 'height' : 360
|
67
|
+
# },
|
68
|
+
# ...
|
69
|
+
# ],
|
70
|
+
# 'meta': {
|
71
|
+
# 'nextCriteria': {
|
72
|
+
# 'query' : 'education',
|
73
|
+
# 'page' : 2
|
74
|
+
# 'per_page' : 25
|
75
|
+
# 'sort' : 'relevant'
|
76
|
+
# 'content_filter' : 'safe'
|
77
|
+
# }
|
78
|
+
# }
|
79
|
+
# }
|
80
|
+
#
|
81
|
+
# @return [JSON] the normalized response object
|
82
|
+
def perform_request(request_criteria)
|
83
|
+
request_criteria.validate_presence!([:query])
|
84
|
+
raise StandardError.new("driver has not been initialized properly") unless @client
|
85
|
+
|
86
|
+
results = @client.search(request_criteria.query, {
|
87
|
+
:page => request_criteria.page || 1,
|
88
|
+
:per_page => request_criteria.per_page || 25,
|
89
|
+
:full_response => 1,
|
90
|
+
:sort => request_criteria.sort || "relevant",
|
91
|
+
:user_id => nil,
|
92
|
+
:content_filter => request_criteria.content_filter || 'safe'
|
93
|
+
})
|
94
|
+
|
95
|
+
return parse_results(request_criteria, results)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def sort(val)
|
101
|
+
case val
|
102
|
+
when APR::RequestCriteria::SORT_RELEVANCE
|
103
|
+
'relevant'
|
104
|
+
when APR::RequestCriteria::SORT_RECENT
|
105
|
+
'newest'
|
106
|
+
when APR::RequestCriteria::SORT_POPULAR
|
107
|
+
'most_played'
|
108
|
+
else
|
109
|
+
'relevant'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def content_filter(val)
|
114
|
+
case val
|
115
|
+
when APR::RequestCriteria::CONTENT_FILTER_NONE
|
116
|
+
'safe'
|
117
|
+
when APR::RequestCriteria::CONTENT_FILTER_STRICT
|
118
|
+
'safe'
|
119
|
+
else
|
120
|
+
'safe'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_results(request_criteria, results)
|
125
|
+
@driver_response = DriverResponse.new(
|
126
|
+
:criteria => request_criteria,
|
127
|
+
:next_criteria => next_criteria(request_criteria, results),
|
128
|
+
:total_items => results['videos']['total'].to_i,
|
129
|
+
:items => results['videos']['video'].map { |data| parse_video(data) }
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def next_criteria(request_criteria, results)
|
134
|
+
page = results['videos']['page'].to_i
|
135
|
+
per_page = results['videos']['perpage'].to_i
|
136
|
+
total = results['videos']['total'].to_i
|
137
|
+
if ((page * per_page) < total)
|
138
|
+
return RequestCriteria.new({
|
139
|
+
:query => request_criteria.query,
|
140
|
+
:page => page + 1,
|
141
|
+
:per_page => per_page
|
142
|
+
})
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_video(data)
|
147
|
+
video = ActivePublicResources::ResponseTypes::Video.new
|
148
|
+
video.id = data['id']
|
149
|
+
video.title = data['title']
|
150
|
+
video.description = data['description']
|
151
|
+
video.thumbnail_url = data['thumbnails']['thumbnail'][0]['_content']
|
152
|
+
video.url = data['urls']['url'][0]['_content']
|
153
|
+
video.duration = data['duration'].to_i
|
154
|
+
video.num_views = data['number_of_plays'].to_i
|
155
|
+
video.num_likes = data['number_of_likes'].to_i
|
156
|
+
video.num_comments = data['number_of_comments'].to_i
|
157
|
+
video.created_date = Date.parse(data['upload_date'])
|
158
|
+
video.username = data['owner']['display_name']
|
159
|
+
video.width = 640
|
160
|
+
video.height = 360
|
161
|
+
|
162
|
+
# Return Types
|
163
|
+
video.return_types << APR::ReturnTypes::Url.new(
|
164
|
+
:url => video.url,
|
165
|
+
:text => video.title,
|
166
|
+
:title => video.title
|
167
|
+
)
|
168
|
+
video.return_types << APR::ReturnTypes::Iframe.new(
|
169
|
+
:url => "//player.vimeo.com/video/#{data['id']}",
|
170
|
+
:text => video.title,
|
171
|
+
:title => video.title,
|
172
|
+
:width => 640,
|
173
|
+
:height => 360
|
174
|
+
)
|
175
|
+
|
176
|
+
video
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module ActivePublicResources
|
4
|
+
module Drivers
|
5
|
+
class Youtube < Driver
|
6
|
+
|
7
|
+
def perform_request(request_criteria)
|
8
|
+
request_criteria.validate_presence!([:query])
|
9
|
+
|
10
|
+
uri = URI('https://gdata.youtube.com/feeds/api/videos')
|
11
|
+
params = {
|
12
|
+
'v' => 2,
|
13
|
+
'q' => request_criteria.query,
|
14
|
+
'orderby' => sort(request_criteria.sort),
|
15
|
+
'alt' => 'json',
|
16
|
+
'safeSearch' => content_filter(request_criteria.content_filter),
|
17
|
+
'start-index' => request_criteria.page || 1,
|
18
|
+
'max-results' => request_criteria.per_page || 25
|
19
|
+
}
|
20
|
+
|
21
|
+
uri.query = URI.encode_www_form(params)
|
22
|
+
|
23
|
+
res = Net::HTTP.get_response(uri)
|
24
|
+
results = JSON.parse(res.body)
|
25
|
+
|
26
|
+
return parse_results(request_criteria, results)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def sort(val)
|
32
|
+
case val
|
33
|
+
when APR::RequestCriteria::SORT_RELEVANCE
|
34
|
+
'relevance'
|
35
|
+
when APR::RequestCriteria::SORT_RECENT
|
36
|
+
'relevance'
|
37
|
+
when APR::RequestCriteria::SORT_POPULAR
|
38
|
+
'relevance'
|
39
|
+
else
|
40
|
+
'relevance'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def content_filter(val)
|
45
|
+
case val
|
46
|
+
when APR::RequestCriteria::CONTENT_FILTER_NONE
|
47
|
+
'strict'
|
48
|
+
when APR::RequestCriteria::CONTENT_FILTER_STRICT
|
49
|
+
'strict'
|
50
|
+
else
|
51
|
+
'strict'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_results(request_criteria, results)
|
56
|
+
@driver_response = DriverResponse.new(
|
57
|
+
:criteria => request_criteria,
|
58
|
+
:next_criteria => next_criteria(request_criteria, results),
|
59
|
+
:total_items => results['feed']['openSearch$totalResults']['$t'].to_i,
|
60
|
+
:items => results['feed']['entry'].map { |data| parse_video(data) }
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def next_criteria(request_criteria, results)
|
65
|
+
page = results['feed']['openSearch$startIndex']['$t'].to_i
|
66
|
+
per_page = results['feed']['openSearch$itemsPerPage']['$t'].to_i
|
67
|
+
total = results['feed']['openSearch$totalResults']['$t'].to_i
|
68
|
+
if ((page * per_page) < total)
|
69
|
+
return RequestCriteria.new({
|
70
|
+
:query => request_criteria.query,
|
71
|
+
:page => page + 1,
|
72
|
+
:per_page => per_page
|
73
|
+
})
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_video(data)
|
78
|
+
video_id = data['id']['$t'].split(':').last
|
79
|
+
video = APR::ResponseTypes::Video.new
|
80
|
+
video.id = video_id
|
81
|
+
video.title = data['title']['$t']
|
82
|
+
video.description = data['media$group']['media$description']['$t']
|
83
|
+
video.thumbnail_url = data['media$group']['media$thumbnail'][0]['url']
|
84
|
+
video.url = data['media$group']['media$player']['url']
|
85
|
+
video.duration = data['media$group']['yt$duration']['seconds'].to_i
|
86
|
+
video.num_views = data['yt$statistics'] ? data['yt$statistics']['viewCount'].to_i : 0
|
87
|
+
video.num_likes = data['yt$rating'] ? data['yt$rating']['numLikes'].to_i : 0
|
88
|
+
video.num_comments = data['gd$comments'] ? data['gd$comments']['gd$feedLink']['countHint'] : 0
|
89
|
+
video.created_date = Date.parse(data['published']['$t'])
|
90
|
+
video.username = data['author'][0]['name']['$t']
|
91
|
+
video.width = 640
|
92
|
+
video.height = 360
|
93
|
+
|
94
|
+
# Return Types
|
95
|
+
video.return_types << APR::ReturnTypes::Url.new(
|
96
|
+
:url => video.url,
|
97
|
+
:text => video.title,
|
98
|
+
:title => video.title
|
99
|
+
)
|
100
|
+
video.return_types << APR::ReturnTypes::Iframe.new(
|
101
|
+
:url => "//www.youtube.com/embed/#{video_id}?feature=oembed",
|
102
|
+
:text => video.title,
|
103
|
+
:title => video.title,
|
104
|
+
:width => 640,
|
105
|
+
:height => 360
|
106
|
+
)
|
107
|
+
|
108
|
+
video
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module ActivePublicResources
|
2
|
+
class RequestCriteria
|
3
|
+
|
4
|
+
SORTS = [
|
5
|
+
SORT_RELEVANCE = 'relevance',
|
6
|
+
SORT_RECENT = 'recent',
|
7
|
+
SORT_POPULAR = 'popular'
|
8
|
+
]
|
9
|
+
|
10
|
+
CONTENT_FILTERS = [
|
11
|
+
CONTENT_FILTER_NONE = 'none',
|
12
|
+
CONTENT_FILTER_STRICT = 'strict'
|
13
|
+
]
|
14
|
+
|
15
|
+
attr_accessor :query, :page, :per_page, :content_filter, :sort, :folder
|
16
|
+
|
17
|
+
def initialize(args={})
|
18
|
+
args.each do |k,v|
|
19
|
+
if k.to_s == 'sort' && !SORTS.include?(v)
|
20
|
+
raise ArgumentError.new("sort is invalid. Must be in [#{SORTS.join(', ')}]")
|
21
|
+
end
|
22
|
+
if k.to_s == 'content_filter' && !CONTENT_FILTERS.include?(v)
|
23
|
+
raise ArgumentError.new("content_filter is invalid. Must be in [#{CONTENT_FILTERS.join(', ')}]")
|
24
|
+
end
|
25
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sort=(val)
|
30
|
+
unless SORTS.include?(val)
|
31
|
+
raise ArgumentError.new("is invalid. Must be in [#{SORTS.join(', ')}]")
|
32
|
+
end
|
33
|
+
@sort = val
|
34
|
+
end
|
35
|
+
|
36
|
+
def content_filter=(val)
|
37
|
+
unless CONTENT_FILTERS.include?(val)
|
38
|
+
raise ArgumentError.new("is invalid. Must be in [#{CONTENT_FILTERS.join(', ')}]")
|
39
|
+
end
|
40
|
+
@content_filter = val
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate_presence!(attr_names)
|
44
|
+
attr_names.each do |k|
|
45
|
+
if instance_variable_get("@#{k}").blank?
|
46
|
+
raise ArgumentError.new("must include #{attr_names.join(', ')}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|