active_public_resources 0.0.2
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.
- 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
|