From f4c6e55ed10bcf1a561c6c5f2a3cc7a89fc634cf Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 7 Jul 2012 18:57:40 -0600 Subject: [PATCH 01/18] move to json, add options --- duckduckgo.py | 172 +++++++++++++++++++------------------------------- 1 file changed, 65 insertions(+), 107 deletions(-) diff --git a/duckduckgo.py b/duckduckgo.py index 7ff2209..3f937b6 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -1,14 +1,13 @@ -#!/usr/bin/env python import urllib import urllib2 -from xml.etree import ElementTree +import json as j -__version__ = 0.1 +__version__ = 0.2 -def query(query, useragent='python-duckduckgo 0.1'): +def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, **kwargs): """ - Query Duck Duck Go, returning a Results object. + Query DuckDuckGo, returning a Results object. Here's a query that's unlikely to change: @@ -19,146 +18,105 @@ def query(query, useragent='python-duckduckgo 0.1'): '1 + 1 = 2' >>> result.answer.type 'calc' - """ - params = urllib.urlencode({'q': query, 'o': 'x'}) - url = 'http://duckduckgo.com/?' + params + + Keword arguments: + useragent: UserAgent to use while querying. Default: "python-duckduckgo %d" (str) + safesearch: True for on, False for off. Default: True (bool) + html: True to allow HTML in output. Default: False (bool) + Any other keyword arguments are passed directly to DuckDuckGo as URL params. + """ % __version__ + + safesearch = '1' if safesearch else '-1' + html = '0' if html else '1' + params = { + 'q': query, + 'o': 'json', + 'kp': safesearch, + 'no_redirect': '1', + 'no_html': html, + } + params.update(kwargs) + encparams = urllib.urlencode(params) + url = 'http://duckduckgo.com/?' + encparams request = urllib2.Request(url, headers={'User-Agent': useragent}) response = urllib2.urlopen(request) - xml = ElementTree.fromstring(response.read()) + json = j.loads(response.read()) response.close() - return Results(xml) + return Results(json) class Results(object): - def __init__(self, xml): + def __init__(self, json): self.type = {'A': 'answer', 'D': 'disambiguation', 'C': 'category', 'N': 'name', - 'E': 'exclusive', '': 'nothing'}[xml.findtext('Type', '')] + 'E': 'exclusive', '': 'nothing'}[json.get('Type','')] - self.api_version = xml.attrib.get('version', None) + self.api_version = None # compat - self.heading = xml.findtext('Heading', '') + self.heading = json.get('Heading', '') - self.results = [Result(elem) for elem in xml.getiterator('Result')] + self.results = [Result(elem) for elem in json.get('Results',[])] self.related = [Result(elem) for elem in - xml.getiterator('RelatedTopic')] + json.get('RelatedTopics',[])] - self.abstract = Abstract(xml) + self.abstract = Abstract(json) + self.redirect = Redirect(json) - answer_xml = xml.find('Answer') - if answer_xml is not None: - self.answer = Answer(answer_xml) + answer_json = {} + for key in json.keys(): + if key.lower().startswith('answer'): + answer_json.update({key: json[key]}) + if answer_json is not {}: + self.answer = Answer(answer_json) if not self.answer.text: self.answer = None else: self.answer = None - image_xml = xml.find('Image') - if image_xml is not None and image_xml.text: - self.image = Image(image_xml) - else: - self.image = None + self.image = Image({'Result':json.get('Image','')}) class Abstract(object): - def __init__(self, xml): - self.html = xml.findtext('Abstract', '') - self.text = xml.findtext('AbstractText', '') - self.url = xml.findtext('AbstractURL', '') - self.source = xml.findtext('AbstractSource') + def __init__(self, json): + self.html = json.get('Abstract', '') + self.text = json.get('AbstractText', '') + self.url = json.get('AbstractURL', '') + self.source = json.get('AbstractSource') + +class Redirect(object): + def __init__(self, json): + self.url = json.get('Redirect', '') class Result(object): - def __init__(self, xml): - self.html = xml.text - self.text = xml.findtext('Text') - self.url = xml.findtext('FirstURL') + def __init__(self, json): + self.html = json.get('Result') + self.text = json.get('Text') + self.url = json.get('FirstURL') - icon_xml = xml.find('Icon') - if icon_xml is not None: - self.icon = Image(icon_xml) + icon_json = json.get('Icon') + if icon_json is not None: + self.icon = Image(icon_json) else: self.icon = None class Image(object): - def __init__(self, xml): - self.url = xml.text - self.height = xml.attrib.get('height', None) - self.width = xml.attrib.get('width', None) + def __init__(self, json): + self.url = json.get('Result') + self.height = json.get('Height', None) + self.width = json.get('Width', None) class Answer(object): - def __init__(self, xml): - self.text = xml.text - self.type = xml.attrib.get('type', '') - - -def main(): - import sys - from optparse import OptionParser - - parser = OptionParser(usage="usage: %prog [options] query", - version="ddg %s" % __version__) - parser.add_option("-o", "--open", dest="open", action="store_true", - help="open results in a browser") - parser.add_option("-n", dest="n", type="int", default=3, - help="number of results to show") - parser.add_option("-d", dest="d", type="int", default=None, - help="disambiguation choice") - (options, args) = parser.parse_args() - q = ' '.join(args) - - if options.open: - import urllib - import webbrowser - - webbrowser.open("http://duckduckgo.com/?%s" % urllib.urlencode( - dict(q=q)), new=2) - - sys.exit(0) - - results = query(q) - - if options.d and results.type == 'disambiguation': - try: - related = results.related[options.d - 1] - except IndexError: - print "Invalid disambiguation number." - sys.exit(1) - results = query(related.url.split("/")[-1].replace("_", " ")) - - if results.answer and results.answer.text: - print "Answer: %s\n" % results.answer.text - elif results.abstract and results.abstract.text: - print "%s\n" % results.abstract.text - - if results.type == 'disambiguation': - print ("'%s' can mean multiple things. You can re-run your query " - "and add '-d #' where '#' is the topic number you're " - "interested in.\n" % q) - - for i, related in enumerate(results.related[0:options.n]): - name = related.url.split("/")[-1].replace("_", " ") - summary = related.text - if len(summary) < len(related.text): - summary += "..." - print '%d. %s: %s\n' % (i + 1, name, summary) - else: - for i, result in enumerate(results.results[0:options.n]): - summary = result.text[0:70].replace(" ", " ") - if len(summary) < len(result.text): - summary += "..." - print "%d. %s" % (i + 1, summary) - print " <%s>\n" % result.url - - -if __name__ == '__main__': - main() + def __init__(self, json): + print type(json) + self.text = json.get('Answer') + self.type = json.get('AnswerType', '') From a03c1a7d3cdb900c6bff784b54e87fbffbfd0cd6 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 7 Jul 2012 19:05:08 -0600 Subject: [PATCH 02/18] some bugfixes and add Definition --- duckduckgo.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/duckduckgo.py b/duckduckgo.py index 3f937b6..1bcce36 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -64,17 +64,8 @@ def __init__(self, json): self.abstract = Abstract(json) self.redirect = Redirect(json) - - answer_json = {} - for key in json.keys(): - if key.lower().startswith('answer'): - answer_json.update({key: json[key]}) - if answer_json is not {}: - self.answer = Answer(answer_json) - if not self.answer.text: - self.answer = None - else: - self.answer = None + self.definition = Definition(json) + self.answer = Answer(json) self.image = Image({'Result':json.get('Image','')}) @@ -117,6 +108,11 @@ def __init__(self, json): class Answer(object): def __init__(self, json): - print type(json) self.text = json.get('Answer') self.type = json.get('AnswerType', '') + +class Definition(object): + def __init__(self, json): + self.text = json.get('Definition','') + self.url = json.get('DefinitionURL') + self.source = json.get('DefinitionSource') From b63647fc9850f210689f786aa0d29035fbb0abfc Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 7 Jul 2012 19:39:02 -0600 Subject: [PATCH 03/18] fix commandline client --- duckduckgo.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/duckduckgo.py b/duckduckgo.py index 1bcce36..a72655c 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -1,6 +1,7 @@ import urllib import urllib2 import json as j +import sys __version__ = 0.2 @@ -54,6 +55,7 @@ def __init__(self, json): 'C': 'category', 'N': 'name', 'E': 'exclusive', '': 'nothing'}[json.get('Type','')] + self.json = json self.api_version = None # compat self.heading = json.get('Heading', '') @@ -116,3 +118,18 @@ def __init__(self, json): self.text = json.get('Definition','') self.url = json.get('DefinitionURL') self.source = json.get('DefinitionSource') + + +def main(): + if len(sys.argv) > 1: + q = query(' '.join(sys.argv[1:])) + keys = q.json.keys() + keys.sort() + for key in keys: + sys.stdout.write(key) + if type(q.json[key]) in [str,unicode]: print ':', q.json[key] + else: + sys.stdout.write('\n') + for i in q.json[key]: print '\t',i + else: + print 'Usage: %s [query]' % sys.argv[0] From 8b9874e2419072801b69085af858763743836efb Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 17 Jul 2012 14:10:39 -0600 Subject: [PATCH 04/18] update readme --- README.rst | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 690c1ac..246fc01 100644 --- a/README.rst +++ b/README.rst @@ -2,11 +2,14 @@ python-duckduckgo ================== -A Python library for querying the Duck Duck Go API. +A Python library for querying the DuckDuckGo API. Copyright Michael Stephens , released under a BSD-style license. -Source: http://github.com/mikejs/python-duckduckgo +Source: http://github.com/crazedpsyc/python-duckduckgo +Original source: http://github.com/mikejs/python-duckduckgo (outdated) + +This version has been forked from the original to handle some new features of the API, and switch from XML to JSON. Installation ============ @@ -19,7 +22,7 @@ Usage ===== >>> import duckduckgo - >>> r = duckduckgo.query('Duck Duck Go') + >>> r = duckduckgo.query('DuckDuckGo') >>> r.type 'answer' >>> r.results[0].text @@ -46,3 +49,15 @@ Usage '1 + 1 = 2' >>> r.answer.type 'calc' + + # query() takes some special arguments, and passes the rest directly to the API. + >>> print duckduckgo.query('19301', kad='es_ES').answer.text + 19301 es un código postal de Paoli, PA + >>> print duckduckgo.query('how to spell test', html=True).answer.text + Test appears to be spelled right!
Suggestions: test, testy, teat, tests, rest, yest. + + # Special keyword args: + # useragent - string, The useragent used to make API calls. This is somewhat irrelevant, as they are not logged or used on DuckDuckGo, but it is retained for backwards compatibility. + # safesearch - boolean, enable or disable safesearch. + # html - boolean, Allow HTML in responses? + From 49b64e33308fc0e9248b24f0f847ab95df2d8e7b Mon Sep 17 00:00:00 2001 From: Michael X Date: Tue, 17 Jul 2012 14:16:16 -0600 Subject: [PATCH 05/18] Update master --- README.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 246fc01..cc33d0d 100644 --- a/README.rst +++ b/README.rst @@ -50,14 +50,13 @@ Usage >>> r.answer.type 'calc' - # query() takes some special arguments, and passes the rest directly to the API. >>> print duckduckgo.query('19301', kad='es_ES').answer.text 19301 es un código postal de Paoli, PA >>> print duckduckgo.query('how to spell test', html=True).answer.text Test appears to be spelled right!
Suggestions: test, testy, teat, tests, rest, yest. - # Special keyword args: - # useragent - string, The useragent used to make API calls. This is somewhat irrelevant, as they are not logged or used on DuckDuckGo, but it is retained for backwards compatibility. - # safesearch - boolean, enable or disable safesearch. - # html - boolean, Allow HTML in responses? +Special keyword args for query(): + - useragent - string, The useragent used to make API calls. This is somewhat irrelevant, as they are not logged or used on DuckDuckGo, but it is retained for backwards compatibility. + - safesearch - boolean, enable or disable safesearch. + - html - boolean, Allow HTML in responses? From cc44734355667bf7c269c3452d4d8a5aa1928697 Mon Sep 17 00:00:00 2001 From: Michael X Date: Tue, 17 Jul 2012 19:25:13 -0600 Subject: [PATCH 06/18] Update readme to reflect RelatedTopics structure --- README.rst | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index cc33d0d..df0b35f 100644 --- a/README.rst +++ b/README.rst @@ -24,31 +24,34 @@ Usage >>> import duckduckgo >>> r = duckduckgo.query('DuckDuckGo') >>> r.type - 'answer' + u'answer' >>> r.results[0].text - 'Official site' + u'Official site' >>> r.results[0].url - 'http://duckduckgo.com/' + u'http://duckduckgo.com/' >>> r.abstract.url - 'http://en.wikipedia.org/wiki/Duck_Duck_Go' + u'http://en.wikipedia.org/wiki/Duck_Duck_Go' >>> r.abstract.source - 'Wikipedia' + u'Wikipedia' >>> r = duckduckgo.query('Python') >>> r.type - 'disambiguation' - >>> r.related[6].text - 'Python (programming language), a computer programming language' - >>> r.related[6].url - 'http://duckduckgo.com/Python_(programming_language)' + u'disambiguation' + >>> r.related[1].text + u'Python (programming language), a computer programming language' + >>> r.related[1].url + u'http://duckduckgo.com/Python_(programming_language)' + >>> r.related[7].topics[0].text # weird, but this is how the DDG API is currently organized + u'Armstrong Siddeley Python, an early turboprop engine' + >>> r = duckduckgo.query('1 + 1') >>> r.type - 'nothing' + u'nothing' >>> r.answer.text - '1 + 1 = 2' + u'1 + 1 = 2' >>> r.answer.type - 'calc' + u'calc' >>> print duckduckgo.query('19301', kad='es_ES').answer.text 19301 es un código postal de Paoli, PA From 0dfadd40cf7c53fdb3d38eacf5cab1cef11b0331 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 17 Jul 2012 19:25:41 -0600 Subject: [PATCH 07/18] fix handling of nested topic lists --- duckduckgo.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/duckduckgo.py b/duckduckgo.py index a72655c..2761216 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -88,6 +88,10 @@ def __init__(self, json): class Result(object): def __init__(self, json): + self.topics = json.get('Topics', []) + if self.topics: + self.topics = [Result(t) for t in self.topics] + return self.html = json.get('Result') self.text = json.get('Text') self.url = json.get('FirstURL') From 3abd21cbb9da661627b9dae97be0eb6f260ff2c5 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 18 Jul 2012 16:08:09 -0600 Subject: [PATCH 08/18] update setup.py --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index e82cc94..31d578a 100644 --- a/setup.py +++ b/setup.py @@ -3,14 +3,14 @@ long_description = open('README.rst').read() -setup(name='duckduckgo', +setup(name='duckduckgo2', version=__version__, py_modules=['duckduckgo'], - description='Library for querying the Duck Duck Go API', - author='Michael Stephens', - author_email='me@mikej.st', + description='Library for querying the DuckDuckGo API', + author='Michael Smith', + author_email='crazedpsyc@duckduckgo.com', license='BSD', - url='http://github.com/mikejs/python-duckduckgo/', + url='http://github.com/crazedpsyc/python-duckduckgo/', long_description=long_description, platforms=['any'], classifiers=["Development Status :: 4 - Beta", From b306aa93b79db0d66585a409f4056057fa780c0d Mon Sep 17 00:00:00 2001 From: mrshu Date: Wed, 15 Aug 2012 19:40:59 +0300 Subject: [PATCH 09/18] Added meanings option This commit adds ability to query just for meanings of queries. Example: http://api.duckduckgo.com/?q=apple&format=json&pretty=1&d=0 vs. http://api.duckduckgo.com/?q=apple&format=json&pretty=1&d=1 --- duckduckgo.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/duckduckgo.py b/duckduckgo.py index 2761216..3ba247a 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -6,7 +6,7 @@ __version__ = 0.2 -def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, **kwargs): +def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, meanings=True, **kwargs): """ Query DuckDuckGo, returning a Results object. @@ -24,17 +24,20 @@ def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=Tru useragent: UserAgent to use while querying. Default: "python-duckduckgo %d" (str) safesearch: True for on, False for off. Default: True (bool) html: True to allow HTML in output. Default: False (bool) + meanings: True to include disambiguations in results (bool) Any other keyword arguments are passed directly to DuckDuckGo as URL params. """ % __version__ safesearch = '1' if safesearch else '-1' html = '0' if html else '1' + meanings = '1' if meanings else '0' params = { 'q': query, 'o': 'json', 'kp': safesearch, 'no_redirect': '1', 'no_html': html, + 'd': meanings, } params.update(kwargs) encparams = urllib.urlencode(params) From f75d7deffbf77c763fe0d1cb2b1a34fe6238af2f Mon Sep 17 00:00:00 2001 From: mrshu Date: Wed, 15 Aug 2012 19:42:51 +0300 Subject: [PATCH 10/18] Wrong number ... --- duckduckgo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/duckduckgo.py b/duckduckgo.py index 3ba247a..9cbb7f2 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -30,7 +30,7 @@ def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=Tru safesearch = '1' if safesearch else '-1' html = '0' if html else '1' - meanings = '1' if meanings else '0' + meanings = '0' if meanings else '1' params = { 'q': query, 'o': 'json', From a7aa4cac581642bfbab49282dd71cb3b25b20236 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 21 Sep 2012 12:57:24 -0600 Subject: [PATCH 11/18] add get_zci function for easily grabbing the best result --- duckduckgo.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/duckduckgo.py b/duckduckgo.py index 9cbb7f2..85df382 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -3,7 +3,7 @@ import json as j import sys -__version__ = 0.2 +__version__ = 0.21 def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, meanings=True, **kwargs): @@ -127,6 +127,42 @@ def __init__(self, json): self.source = json.get('DefinitionSource') +def get_zci(q, web_fallback=True, priority=['answer', 'abstract', 'related.0', 'definition'], **kwargs): + '''A helper method to get a single (and hopefully the best) ZCI result. + priority=list can be used to set the order in which fields will be checked for answers. + Use web_fallback=True to fall back to grabbing the first web result. + passed to query. This method will fall back to 'Sorry, no results.' + if it cannot find anything.''' + + ddg = query('\\'+q, **kwargs) + response = '' + + for p in priority: + ps = p.split('.') + type = ps[0] + index = int(ps[1]) if len(ps) > 1 else None + + result = getattr(ddg, type) + if index is not None: + result = result[index] if len(result) > index else None + if not result: continue + + if result.text: response = result.text + if result.text and hasattr(result,'url'): + if result.url: response += ' (%s)' % result.url + if response: break + + # if there still isn't anything, try to get the first web result + if not response and web_fallback: + if ddg.redirect.url: + response = ddg.redirect.url + + # final fallback + if not response: + response = 'Sorry, no results.' + + return response + def main(): if len(sys.argv) > 1: q = query(' '.join(sys.argv[1:])) From 8cc7b1b4e214ad6826733400a663aaa781a2053c Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Fri, 21 Sep 2012 13:02:33 -0600 Subject: [PATCH 12/18] update docs for get_zci() --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index df0b35f..81e5e37 100644 --- a/README.rst +++ b/README.rst @@ -58,6 +58,12 @@ Usage >>> print duckduckgo.query('how to spell test', html=True).answer.text Test appears to be spelled right!
Suggestions: test, testy, teat, tests, rest, yest. + # The easiest method of quickly grabbing the best (hopefully) API result is to use duckduckgo.get_zci: + >>> print duckduckgo.get_zci('foo') + The terms foobar /ˈfʊːbɑːr/, fubar, or foo, bar, baz and qux are sometimes used as placeholder names in computer programming or computer-related documentation. (https://en.wikipedia.org/wiki/Foobar) + >>> print ddg.get_zci('foo fighters site') + http://www.foofighters.com/us/home + Special keyword args for query(): - useragent - string, The useragent used to make API calls. This is somewhat irrelevant, as they are not logged or used on DuckDuckGo, but it is retained for backwards compatibility. - safesearch - boolean, enable or disable safesearch. From d7646b9055144bbd422648c5fdc139eaeca06625 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 30 Sep 2012 07:55:42 -0600 Subject: [PATCH 13/18] add option to ignore ZCI urls in get_zci --- README.rst | 2 +- duckduckgo.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 81e5e37..99aa8c3 100644 --- a/README.rst +++ b/README.rst @@ -58,7 +58,7 @@ Usage >>> print duckduckgo.query('how to spell test', html=True).answer.text Test appears to be spelled right!
Suggestions: test, testy, teat, tests, rest, yest. - # The easiest method of quickly grabbing the best (hopefully) API result is to use duckduckgo.get_zci: +The easiest method of quickly grabbing the best (hopefully) API result is to use duckduckgo.get_zci:: >>> print duckduckgo.get_zci('foo') The terms foobar /ˈfʊːbɑːr/, fubar, or foo, bar, baz and qux are sometimes used as placeholder names in computer programming or computer-related documentation. (https://en.wikipedia.org/wiki/Foobar) >>> print ddg.get_zci('foo fighters site') diff --git a/duckduckgo.py b/duckduckgo.py index 85df382..63c97da 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -3,7 +3,7 @@ import json as j import sys -__version__ = 0.21 +__version__ = 0.24 def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, meanings=True, **kwargs): @@ -127,7 +127,7 @@ def __init__(self, json): self.source = json.get('DefinitionSource') -def get_zci(q, web_fallback=True, priority=['answer', 'abstract', 'related.0', 'definition'], **kwargs): +def get_zci(q, web_fallback=True, priority=['answer', 'abstract', 'related.0', 'definition'], urls=True, **kwargs): '''A helper method to get a single (and hopefully the best) ZCI result. priority=list can be used to set the order in which fields will be checked for answers. Use web_fallback=True to fall back to grabbing the first web result. @@ -148,7 +148,7 @@ def get_zci(q, web_fallback=True, priority=['answer', 'abstract', 'related.0', ' if not result: continue if result.text: response = result.text - if result.text and hasattr(result,'url'): + if result.text and hasattr(result,'url') and urls: if result.url: response += ' (%s)' % result.url if response: break From 7ab977f74fe0ed2df673db6c51799eff052cceb4 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 3 Apr 2013 19:05:15 -0600 Subject: [PATCH 14/18] use API subdomain --- duckduckgo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/duckduckgo.py b/duckduckgo.py index 63c97da..12dcc77 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -3,7 +3,7 @@ import json as j import sys -__version__ = 0.24 +__version__ = 0.241 def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, meanings=True, **kwargs): @@ -41,7 +41,7 @@ def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=Tru } params.update(kwargs) encparams = urllib.urlencode(params) - url = 'http://duckduckgo.com/?' + encparams + url = 'http://api.duckduckgo.com/?' + encparams request = urllib2.Request(url, headers={'User-Agent': useragent}) response = urllib2.urlopen(request) @@ -144,6 +144,7 @@ def get_zci(q, web_fallback=True, priority=['answer', 'abstract', 'related.0', ' result = getattr(ddg, type) if index is not None: + if not hasattr(result, '__getitem__'): raise TypeError('%s field is not indexable' % type) result = result[index] if len(result) > index else None if not result: continue From 50fca615555db1bacbe947f76da2ed7a1dc4debc Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 2 Jun 2013 18:39:50 -0600 Subject: [PATCH 15/18] Handle invalid/unknown Type field --- duckduckgo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/duckduckgo.py b/duckduckgo.py index 12dcc77..994e85e 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -56,7 +56,7 @@ class Results(object): def __init__(self, json): self.type = {'A': 'answer', 'D': 'disambiguation', 'C': 'category', 'N': 'name', - 'E': 'exclusive', '': 'nothing'}[json.get('Type','')] + 'E': 'exclusive', '': 'nothing'}.get(json.get('Type',''), '') self.json = json self.api_version = None # compat @@ -171,9 +171,9 @@ def main(): keys.sort() for key in keys: sys.stdout.write(key) - if type(q.json[key]) in [str,unicode]: print ':', q.json[key] + if type(q.json[key]) in [str,unicode]: print(':', q.json[key]) else: sys.stdout.write('\n') - for i in q.json[key]: print '\t',i + for i in q.json[key]: print('\t',i) else: - print 'Usage: %s [query]' % sys.argv[0] + print('Usage: %s [query]' % sys.argv[0]) From 74fdec8ba0a7e71a5fad2fccda580bbad17bae48 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 2 Jun 2013 18:41:28 -0600 Subject: [PATCH 16/18] Bump version number --- duckduckgo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/duckduckgo.py b/duckduckgo.py index 994e85e..04ed1a5 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -3,7 +3,7 @@ import json as j import sys -__version__ = 0.241 +__version__ = 0.242 def query(query, useragent='python-duckduckgo '+str(__version__), safesearch=True, html=False, meanings=True, **kwargs): From 99ad104f8b7d8405e0798578c342ee1bc6127135 Mon Sep 17 00:00:00 2001 From: "Iain R. Learmonth" Date: Fri, 9 Oct 2015 21:40:39 +0100 Subject: [PATCH 17/18] Clarify copyright and licensing Many Linux and BSD-like distributions will find it easier to include this library in their packaging systems if the copyright and licensing conditions are clarified. --- LICENSE | 3 ++- README.rst | 10 +++++++--- duckduckgo.py | 7 +++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index f3e2506..cc631e0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,8 @@ BSD-style license ================= -Copyright (c) 2010, Michael Stephens +Copyright (c) 2010 Michael Stephens +Copyright (c) 2012-2013 Michael Smith All rights reserved. diff --git a/README.rst b/README.rst index 99aa8c3..486aaaf 100644 --- a/README.rst +++ b/README.rst @@ -4,12 +4,16 @@ python-duckduckgo A Python library for querying the DuckDuckGo API. -Copyright Michael Stephens , released under a BSD-style license. +Copyright (c) 2010 Michael Stephens +Copyright (c) 2012-2013 Michael Smith -Source: http://github.com/crazedpsyc/python-duckduckgo +Released under a 3-clause BSD license, see LICENSE for details. + +Latest Source: http://github.com/crazedpsyc/python-duckduckgo Original source: http://github.com/mikejs/python-duckduckgo (outdated) -This version has been forked from the original to handle some new features of the API, and switch from XML to JSON. +This version has been forked from the original to handle some new features of +the API, and switch from XML to JSON. Installation ============ diff --git a/duckduckgo.py b/duckduckgo.py index 04ed1a5..2d7f128 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -1,3 +1,10 @@ +# duckduckgo.py - Library for querying the DuckDuckGo API +# +# Copyright (c) 2010 Michael Stephens +# Copyright (c) 2012-2013 Michael Smith +# +# See LICENSE for terms of usage, modification and redistribution. + import urllib import urllib2 import json as j From d7ed1d5778ec1fc44be25361e16753f0777ff999 Mon Sep 17 00:00:00 2001 From: "Iain R. Learmonth" Date: Fri, 9 Oct 2015 21:48:04 +0100 Subject: [PATCH 18/18] Print ints instead of attempting to iterate over them --- duckduckgo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/duckduckgo.py b/duckduckgo.py index 04ed1a5..cd243c4 100755 --- a/duckduckgo.py +++ b/duckduckgo.py @@ -171,7 +171,7 @@ def main(): keys.sort() for key in keys: sys.stdout.write(key) - if type(q.json[key]) in [str,unicode]: print(':', q.json[key]) + if type(q.json[key]) in [str,unicode,int]: print(':', q.json[key]) else: sys.stdout.write('\n') for i in q.json[key]: print('\t',i)