duck-duck-go 1.0.0

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.
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
@@ -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