199 lines
5.3 KiB
Python
Executable File
199 lines
5.3 KiB
Python
Executable File
import requests
|
|
import urllib
|
|
import json as j
|
|
import sys
|
|
|
|
__version__ = 0.242
|
|
|
|
|
|
def query(
|
|
query,
|
|
useragent="python-duckduckgo " + str(__version__),
|
|
safesearch=True,
|
|
html=False,
|
|
meanings=True,
|
|
**kwargs
|
|
):
|
|
"""
|
|
Query DuckDuckGo, returning a Results object.
|
|
|
|
Here's a query that's unlikely to change:
|
|
|
|
>>> result = query('1 + 1')
|
|
>>> result.type
|
|
'nothing'
|
|
>>> result.answer.text
|
|
'1 + 1 = 2'
|
|
>>> result.answer.type
|
|
'calc'
|
|
|
|
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)
|
|
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 = "0" if meanings else "1"
|
|
params = {
|
|
"q": query,
|
|
"o": "json",
|
|
"kp": safesearch,
|
|
"no_redirect": "1",
|
|
"no_html": html,
|
|
"d": meanings,
|
|
}
|
|
params.update(kwargs)
|
|
encparams = urllib.parse.urlencode(params)
|
|
url = "http://api.duckduckgo.com/?" + encparams
|
|
|
|
request = requests.get(url, headers={"User-Agent": useragent})
|
|
|
|
return Results(request.json())
|
|
|
|
|
|
class Results(object):
|
|
def __init__(self, json):
|
|
self.type = {
|
|
"A": "answer",
|
|
"D": "disambiguation",
|
|
"C": "category",
|
|
"N": "name",
|
|
"E": "exclusive",
|
|
"": "nothing",
|
|
}.get(json.get("Type", ""), "")
|
|
|
|
self.json = json
|
|
self.api_version = None # compat
|
|
|
|
self.heading = json.get("Heading", "")
|
|
|
|
self.results = [Result(elem) for elem in json.get("Results", [])]
|
|
self.related = [Result(elem) for elem in json.get("RelatedTopics", [])]
|
|
|
|
self.abstract = Abstract(json)
|
|
self.redirect = Redirect(json)
|
|
self.definition = Definition(json)
|
|
self.answer = Answer(json)
|
|
|
|
self.image = Image({"Result": json.get("Image", "")})
|
|
|
|
|
|
class Abstract(object):
|
|
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, 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")
|
|
|
|
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, json):
|
|
self.url = json.get("Result")
|
|
self.height = json.get("Height", None)
|
|
self.width = json.get("Width", None)
|
|
|
|
|
|
class Answer(object):
|
|
def __init__(self, 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")
|
|
|
|
|
|
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.
|
|
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:
|
|
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
|
|
|
|
if result.text:
|
|
response = result.text
|
|
if result.text and hasattr(result, "url") and urls:
|
|
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:]))
|
|
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])
|