duck-duck-go 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/duck_duck_go.rb +50 -0
- data/lib/duck_duck_go/icon.rb +26 -0
- data/lib/duck_duck_go/link.rb +29 -0
- data/lib/duck_duck_go/zero_click_info.rb +82 -0
- data/scripts/search_dump.rb +23 -0
- data/test/tc_icon.rb +23 -0
- data/test/tc_link.rb +46 -0
- data/test/tc_live.rb +45 -0
- data/test/tc_zero_click_info.rb +825 -0
- metadata +110 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Andrew Jones, http://andrew-jones.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
This is a Ruby library to access the DuckDuckGo Zero Click Info API.
|
2
|
+
|
3
|
+
= How to install
|
4
|
+
|
5
|
+
gem install duck-duck-go
|
6
|
+
|
7
|
+
= Synopsis
|
8
|
+
|
9
|
+
require "duck_duck_go"
|
10
|
+
|
11
|
+
ddg = DuckDuckGo.new
|
12
|
+
zci = ddg.zeroclickinfo("Stephen Fry") # ZeroClickInfo object
|
13
|
+
|
14
|
+
zci.heading # Stephen Fry
|
15
|
+
zci.abstract_text # Stephen John Fry is an English actor, screenwriter, author, playwright, ...
|
16
|
+
zci.related_topics["_"][0].text # Stephen Fry (cricketer) ...
|
17
|
+
|
18
|
+
= Description
|
19
|
+
|
20
|
+
This library accesses the DuckDuckGo Zero Click Info API[http://duckduckgo.com/api.html], and returns an DuckDuckGo::ZeroClickInfo object containing the result.
|
21
|
+
|
22
|
+
Depending on the result type, the +related_topics+ hash will contain slightly different results.
|
23
|
+
|
24
|
+
For most queries, <code>related_topics["_"]</code> contains an array of the related topics.
|
25
|
+
|
26
|
+
For a disambiguation query, <code>related_topics["_"]</code> contains the related topics, where as <code>related_topics["Topic"]</code> contains the disambiguation results. For example, searching for _apple_, you will find a <code>related_topics["Compaines"]</code> entry containing <i>Apple Inc</i> and <i>Apple Corp</i>, amongst others.
|
27
|
+
|
28
|
+
= Issues
|
29
|
+
|
30
|
+
http://github.com/andrewrjones/ruby-duck-duck-go/issues
|
31
|
+
|
32
|
+
= Source code
|
33
|
+
|
34
|
+
http://github.com/andrewrjones/ruby-duck-duck-go
|
35
|
+
|
36
|
+
= Credits
|
37
|
+
|
38
|
+
API and design heavily influenced by WWW::DuckDuckGo[http://search.cpan.org/~getty/WWW-DuckDuckGo-0.006/lib/WWW/DuckDuckGo/ZeroClickInfo.pm], the equivalent Perl module.
|
39
|
+
|
40
|
+
= Copyright and License
|
41
|
+
|
42
|
+
Copyright (c) 2011 Andrew Jones (http://andrew-jones.com)
|
43
|
+
|
44
|
+
This code is available under the MIT License. See the LICENSE file for more details.
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
task :default => [:test]
|
4
|
+
|
5
|
+
require 'rake/testtask'
|
6
|
+
desc "Run unit tests"
|
7
|
+
Rake::TestTask.new("test") { |t|
|
8
|
+
t.pattern = 'test/tc_*.rb'
|
9
|
+
t.verbose = true
|
10
|
+
}
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
gem.name = "duck-duck-go"
|
16
|
+
gem.summary = %Q{Access the DuckDuckGo Zero Click Info API}
|
17
|
+
gem.description = %Q{A Ruby library to access the DuckDuckGo Zero Click Info API.}
|
18
|
+
gem.email = "andrwe@arjones.co.uk"
|
19
|
+
gem.homepage = "https://github.com/andrewrjones/ruby-duck-duck-go"
|
20
|
+
gem.authors = ["andrewrjones"]
|
21
|
+
gem.add_dependency('httpclient')
|
22
|
+
gem.add_dependency('json')
|
23
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
24
|
+
end
|
25
|
+
Jeweler::GemcutterTasks.new
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |rcov|
|
33
|
+
rcov.libs << 'test'
|
34
|
+
rcov.pattern = 'test/tc_*.rb'
|
35
|
+
rcov.verbose = true
|
36
|
+
rcov.rcov_opts << '--exclude gems'
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
task :rcov do
|
40
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install rcov"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "duck-duck-go #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('LICENSE')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/duck_duck_go.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Author: Andrew Jones <http://andrew-jones.com>
|
2
|
+
# The license of this source is MIT Licence
|
3
|
+
|
4
|
+
module DuckDuckGo
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'uri'
|
8
|
+
require 'httpclient'
|
9
|
+
require 'json'
|
10
|
+
require 'duck_duck_go/zero_click_info'
|
11
|
+
|
12
|
+
# see DuckDuckGo::Main.new
|
13
|
+
def self.new(*params)
|
14
|
+
DuckDuckGo::Main.new(*params)
|
15
|
+
end
|
16
|
+
|
17
|
+
class Main
|
18
|
+
|
19
|
+
API_URL = 'http://api.duckduckgo.com/'
|
20
|
+
API_URL_SECURE = 'https://api.duckduckgo.com/'
|
21
|
+
|
22
|
+
# Create a new instance.
|
23
|
+
# It is recommended to pass in a meaningful user agent.
|
24
|
+
# Defaults to using the secure api (https)
|
25
|
+
def initialize(http_agent_name = 'DuckDuckGo.rb', secure = true)
|
26
|
+
if secure
|
27
|
+
@url = API_URL_SECURE
|
28
|
+
else
|
29
|
+
@url = API_URL
|
30
|
+
end
|
31
|
+
@http = HTTPClient.new(:agent_name => http_agent_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Run a query against Duck Duck Go
|
35
|
+
# Returns an instance of DuckDuckGo::ZeroClickInfo
|
36
|
+
def zeroclickinfo(query)
|
37
|
+
data = @http.get_content(@url, { 'q' => query, 'o' => 'json' })
|
38
|
+
|
39
|
+
# parse the JSON and return an instance
|
40
|
+
# of the ZeroClickInfo class
|
41
|
+
DuckDuckGo::ZeroClickInfo.by(JSON.parse(data))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Alias for DuckDuckGo::Main.zeroclickinfo
|
45
|
+
def zci(*params)
|
46
|
+
self.zeroclickinfo(*params)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DuckDuckGo
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
class DuckDuckGo::Icon
|
6
|
+
|
7
|
+
attr_reader :url, :width, :height
|
8
|
+
|
9
|
+
# Representes a DDG icon
|
10
|
+
def initialize(url = nil, width = nil, height = nil)
|
11
|
+
@url = url
|
12
|
+
@width = width
|
13
|
+
@height = height
|
14
|
+
end
|
15
|
+
|
16
|
+
# Populate an DuckDuckGo::Icon from a result
|
17
|
+
def self.by(icon_result)
|
18
|
+
url = URI.parse(icon_result['URL']) unless icon_result['URL'].empty?
|
19
|
+
width = icon_result['Width'] if icon_result['Width']
|
20
|
+
height = icon_result['Height'] if icon_result['Height']
|
21
|
+
|
22
|
+
self.new(url, width, height)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module DuckDuckGo
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'duck_duck_go/icon'
|
5
|
+
|
6
|
+
class DuckDuckGo::Link
|
7
|
+
|
8
|
+
attr_reader :result, :first_url, :icon, :text
|
9
|
+
|
10
|
+
# Representes a DDG link
|
11
|
+
def initialize(result = nil, first_url = nil, icon = nil, text = nil)
|
12
|
+
@result = result
|
13
|
+
@first_url = first_url
|
14
|
+
@icon = icon
|
15
|
+
@text = text
|
16
|
+
end
|
17
|
+
|
18
|
+
# Populate a DuckDuckGo::Link from a result
|
19
|
+
def self.by(link_result)
|
20
|
+
result = link_result['Result'] unless link_result['Result'].empty?
|
21
|
+
first_url = URI.parse(link_result['FirstURL']) unless link_result['FirstURL'].empty?
|
22
|
+
icon = DuckDuckGo::Icon.by(link_result['Icon']) unless link_result['Icon'].empty?
|
23
|
+
text = link_result['Text'] unless link_result['Text'].empty?
|
24
|
+
|
25
|
+
self.new(result, first_url, icon, text)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module DuckDuckGo
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'duck_duck_go/link'
|
5
|
+
require 'duck_duck_go/icon'
|
6
|
+
|
7
|
+
class DuckDuckGo::ZeroClickInfo
|
8
|
+
|
9
|
+
# Definitions for the result type
|
10
|
+
TYPE_DEFINITIONS = {
|
11
|
+
'A' => 'article',
|
12
|
+
'D' => 'disambiguation',
|
13
|
+
'C' => 'category',
|
14
|
+
'N' => 'name',
|
15
|
+
'E' => 'exclusive',
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_reader :abstract, :abstract_text, :abstract_source, :abstract_url, :image, :heading, :answer, :answer_type, :definition, :definition_source, :definition_url, :type, :type_long, :results, :related_topics
|
19
|
+
|
20
|
+
# Representes a DDG Zero Click Info result
|
21
|
+
def initialize(abstract = nil, abstract_text = nil, abstract_source = nil, abstract_url = nil, image = nil, heading = nil, answer = nil, answer_type = nil, definition = nil, definition_source = nil, definition_url = nil, type = nil, results = nil, related_topics = nil)
|
22
|
+
@abstract = abstract
|
23
|
+
@abstract_text = abstract_text
|
24
|
+
@abstract_source = abstract_source
|
25
|
+
@abstract_url = abstract_url
|
26
|
+
@image = image
|
27
|
+
@heading = heading
|
28
|
+
@answer = answer
|
29
|
+
@answer_type = answer_type
|
30
|
+
@definition = definition
|
31
|
+
@definition_source = definition_source
|
32
|
+
@definition_url = definition_url
|
33
|
+
@type = type unless type.nil?
|
34
|
+
@type_long = TYPE_DEFINITIONS[type]
|
35
|
+
@results = results
|
36
|
+
@related_topics = related_topics
|
37
|
+
end
|
38
|
+
|
39
|
+
# Populate the DuckDuckGo::ZeroClickInfo from a result
|
40
|
+
def self.by(result)
|
41
|
+
|
42
|
+
abstract = result['Abstract'] unless result['Abstract'].empty?
|
43
|
+
abstract_text = result['AbstractText'] unless result['AbstractText'].empty?
|
44
|
+
abstract_source = result['AbstractSource'] unless result['AbstractSource'].empty?
|
45
|
+
abstract_url = URI.parse(result['AbstractURL']) unless result['AbstractURL'].empty?
|
46
|
+
image = URI.parse(result['Image']) unless result['Image'].empty?
|
47
|
+
heading = result['Heading'] unless result['Heading'].empty?
|
48
|
+
answer = result['Answer'] unless result['Answer'].empty?
|
49
|
+
answer_type = result['AnswerType'] unless result['AnswerType'].empty?
|
50
|
+
definition = result['Definition'] unless result['Definition'].empty?
|
51
|
+
definition_source = result['DefinitionSource'] unless result['DefinitionSource'].empty?
|
52
|
+
definition_url = URI.parse(result['DefinitionURL']) unless result['DefinitionURL'].empty?
|
53
|
+
type = result['Type'] unless result['Type'].empty?
|
54
|
+
|
55
|
+
if result['Results']
|
56
|
+
results = Array.new
|
57
|
+
result['Results'].each do |link_result|
|
58
|
+
results << DuckDuckGo::Link.by(link_result)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if result['RelatedTopics']
|
63
|
+
related_topics = Hash.new
|
64
|
+
result['RelatedTopics'].each do |t|
|
65
|
+
if t['Topics']
|
66
|
+
topics = Array.new
|
67
|
+
t['Topics'].each do |link_result|
|
68
|
+
topics << DuckDuckGo::Link.by(link_result)
|
69
|
+
end
|
70
|
+
related_topics[t['Name']] = topics
|
71
|
+
else
|
72
|
+
related_topics['_'] = Array.new if related_topics['_'].nil?
|
73
|
+
related_topics['_'] << DuckDuckGo::Link.by(t)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
self.new(abstract, abstract_text, abstract_source, abstract_url, image, heading, answer, answer_type, definition, definition_source, definition_url, type, results, related_topics)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# dumps out the result of the query, in JSON and Ruby
|
2
|
+
# used during development
|
3
|
+
#
|
4
|
+
# usage: ruby scripts/search_dump.rb foo
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'httpclient'
|
8
|
+
require 'json'
|
9
|
+
require 'pp'
|
10
|
+
|
11
|
+
http = HTTPClient.new
|
12
|
+
query = { 'q' => ARGV[0], 'o' => 'json' }
|
13
|
+
|
14
|
+
json = http.get_content('http://api.duckduckgo.com/', query)
|
15
|
+
ruby = JSON.parse(json)
|
16
|
+
|
17
|
+
puts 'JSON'
|
18
|
+
puts '----'
|
19
|
+
puts json
|
20
|
+
puts
|
21
|
+
puts 'Ruby'
|
22
|
+
puts '----'
|
23
|
+
pp ruby
|
data/test/tc_icon.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'duck_duck_go/icon'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
class TestIcon < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def test_icon
|
10
|
+
data = {
|
11
|
+
"URL" => "https://i.duckduckgo.com/i/foo.com.ico",
|
12
|
+
"Height" => 16,
|
13
|
+
"Width" => 16
|
14
|
+
}
|
15
|
+
|
16
|
+
icon = DuckDuckGo::Icon.by(data)
|
17
|
+
assert_instance_of(DuckDuckGo::Icon, icon)
|
18
|
+
assert_instance_of(URI::HTTPS, icon.url)
|
19
|
+
assert_equal("i.duckduckgo.com", icon.url.host)
|
20
|
+
assert_equal(16, icon.width)
|
21
|
+
assert_equal(16, icon.height)
|
22
|
+
end
|
23
|
+
end
|
data/test/tc_link.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'duck_duck_go/link'
|
5
|
+
require 'duck_duck_go/icon'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
class TestLink < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def test_link
|
11
|
+
data = {
|
12
|
+
"Result" => "<a href=\"http://www.foo.com\"><b>Official site</b></a><a href=\"http://www.foo.com\"></a>",
|
13
|
+
"Icon" => {
|
14
|
+
"URL" => "https://i.duckduckgo.com/i/foo.com.ico",
|
15
|
+
"Height" => 16,
|
16
|
+
"Width" => 16
|
17
|
+
},
|
18
|
+
"FirstURL" => "http://www.foo.com",
|
19
|
+
"Text" => "Official site"
|
20
|
+
}
|
21
|
+
|
22
|
+
link = DuckDuckGo::Link.by(data)
|
23
|
+
assert_instance_of(DuckDuckGo::Link, link)
|
24
|
+
assert_instance_of(DuckDuckGo::Icon, link.icon)
|
25
|
+
assert_instance_of(URI::HTTP, link.first_url)
|
26
|
+
assert_equal('www.foo.com', link.first_url.host)
|
27
|
+
assert_equal('Official site', link.text)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_link_missing_icon
|
31
|
+
data = {
|
32
|
+
"Result" => "<a href=\"http://www.foo.com\"><b>Official site</b></a><a href=\"http://www.foo.com\"></a>",
|
33
|
+
"Icon" => {
|
34
|
+
},
|
35
|
+
"FirstURL" => "http://www.foo.com",
|
36
|
+
"Text" => "Official site"
|
37
|
+
}
|
38
|
+
|
39
|
+
link = DuckDuckGo::Link.by(data)
|
40
|
+
assert_instance_of(DuckDuckGo::Link, link)
|
41
|
+
assert_nil(link.icon)
|
42
|
+
assert_instance_of(URI::HTTP, link.first_url)
|
43
|
+
assert_equal('www.foo.com', link.first_url.host)
|
44
|
+
assert_equal('Official site', link.text)
|
45
|
+
end
|
46
|
+
end
|
data/test/tc_live.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'duck_duck_go'
|
5
|
+
require 'duck_duck_go/zero_click_info'
|
6
|
+
|
7
|
+
class TestLive < Test::Unit::TestCase
|
8
|
+
|
9
|
+
# test a live search for 'stephen fry'
|
10
|
+
def test_live
|
11
|
+
|
12
|
+
ddg = DuckDuckGo.new(nil, false)
|
13
|
+
zci = ddg.zci('stephen fry')
|
14
|
+
|
15
|
+
assert_instance_of(DuckDuckGo::ZeroClickInfo, zci)
|
16
|
+
assert_equal("Stephen Fry", zci.heading)
|
17
|
+
assert_equal("Wikipedia", zci.abstract_source)
|
18
|
+
assert_equal("A", zci.type)
|
19
|
+
end
|
20
|
+
|
21
|
+
# test using http
|
22
|
+
def test_live_http
|
23
|
+
|
24
|
+
ddg = DuckDuckGo.new(nil, true)
|
25
|
+
zci = ddg.zci('stephen fry')
|
26
|
+
|
27
|
+
assert_instance_of(DuckDuckGo::ZeroClickInfo, zci)
|
28
|
+
assert_equal("Stephen Fry", zci.heading)
|
29
|
+
assert_equal("Wikipedia", zci.abstract_source)
|
30
|
+
assert_equal("A", zci.type)
|
31
|
+
end
|
32
|
+
|
33
|
+
# questions do not currently get a response on the api
|
34
|
+
def test_answer
|
35
|
+
ddg = DuckDuckGo.new
|
36
|
+
zci = ddg.zci('age of obama')
|
37
|
+
|
38
|
+
assert_nil(zci.abstract)
|
39
|
+
assert_nil(zci.answer)
|
40
|
+
assert_nil(zci.definition)
|
41
|
+
assert_nil(zci.heading)
|
42
|
+
assert_equal(0, zci.related_topics.size)
|
43
|
+
assert_equal(0, zci.results.size)
|
44
|
+
end
|
45
|
+
end
|