summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS.rst1
-rw-r--r--searx/__init__.py20
-rw-r--r--searx/autocomplete.py29
-rw-r--r--searx/engines/__init__.py20
-rw-r--r--searx/engines/bing.py46
-rw-r--r--searx/engines/bing_images.py80
-rw-r--r--searx/engines/bing_news.py84
-rw-r--r--searx/engines/currency_convert.py11
-rw-r--r--searx/engines/dailymotion.py61
-rw-r--r--searx/engines/deviantart.py32
-rw-r--r--searx/engines/duckduckgo.py77
-rw-r--r--searx/engines/duckduckgo_definitions.py128
-rw-r--r--searx/engines/dummy.py8
-rw-r--r--searx/engines/generalfile.py31
-rw-r--r--searx/engines/github.py32
-rw-r--r--searx/engines/google.py116
-rw-r--r--searx/engines/google_images.py30
-rw-r--r--searx/engines/google_news.py31
-rw-r--r--searx/engines/mediawiki.py76
-rw-r--r--searx/engines/openstreetmap.py47
-rw-r--r--searx/engines/piratebay.py41
-rw-r--r--searx/engines/soundcloud.py33
-rw-r--r--searx/engines/stackoverflow.py42
-rw-r--r--searx/engines/startpage.py74
-rw-r--r--searx/engines/twitter.py37
-rw-r--r--searx/engines/vimeo.py62
-rw-r--r--searx/engines/wikidata.py238
-rw-r--r--searx/engines/wikipedia.py30
-rw-r--r--searx/engines/yacy.py85
-rw-r--r--searx/engines/yahoo.py53
-rw-r--r--searx/engines/yahoo_news.py42
-rw-r--r--searx/engines/youtube.py45
-rw-r--r--searx/languages.py18
-rw-r--r--searx/query.py127
-rw-r--r--searx/search.py300
-rw-r--r--searx/settings.yml60
-rw-r--r--searx/static/courgette/css/style.css21
-rw-r--r--searx/static/default/css/style.css23
-rw-r--r--searx/static/default/less/style.less139
-rw-r--r--searx/templates/default/infobox.html44
-rw-r--r--searx/templates/default/result_templates/default.html4
-rw-r--r--searx/templates/default/results.html16
-rw-r--r--searx/tests/test_webapp.py10
-rw-r--r--searx/translations/de/LC_MESSAGES/messages.mobin2387 -> 2549 bytes
-rw-r--r--searx/translations/de/LC_MESSAGES/messages.po148
-rw-r--r--searx/translations/en/LC_MESSAGES/messages.mobin2278 -> 2492 bytes
-rw-r--r--searx/translations/en/LC_MESSAGES/messages.po122
-rw-r--r--searx/translations/es/LC_MESSAGES/messages.mobin2458 -> 2600 bytes
-rw-r--r--searx/translations/es/LC_MESSAGES/messages.po149
-rw-r--r--searx/translations/fr/LC_MESSAGES/messages.mobin2473 -> 2613 bytes
-rw-r--r--searx/translations/fr/LC_MESSAGES/messages.po149
-rw-r--r--searx/translations/hu/LC_MESSAGES/messages.mobin2338 -> 2545 bytes
-rw-r--r--searx/translations/hu/LC_MESSAGES/messages.po138
-rw-r--r--searx/translations/it/LC_MESSAGES/messages.mobin2439 -> 2566 bytes
-rw-r--r--searx/translations/it/LC_MESSAGES/messages.po145
-rw-r--r--searx/translations/ja/LC_MESSAGES/messages.mobin0 -> 2477 bytes
-rw-r--r--searx/translations/ja/LC_MESSAGES/messages.po215
-rw-r--r--searx/translations/nl/LC_MESSAGES/messages.mobin2396 -> 2520 bytes
-rw-r--r--searx/translations/nl/LC_MESSAGES/messages.po143
-rw-r--r--searx/webapp.py53
-rw-r--r--setup.py2
61 files changed, 2998 insertions, 770 deletions
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 37332bb5f..311c97781 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -25,3 +25,4 @@ generally made searx better:
- rike
- dp
- Martin Zimmermann
+- @courgette
diff --git a/searx/__init__.py b/searx/__init__.py
index d4fc7f0bb..c4c363bf8 100644
--- a/searx/__init__.py
+++ b/searx/__init__.py
@@ -1,3 +1,20 @@
+'''
+searx is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+searx is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with searx. If not, see < http://www.gnu.org/licenses/ >.
+
+(C) 2013- by Adam Tauber, <asciimoo@gmail.com>
+'''
+
from os import environ
from os.path import realpath, dirname, join, abspath
from searx.https_rewrite import load_https_rules
@@ -11,8 +28,10 @@ except:
searx_dir = abspath(dirname(__file__))
engine_dir = dirname(realpath(__file__))
+# if possible set path to settings using the enviroment variable SEARX_SETTINGS_PATH
if 'SEARX_SETTINGS_PATH' in environ:
settings_path = environ['SEARX_SETTINGS_PATH']
+# otherwise using default path
else:
settings_path = join(searx_dir, 'settings.yml')
@@ -21,6 +40,7 @@ if 'SEARX_HTTPS_REWRITE_PATH' in environ:
else:
https_rewrite_path = join(searx_dir, 'https_rules')
+# load settings
with open(settings_path) as settings_yaml:
settings = load(settings_yaml)
diff --git a/searx/autocomplete.py b/searx/autocomplete.py
index 1726a8c3d..183769af8 100644
--- a/searx/autocomplete.py
+++ b/searx/autocomplete.py
@@ -1,3 +1,21 @@
+'''
+searx is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+searx is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with searx. If not, see < http://www.gnu.org/licenses/ >.
+
+(C) 2013- by Adam Tauber, <asciimoo@gmail.com>
+'''
+
+
from lxml import etree
from requests import get
from json import loads
@@ -21,6 +39,16 @@ def dbpedia(query):
return results
+def duckduckgo(query):
+ # duckduckgo autocompleter
+ url = 'https://ac.duckduckgo.com/ac/?{0}&type=list'
+
+ resp = loads(get(url.format(urlencode(dict(q=query)))).text)
+ if len(resp) > 1:
+ return resp[1]
+ return []
+
+
def google(query):
# google autocompleter
autocomplete_url = 'http://suggestqueries.google.com/complete/search?client=toolbar&' # noqa
@@ -48,6 +76,7 @@ def wikipedia(query):
backends = {'dbpedia': dbpedia,
+ 'duckduckgo': duckduckgo,
'google': google,
'wikipedia': wikipedia
}
diff --git a/searx/engines/__init__.py b/searx/engines/__init__.py
index 82c9407a2..e63dd7189 100644
--- a/searx/engines/__init__.py
+++ b/searx/engines/__init__.py
@@ -142,16 +142,28 @@ def get_engines_stats():
})
for engine in pageloads:
- engine['percentage'] = int(engine['avg'] / max_pageload * 100)
+ if max_pageload:
+ engine['percentage'] = int(engine['avg'] / max_pageload * 100)
+ else:
+ engine['percentage'] = 0
for engine in results:
- engine['percentage'] = int(engine['avg'] / max_results * 100)
+ if max_results:
+ engine['percentage'] = int(engine['avg'] / max_results * 100)
+ else:
+ engine['percentage'] = 0
for engine in scores:
- engine['percentage'] = int(engine['avg'] / max_score * 100)
+ if max_score:
+ engine['percentage'] = int(engine['avg'] / max_score * 100)
+ else:
+ engine['percentage'] = 0
for engine in scores_per_result:
- engine['percentage'] = int(engine['avg'] / max_score_per_result * 100)
+ if max_score_per_result:
+ engine['percentage'] = int(engine['avg'] / max_score_per_result * 100)
+ else:
+ engine['percentage'] = 0
for engine in errors:
if max_errors:
diff --git a/searx/engines/bing.py b/searx/engines/bing.py
index 88b097289..56c6b36c1 100644
--- a/searx/engines/bing.py
+++ b/searx/engines/bing.py
@@ -1,48 +1,82 @@
+## Bing (Web)
+#
+# @website https://www.bing.com
+# @provide-api yes (http://datamarket.azure.com/dataset/bing/search), max. 5000 query/month
+#
+# @using-api no (because of query limit)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content
+#
+# @todo publishedDate
+
from urllib import urlencode
from cgi import escape
from lxml import html
-base_url = 'http://www.bing.com/'
-search_string = 'search?{query}&first={offset}'
+# engine dependent config
+categories = ['general']
paging = True
language_support = True
+# search-url
+base_url = 'https://www.bing.com/'
+search_string = 'search?{query}&first={offset}'
+
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 10 + 1
+
if params['language'] == 'all':
language = 'en-US'
else:
language = params['language'].replace('_', '-')
+
search_path = search_string.format(
query=urlencode({'q': query, 'setmkt': language}),
offset=offset)
params['cookies']['SRCHHPGUSR'] = \
'NEWWND=0&NRSLT=-1&SRCHLANG=' + language.split('-')[0]
- #if params['category'] == 'images':
- # params['url'] = base_url + 'images/' + search_path
+
params['url'] = base_url + search_path
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.content)
+
+ # parse results
for result in dom.xpath('//div[@class="sa_cc"]'):
link = result.xpath('.//h3/a')[0]
url = link.attrib.get('href')
title = ' '.join(link.xpath('.//text()'))
content = escape(' '.join(result.xpath('.//p//text()')))
- results.append({'url': url, 'title': title, 'content': content})
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content})
+
+ # return results if something is found
if results:
return results
+ # parse results again if nothing is found yet
for result in dom.xpath('//li[@class="b_algo"]'):
link = result.xpath('.//h2/a')[0]
url = link.attrib.get('href')
title = ' '.join(link.xpath('.//text()'))
content = escape(' '.join(result.xpath('.//p//text()')))
- results.append({'url': url, 'title': title, 'content': content})
+
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content})
+
+ # return results
return results
diff --git a/searx/engines/bing_images.py b/searx/engines/bing_images.py
new file mode 100644
index 000000000..b3eabba45
--- /dev/null
+++ b/searx/engines/bing_images.py
@@ -0,0 +1,80 @@
+## Bing (Images)
+#
+# @website https://www.bing.com/images
+# @provide-api yes (http://datamarket.azure.com/dataset/bing/search), max. 5000 query/month
+#
+# @using-api no (because of query limit)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, img_src
+#
+# @todo currently there are up to 35 images receive per page, because bing does not parse count=10. limited response to 10 images
+
+from urllib import urlencode
+from cgi import escape
+from lxml import html
+from yaml import load
+import re
+
+# engine dependent config
+categories = ['images']
+paging = True
+
+# search-url
+base_url = 'https://www.bing.com/'
+search_string = 'images/search?{query}&count=10&first={offset}'
+
+
+# do search-request
+def request(query, params):
+ offset = (params['pageno'] - 1) * 10 + 1
+
+ # required for cookie
+ language = 'en-US'
+
+ search_path = search_string.format(
+ query=urlencode({'q': query}),
+ offset=offset)
+
+ params['cookies']['SRCHHPGUSR'] = \
+ 'NEWWND=0&NRSLT=-1&SRCHLANG=' + language.split('-')[0]
+
+ params['url'] = base_url + search_path
+
+ return params
+
+
+# get response from search-request
+def response(resp):
+ results = []
+
+ dom = html.fromstring(resp.content)
+
+ # init regex for yaml-parsing
+ p = re.compile( '({|,)([a-z]+):(")')
+
+ # parse results
+ for result in dom.xpath('//div[@class="dg_u"]'):
+ link = result.xpath('./a')[0]
+
+ # parse yaml-data (it is required to add a space, to make it parsable)
+ yaml_data = load(p.sub( r'\1\2: \3', link.attrib.get('m')))
+
+ title = link.attrib.get('t1')
+ #url = 'http://' + link.attrib.get('t3')
+ url = yaml_data.get('surl')
+ img_src = yaml_data.get('imgurl')
+
+ # append result
+ results.append({'template': 'images.html',
+ 'url': url,
+ 'title': title,
+ 'content': '',
+ 'img_src': img_src})
+
+ # TODO stop parsing if 10 images are found
+ if len(results) >= 10:
+ break
+
+ # return results
+ return results
diff --git a/searx/engines/bing_news.py b/searx/engines/bing_news.py
index 56c6f1208..279f0d698 100644
--- a/searx/engines/bing_news.py
+++ b/searx/engines/bing_news.py
@@ -1,50 +1,100 @@
+## Bing (News)
+#
+# @website https://www.bing.com/news
+# @provide-api yes (http://datamarket.azure.com/dataset/bing/search), max. 5000 query/month
+#
+# @using-api no (because of query limit)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content, publishedDate
+
from urllib import urlencode
from cgi import escape
from lxml import html
+from datetime import datetime, timedelta
+from dateutil import parser
+import re
+# engine dependent config
categories = ['news']
-
-base_url = 'http://www.bing.com/'
-search_string = 'news/search?{query}&first={offset}'
paging = True
language_support = True
+# search-url
+base_url = 'https://www.bing.com/'
+search_string = 'news/search?{query}&first={offset}'
+
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 10 + 1
+
if params['language'] == 'all':
language = 'en-US'
else:
language = params['language'].replace('_', '-')
+
search_path = search_string.format(
query=urlencode({'q': query, 'setmkt': language}),
offset=offset)
params['cookies']['SRCHHPGUSR'] = \
'NEWWND=0&NRSLT=-1&SRCHLANG=' + language.split('-')[0]
- #if params['category'] == 'images':
- # params['url'] = base_url + 'images/' + search_path
+
params['url'] = base_url + search_path
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.content)
- for result in dom.xpath('//div[@class="sa_cc"]'):
- link = result.xpath('.//h3/a')[0]
+
+ # parse results
+ for result in dom.xpath('//div[@class="sn_r"]'):
+ link = result.xpath('.//div[@class="newstitle"]/a')[0]
url = link.attrib.get('href')
title = ' '.join(link.xpath('.//text()'))
- content = escape(' '.join(result.xpath('.//p//text()')))
- results.append({'url': url, 'title': title, 'content': content})
+ contentXPath = result.xpath('.//div[@class="sn_txt"]/div//span[@class="sn_snip"]//text()')
+ if contentXPath != None:
+ content = escape(' '.join(contentXPath))
+
+ # parse publishedDate
+ publishedDateXPath = result.xpath('.//div[@class="sn_txt"]/div//span[contains(@class,"sn_ST")]//span[contains(@class,"sn_tm")]//text()')
+ if publishedDateXPath != None:
+ publishedDate = escape(' '.join(publishedDateXPath))
- if results:
- return results
+ if re.match("^[0-9]+ minute(s|) ago$", publishedDate):
+ timeNumbers = re.findall(r'\d+', publishedDate)
+ publishedDate = datetime.now()\
+ - timedelta(minutes=int(timeNumbers[0]))
+ elif re.match("^[0-9]+ hour(s|) ago$", publishedDate):
+ timeNumbers = re.findall(r'\d+', publishedDate)
+ publishedDate = datetime.now()\
+ - timedelta(hours=int(timeNumbers[0]))
+ elif re.match("^[0-9]+ hour(s|), [0-9]+ minute(s|) ago$", publishedDate):
+ timeNumbers = re.findall(r'\d+', publishedDate)
+ publishedDate = datetime.now()\
+ - timedelta(hours=int(timeNumbers[0]))\
+ - timedelta(minutes=int(timeNumbers[1]))
+ elif re.match("^[0-9]+ day(s|) ago$", publishedDate):
+ timeNumbers = re.findall(r'\d+', publishedDate)
+ publishedDate = datetime.now()\
+ - timedelta(days=int(timeNumbers[0]))
+ else:
+ try:
+ # FIXME use params['language'] to parse either mm/dd or dd/mm
+ publishedDate = parser.parse(publishedDate, dayfirst=False)
+ except TypeError:
+ # FIXME
+ publishedDate = datetime.now()
+
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'publishedDate': publishedDate,
+ 'content': content})
- for result in dom.xpath('//li[@class="b_algo"]'):
- link = result.xpath('.//h2/a')[0]
- url = link.attrib.get('href')
- title = ' '.join(link.xpath('.//text()'))
- content = escape(' '.join(result.xpath('.//p//text()')))
- results.append({'url': url, 'title': title, 'content': content})
+ # return results
return results
diff --git a/searx/engines/currency_convert.py b/searx/engines/currency_convert.py
index 561527bce..b5f0953d8 100644
--- a/searx/engines/currency_convert.py
+++ b/searx/engines/currency_convert.py
@@ -38,16 +38,14 @@ def response(resp):
except:
return results
- title = '{0} {1} in {2} is {3}'.format(
+ answer = '{0} {1} = {2} {3} (1 {1} = {4} {3})'.format(
resp.search_params['ammount'],
resp.search_params['from'],
+ resp.search_params['ammount'] * conversion_rate,
resp.search_params['to'],
- resp.search_params['ammount'] * conversion_rate
+ conversion_rate
)
- content = '1 {0} is {1} {2}'.format(resp.search_params['from'],
- conversion_rate,
- resp.search_params['to'])
now_date = datetime.now().strftime('%Y%m%d')
url = 'http://finance.yahoo.com/currency/converter-results/{0}/{1}-{2}-to-{3}.html' # noqa
url = url.format(
@@ -56,6 +54,7 @@ def response(resp):
resp.search_params['from'].lower(),
resp.search_params['to'].lower()
)
- results.append({'title': title, 'content': content, 'url': url})
+
+ results.append({'answer' : answer, 'url': url})
return results
diff --git a/searx/engines/dailymotion.py b/searx/engines/dailymotion.py
index 03e1d7ffc..75c2e5071 100644
--- a/searx/engines/dailymotion.py
+++ b/searx/engines/dailymotion.py
@@ -1,45 +1,66 @@
+## Dailymotion (Videos)
+#
+# @website https://www.dailymotion.com
+# @provide-api yes (http://www.dailymotion.com/developer)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse url, title, thumbnail
+#
+# @todo set content-parameter with correct data
+
from urllib import urlencode
from json import loads
from lxml import html
+# engine dependent config
categories = ['videos']
-locale = 'en_US'
+paging = True
+language_support = True
+# search-url
# see http://www.dailymotion.com/doc/api/obj-video.html
-search_url = 'https://api.dailymotion.com/videos?fields=title,description,duration,url,thumbnail_360_url&sort=relevance&limit=25&page={pageno}&{query}' # noqa
-
-# TODO use video result template
-content_tpl = '<a href="{0}" title="{0}" ><img src="{1}" /></a><br />'
-
-paging = True
+search_url = 'https://api.dailymotion.com/videos?fields=title,description,duration,url,thumbnail_360_url&sort=relevance&limit=5&page={pageno}&{query}' # noqa
+# do search-request
def request(query, params):
+ if params['language'] == 'all':
+ locale = 'en-US'
+ else:
+ locale = params['language']
+
params['url'] = search_url.format(
query=urlencode({'search': query, 'localization': locale}),
pageno=params['pageno'])
+
return params
+# get response from search-request
def response(resp):
results = []
+
search_res = loads(resp.text)
+
+ # return empty array if there are no results
if not 'list' in search_res:
- return results
+ return []
+
+ # parse results
for res in search_res['list']:
title = res['title']
url = res['url']
- if res['thumbnail_360_url']:
- content = content_tpl.format(url, res['thumbnail_360_url'])
- else:
- content = ''
- if res['description']:
- description = text_content_from_html(res['description'])
- content += description[:500]
- results.append({'url': url, 'title': title, 'content': content})
- return results
+ #content = res['description']
+ content = ''
+ thumbnail = res['thumbnail_360_url']
+ results.append({'template': 'videos.html',
+ 'url': url,
+ 'title': title,
+ 'content': content,
+ 'thumbnail': thumbnail})
-def text_content_from_html(html_string):
- desc_html = html.fragment_fromstring(html_string, create_parent=True)
- return desc_html.text_content()
+ # return results
+ return results
diff --git a/searx/engines/deviantart.py b/searx/engines/deviantart.py
index 298b9a397..ff5e1d465 100644
--- a/searx/engines/deviantart.py
+++ b/searx/engines/deviantart.py
@@ -1,35 +1,61 @@
+## Deviantart (Images)
+#
+# @website https://www.deviantart.com/
+# @provide-api yes (https://www.deviantart.com/developers/) (RSS)
+#
+# @using-api no (TODO, rewrite to api)
+# @results HTML
+# @stable no (HTML can change)
+# @parse url, title, thumbnail, img_src
+#
+# @todo rewrite to api
+
from urllib import urlencode
from urlparse import urljoin
from lxml import html
+# engine dependent config
categories = ['images']
+paging = True
+# search-url
base_url = 'https://www.deviantart.com/'
search_url = base_url+'search?offset={offset}&{query}'
-paging = True
-
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 24
+
params['url'] = search_url.format(offset=offset,
query=urlencode({'q': query}))
+
return params
+# get response from search-request
def response(resp):
results = []
+
+ # return empty array if a redirection code is returned
if resp.status_code == 302:
- return results
+ return []
+
dom = html.fromstring(resp.text)
+
+ # parse results
for result in dom.xpath('//div[contains(@class, "tt-a tt-fh")]'):
link = result.xpath('.//a[contains(@class, "thumb")]')[0]
url = urljoin(base_url, link.attrib.get('href'))
title_links = result.xpath('.//span[@class="details"]//a[contains(@class, "t")]') # noqa
title = ''.join(title_links[0].xpath('.//text()'))
img_src = link.xpath('.//img')[0].attrib['src']
+
+ # append result
results.append({'url': url,
'title': title,
'img_src': img_src,
'template': 'images.html'})
+
+ # return results
return results
diff --git a/searx/engines/duckduckgo.py b/searx/engines/duckduckgo.py
index 58cbc9872..296dd9b2d 100644
--- a/searx/engines/duckduckgo.py
+++ b/searx/engines/duckduckgo.py
@@ -1,65 +1,74 @@
+## DuckDuckGo (Web)
+#
+# @website https://duckduckgo.com/
+# @provide-api yes (https://duckduckgo.com/api), but not all results from search-site
+#
+# @using-api no
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content
+#
+# @todo rewrite to api
+# @todo language support (the current used site does not support language-change)
+
from urllib import urlencode
from lxml.html import fromstring
from searx.utils import html_to_text
+# engine dependent config
+categories = ['general']
+paging = True
+language_support = True
+
+# search-url
url = 'https://duckduckgo.com/html?{query}&s={offset}'
-locale = 'us-en'
+# specific xpath variables
+result_xpath = '//div[@class="results_links results_links_deep web-result"]' # noqa
+url_xpath = './/a[@class="large"]/@href'
+title_xpath = './/a[@class="large"]//text()'
+content_xpath = './/div[@class="snippet"]//text()'
+
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 30
- q = urlencode({'q': query,
- 'l': locale})
- params['url'] = url.format(query=q, offset=offset)
+
+ if params['language'] == 'all':
+ locale = 'en-us'
+ else:
+ locale = params['language'].replace('_','-').lower()
+
+ params['url'] = url.format(
+ query=urlencode({'q': query, 'kl': locale}),
+ offset=offset)
+
return params
+# get response from search-request
def response(resp):
- result_xpath = '//div[@class="results_links results_links_deep web-result"]' # noqa
- url_xpath = './/a[@class="large"]/@href'
- title_xpath = './/a[@class="large"]//text()'
- content_xpath = './/div[@class="snippet"]//text()'
results = []
doc = fromstring(resp.text)
+ # parse results
for r in doc.xpath(result_xpath):
try:
res_url = r.xpath(url_xpath)[-1]
except:
continue
+
if not res_url:
continue
+
title = html_to_text(''.join(r.xpath(title_xpath)))
content = html_to_text(''.join(r.xpath(content_xpath)))
+
+ # append result
results.append({'title': title,
'content': content,
'url': res_url})
+ # return results
return results
-
-
-#from json import loads
-#search_url = url + 'd.js?{query}&p=1&s={offset}'
-#
-#paging = True
-#
-#
-#def request(query, params):
-# offset = (params['pageno'] - 1) * 30
-# q = urlencode({'q': query,
-# 'l': locale})
-# params['url'] = search_url.format(query=q, offset=offset)
-# return params
-#
-#
-#def response(resp):
-# results = []
-# search_res = loads(resp.text[resp.text.find('[{'):-2])[:-1]
-# for r in search_res:
-# if not r.get('t'):
-# continue
-# results.append({'title': r['t'],
-# 'content': html_to_text(r['a']),
-# 'url': r['u']})
-# return results
diff --git a/searx/engines/duckduckgo_definitions.py b/searx/engines/duckduckgo_definitions.py
index 3037aae53..c008f22f7 100644
--- a/searx/engines/duckduckgo_definitions.py
+++ b/searx/engines/duckduckgo_definitions.py
@@ -1,10 +1,25 @@
import json
from urllib import urlencode
+from lxml import html
+from searx.engines.xpath import extract_text
-url = 'http://api.duckduckgo.com/?{query}&format=json&pretty=0&no_redirect=1'
+url = 'https://api.duckduckgo.com/?{query}&format=json&pretty=0&no_redirect=1&d=1'
+def result_to_text(url, text, htmlResult):
+ # TODO : remove result ending with "Meaning" or "Category"
+ dom = html.fromstring(htmlResult)
+ a = dom.xpath('//a')
+ if len(a)>=1:
+ return extract_text(a[0])
+ else:
+ return text
+
+def html_to_text(htmlFragment):
+ dom = html.fromstring(htmlFragment)
+ return extract_text(dom)
def request(query, params):
+ # TODO add kl={locale}
params['url'] = url.format(query=urlencode({'q': query}))
return params
@@ -12,12 +27,111 @@ def request(query, params):
def response(resp):
search_res = json.loads(resp.text)
results = []
+
+ content = ''
+ heading = search_res.get('Heading', '')
+ attributes = []
+ urls = []
+ infobox_id = None
+ relatedTopics = []
+
+ # add answer if there is one
+ answer = search_res.get('Answer', '')
+ if answer != '':
+ results.append({ 'answer' : html_to_text(answer) })
+
+ # add infobox
if 'Definition' in search_res:
- if search_res.get('AbstractURL'):
- res = {'title': search_res.get('Heading', ''),
- 'content': search_res.get('Definition', ''),
- 'url': search_res.get('AbstractURL', ''),
- 'class': 'definition_result'}
- results.append(res)
+ content = content + search_res.get('Definition', '')
+
+ if 'Abstract' in search_res:
+ content = content + search_res.get('Abstract', '')
+
+
+ # image
+ image = search_res.get('Image', '')
+ image = None if image == '' else image
+
+ # attributes
+ if 'Infobox' in search_res:
+ infobox = search_res.get('Infobox', None)
+ if 'content' in infobox:
+ for info in infobox.get('content'):
+ attributes.append({'label': info.get('label'), 'value': info.get('value')})
+
+ # urls
+ for ddg_result in search_res.get('Results', []):
+ if 'FirstURL' in ddg_result:
+ firstURL = ddg_result.get('FirstURL', '')
+ text = ddg_result.get('Text', '')
+ urls.append({'title':text, 'url':firstURL})
+ results.append({'title':heading, 'url': firstURL})
+
+ # related topics
+ for ddg_result in search_res.get('RelatedTopics', None):
+ if 'FirstURL' in ddg_result:
+ suggestion = result_to_text(ddg_result.get('FirstURL', None), ddg_result.get('Text', None), ddg_result.get('Result', None))
+ if suggestion != heading:
+ results.append({'suggestion': suggestion})
+ elif 'Topics' in ddg_result:
+ suggestions = []
+ relatedTopics.append({ 'name' : ddg_result.get('Name', ''), 'suggestions': suggestions })
+ for topic_result in ddg_result.get('Topics', []):
+ suggestion = result_to_text(topic_result.get('FirstURL', None), topic_result.get('Text', None), topic_result.get('Result', None))
+ if suggestion != heading:
+ suggestions.append(suggestion)
+
+ # abstract
+ abstractURL = search_res.get('AbstractURL', '')
+ if abstractURL != '':
+ # add as result ? problem always in english
+ infobox_id = abstractURL
+ urls.append({'title': search_res.get('AbstractSource'), 'url': abstractURL})
+
+ # definition
+ definitionURL = search_res.get('DefinitionURL', '')
+ if definitionURL != '':
+ # add as result ? as answer ? problem always in english
+ infobox_id = definitionURL
+ urls.append({'title': search_res.get('DefinitionSource'), 'url': definitionURL})
+
+ # entity
+ entity = search_res.get('Entity', None)
+ # TODO continent / country / department / location / waterfall / mountain range : link to map search, get weather, near by locations
+ # TODO musician : link to music search
+ # TODO concert tour : ??
+ # TODO film / actor / television / media franchise : links to IMDB / rottentomatoes (or scrap result)
+ # TODO music : link tu musicbrainz / last.fm
+ # TODO book : ??
+ # TODO artist / playwright : ??
+ # TODO compagny : ??
+ # TODO software / os : ??
+ # TODO software engineer : ??
+ # TODO prepared food : ??
+ # TODO website : ??
+ # TODO performing art : ??
+ # TODO prepared food : ??
+ # TODO programming language : ??
+ # TODO file format : ??
+
+ if len(heading)>0:
+ # TODO get infobox.meta.value where .label='article_title'
+ if image==None and len(attributes)==0 and len(urls)==1 and len(relatedTopics)==0 and len(content)==0:
+ results.append({
+ 'url': urls[0]['url'],
+ 'title': heading,
+ 'content': content
+ })
+ else:
+ results.append({
+ 'infobox': heading,
+ 'id': infobox_id,
+ 'entity': entity,
+ 'content': content,
+ 'img_src' : image,
+ 'attributes': attributes,
+ 'urls': urls,
+ 'relatedTopics': relatedTopics
+ })
return results
diff --git a/searx/engines/dummy.py b/searx/engines/dummy.py
index 4586760a0..5a2cdf6b5 100644
--- a/searx/engines/dummy.py
+++ b/searx/engines/dummy.py
@@ -1,6 +1,14 @@
+## Dummy
+#
+# @results empty array
+# @stable yes
+
+
+# do search-request
def request(query, params):
return params
+# get response from search-request
def response(resp):
return []
diff --git a/searx/engines/generalfile.py b/searx/engines/generalfile.py
index d249c00c7..11d8b6955 100644
--- a/searx/engines/generalfile.py
+++ b/searx/engines/generalfile.py
@@ -1,35 +1,60 @@
+## General Files (Files)
+#
+# @website http://www.general-files.org
+# @provide-api no (nothing found)
+#
+# @using-api no (because nothing found)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content
+#
+# @todo detect torrents?
+
from lxml import html
+# engine dependent config
+categories = ['files']
+paging = True
+# search-url
base_url = 'http://www.general-file.com'
search_url = base_url + '/files-{letter}/{query}/{pageno}'
+# specific xpath variables
result_xpath = '//table[@class="block-file"]'
title_xpath = './/h2/a//text()'
url_xpath = './/h2/a/@href'
content_xpath = './/p//text()'
-paging = True
-
+# do search-request
def request(query, params):
+
params['url'] = search_url.format(query=query,
letter=query[0],
pageno=params['pageno'])
+
return params
+# get response from search-request
def response(resp):
-
results = []
+
dom = html.fromstring(resp.text)
+
+ # parse results
for result in dom.xpath(result_xpath):
url = result.xpath(url_xpath)[0]
+
# skip fast download links
if not url.startswith('/'):
continue
+
+ # append result
results.append({'url': base_url + url,
'title': ''.join(result.xpath(title_xpath)),
'content': ''.join(result.xpath(content_xpath))})
+ # return results
return results
diff --git a/searx/engines/github.py b/searx/engines/github.py
index d36797aba..53fec029f 100644
--- a/searx/engines/github.py
+++ b/searx/engines/github.py
@@ -1,31 +1,59 @@
+## Github (It)
+#
+# @website https://github.com/
+# @provide-api yes (https://developer.github.com/v3/)
+#
+# @using-api yes
+# @results JSON
+# @stable yes (using api)
+# @parse url, title, content
+
from urllib import urlencode
from json import loads
from cgi import escape
+# engine dependent config
categories = ['it']
+# search-url
search_url = 'https://api.github.com/search/repositories?sort=stars&order=desc&{query}' # noqa
accept_header = 'application/vnd.github.preview.text-match+json'
+# do search-request
def request(query, params):
params['url'] = search_url.format(query=urlencode({'q': query}))
+
params['headers']['Accept'] = accept_header
+
return params
+# get response from search-request
def response(resp):
results = []
+
search_res = loads(resp.text)
+
+ # check if items are recieved
if not 'items' in search_res:
- return results
+ return []
+
+ # parse results
for res in search_res['items']:
title = res['name']
url = res['html_url']
+
if res['description']:
content = escape(res['description'][:500])
else:
content = ''
- results.append({'url': url, 'title': title, 'content': content})
+
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content})
+
+ # return results
return results
diff --git a/searx/engines/google.py b/searx/engines/google.py
index 2c6a98af3..9dbe8b8f0 100644
--- a/searx/engines/google.py
+++ b/searx/engines/google.py
@@ -1,37 +1,115 @@
-#!/usr/bin/env python
+## Google (Web)
+#
+# @website https://www.google.com
+# @provide-api yes (https://developers.google.com/custom-search/)
+#
+# @using-api no
+# @results HTML
+# @stable no (HTML can change)
+# @parse url, title, content, suggestion
from urllib import urlencode
-from json import loads
+from urlparse import unquote,urlparse,parse_qsl
+from lxml import html
+from searx.engines.xpath import extract_text, extract_url
+# engine dependent config
categories = ['general']
-
-url = 'https://ajax.googleapis.com/'
-search_url = url + 'ajax/services/search/web?v=2.0&start={offset}&rsz=large&safe=off&filter=off&{query}&hl={language}' # noqa
-
paging = True
language_support = True
+# search-url
+google_hostname = 'www.google.com'
+search_path = '/search'
+redirect_path = '/url'
+images_path = '/images'
+search_url = 'https://' + google_hostname + search_path + '?{query}&start={offset}&gbv=1'
+
+# specific xpath variables
+results_xpath= '//li[@class="g"]'
+url_xpath = './/h3/a/@href'
+title_xpath = './/h3'
+content_xpath = './/span[@class="st"]'
+suggestion_xpath = '//p[@class="_Bmc"]'
+
+images_xpath = './/div/a'
+image_url_xpath = './@href'
+image_img_src_xpath = './img/@src'
+# remove google-specific tracking-url
+def parse_url(url_string):
+ parsed_url = urlparse(url_string)
+ if parsed_url.netloc in [google_hostname, ''] and parsed_url.path==redirect_path:
+ query = dict(parse_qsl(parsed_url.query))
+ return query['q']
+ else:
+ return url_string
+
+# do search-request
def request(query, params):
- offset = (params['pageno'] - 1) * 8
- language = 'en-US'
- if params['language'] != 'all':
- language = params['language'].replace('_', '-')
+ offset = (params['pageno'] - 1) * 10
+
+ if params['language'] == 'all':
+ language = 'en'
+ else:
+ language = params['language'].replace('_','-').lower()
+
params['url'] = search_url.format(offset=offset,
- query=urlencode({'q': query}),
- language=language)
+ query=urlencode({'q': query}))
+
+ params['headers']['Accept-Language'] = language
+
return params
+# get response from search-request
def response(resp):
results = []
- search_res = loads(resp.text)
- if not search_res.get('responseData', {}).get('results'):
- return []
+ dom = html.fromstring(resp.text)
+
+ # parse results
+ for result in dom.xpath(results_xpath):
+ title = extract_text(result.xpath(title_xpath)[0])
+ try:
+ url = parse_url(extract_url(result.xpath(url_xpath), search_url))
+ parsed_url = urlparse(url)
+ if parsed_url.netloc==google_hostname and parsed_url.path==search_path:
+ # remove the link to google news
+ continue
+
+ if parsed_url.netloc==google_hostname and parsed_url.path==images_path:
+ # images result
+ results = results + parse_images(result)
+ else:
+ # normal result
+ content = extract_text(result.xpath(content_xpath)[0])
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content})
+ except:
+ continue
+
+ # parse suggestion
+ for suggestion in dom.xpath(suggestion_xpath):
+ # append suggestion
+ results.append({'suggestion': extract_text(suggestion)})
+
+ # return results
+ return results
+
+def parse_images(result):
+ results = []
+ for image in result.xpath(images_xpath):
+ url = parse_url(extract_text(image.xpath(image_url_xpath)[0]))
+ img_src = extract_text(image.xpath(image_img_src_xpath)[0])
+
+ # append result
+ results.append({'url': url,
+ 'title': '',
+ 'content': '',
+ 'img_src': img_src,
+ 'template': 'images.html'})
- for result in search_res['responseData']['results']:
- results.append({'url': result['unescapedUrl'],
- 'title': result['titleNoFormatting'],
- 'content': result['content']})
return results
diff --git a/searx/engines/google_images.py b/searx/engines/google_images.py
index a6837f039..6c99f2801 100644
--- a/searx/engines/google_images.py
+++ b/searx/engines/google_images.py
@@ -1,36 +1,58 @@
-#!/usr/bin/env python
+## Google (Images)
+#
+# @website https://www.google.com
+# @provide-api yes (https://developers.google.com/web-search/docs/), deprecated!
+#
+# @using-api yes
+# @results JSON
+# @stable yes (but deprecated)
+# @parse url, title, img_src
from urllib import urlencode
from json import loads
+# engine dependent config
categories = ['images']
+paging = True
+# search-url
url = 'https://ajax.googleapis.com/'
search_url = url + 'ajax/services/search/images?v=1.0&start={offset}&rsz=large&safe=off&filter=off&{query}' # noqa
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 8
+
params['url'] = search_url.format(query=urlencode({'q': query}),
offset=offset)
+
return params
+# get response from search-request
def response(resp):
results = []
+
search_res = loads(resp.text)
- if not search_res.get('responseData'):
- return []
- if not search_res['responseData'].get('results'):
+
+ # return empty array if there are no results
+ if not search_res.get('responseData', {}).get('results'):
return []
+
+ # parse results
for result in search_res['responseData']['results']:
href = result['originalContextUrl']
title = result['title']
if not result['url']:
continue
+
+ # append result
results.append({'url': href,
'title': title,
'content': '',
'img_src': result['url'],
'template': 'images.html'})
+
+ # return results
return results
diff --git a/searx/engines/google_news.py b/searx/engines/google_news.py
index 72b7a0661..becc7e21d 100644
--- a/searx/engines/google_news.py
+++ b/searx/engines/google_news.py
@@ -1,43 +1,62 @@
-#!/usr/bin/env python
+## Google (News)
+#
+# @website https://www.google.com
+# @provide-api yes (https://developers.google.com/web-search/docs/), deprecated!
+#
+# @using-api yes
+# @results JSON
+# @stable yes (but deprecated)
+# @parse url, title, content, publishedDate
from urllib import urlencode
from json import loads
from dateutil import parser
+# search-url
categories = ['news']
+paging = True
+language_support = True
+# engine dependent config
url = 'https://ajax.googleapis.com/'
search_url = url + 'ajax/services/search/news?v=2.0&start={offset}&rsz=large&safe=off&filter=off&{query}&hl={language}' # noqa
-paging = True
-language_support = True
-
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 8
+
language = 'en-US'
if params['language'] != 'all':
language = params['language'].replace('_', '-')
+
params['url'] = search_url.format(offset=offset,
query=urlencode({'q': query}),
language=language)
+
return params
+# get response from search-request
def response(resp):
results = []
+
search_res = loads(resp.text)
+ # return empty array if there are no results
if not search_res.get('responseData', {}).get('results'):
return []
+ # parse results
for result in search_res['responseData']['results']:
-
-# Mon, 10 Mar 2014 16:26:15 -0700
+ # parse publishedDate
publishedDate = parser.parse(result['publishedDate'])
+ # append result
results.append({'url': result['unescapedUrl'],
'title': result['titleNoFormatting'],
'publishedDate': publishedDate,
'content': result['content']})
+
+ # return results
return results
diff --git a/searx/engines/mediawiki.py b/searx/engines/mediawiki.py
index f8cfb9afa..4a8b0e8b8 100644
--- a/searx/engines/mediawiki.py
+++ b/searx/engines/mediawiki.py
@@ -1,22 +1,78 @@
+## general mediawiki-engine (Web)
+#
+# @website websites built on mediawiki (https://www.mediawiki.org)
+# @provide-api yes (http://www.mediawiki.org/wiki/API:Search)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse url, title
+#
+# @todo content
+
from json import loads
+from string import Formatter
from urllib import urlencode, quote
-url = 'https://en.wikipedia.org/'
-
-search_url = url + 'w/api.php?action=query&list=search&{query}&srprop=timestamp&format=json&sroffset={offset}' # noqa
+# engine dependent config
+categories = ['general']
+language_support = True
+paging = True
+number_of_results = 1
-number_of_results = 10
+# search-url
+base_url = 'https://{language}.wikipedia.org/'
+search_url = base_url + 'w/api.php?action=query'\
+ '&list=search'\
+ '&{query}'\
+ '&srprop=timestamp'\
+ '&format=json'\
+ '&sroffset={offset}'\
+ '&srlimit={limit}'
+# do search-request
def request(query, params):
- offset = (params['pageno'] - 1) * 10
- params['url'] = search_url.format(query=urlencode({'srsearch': query}),
- offset=offset)
+ offset = (params['pageno'] - 1) * number_of_results
+ string_args = dict(query=urlencode({'srsearch': query}),
+ offset=offset,
+ limit=number_of_results)
+ format_strings = list(Formatter().parse(base_url))
+
+ if params['language'] == 'all':
+ language = 'en'
+ else:
+ language = params['language'].split('_')[0]
+
+ if len(format_strings) > 1:
+ string_args['language'] = language
+
+ # write search-language back to params, required in response
+ params['language'] = language
+
+ params['url'] = search_url.format(**string_args)
+
return params
+# get response from search-request
def response(resp):
+ results = []
+
search_results = loads(resp.text)
- res = search_results.get('query', {}).get('search', [])
- return [{'url': url + 'wiki/' + quote(result['title'].replace(' ', '_').encode('utf-8')), # noqa
- 'title': result['title']} for result in res[:int(number_of_results)]]
+
+ # return empty array if there are no results
+ if not search_results.get('query', {}).get('search'):
+ return []
+
+ # parse results
+ for result in search_results['query']['search']:
+ url = base_url.format(language=resp.search_params['language']) + 'wiki/' + quote(result['title'].replace(' ', '_').encode('utf-8'))
+
+ # append result
+ results.append({'url': url,
+ 'title': result['title'],
+ 'content': ''})
+
+ # return results
+ return results
diff --git a/searx/engines/openstreetmap.py b/searx/engines/openstreetmap.py
new file mode 100644
index 000000000..ea7251486
--- /dev/null
+++ b/searx/engines/openstreetmap.py
@@ -0,0 +1,47 @@
+## OpenStreetMap (Map)
+#
+# @website https://openstreetmap.org/
+# @provide-api yes (http://wiki.openstreetmap.org/wiki/Nominatim)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse url, title
+
+from json import loads
+
+# engine dependent config
+categories = ['map']
+paging = False
+
+# search-url
+url = 'https://nominatim.openstreetmap.org/search/{query}?format=json'
+
+result_base_url = 'https://openstreetmap.org/{osm_type}/{osm_id}'
+
+
+# do search-request
+def request(query, params):
+ params['url'] = url.format(query=query)
+
+ return params
+
+
+# get response from search-request
+def response(resp):
+ results = []
+ json = loads(resp.text)
+
+ # parse results
+ for r in json:
+ title = r['display_name']
+ osm_type = r.get('osm_type', r.get('type'))
+ url = result_base_url.format(osm_type=osm_type,
+ osm_id=r['osm_id'])
+ # append result
+ results.append({'title': title,
+ 'content': '',
+ 'url': url})
+
+ # return results
+ return results
diff --git a/searx/engines/piratebay.py b/searx/engines/piratebay.py
index bb4886868..9533b629e 100644
--- a/searx/engines/piratebay.py
+++ b/searx/engines/piratebay.py
@@ -1,39 +1,61 @@
+## Piratebay (Videos, Music, Files)
+#
+# @website https://thepiratebay.se
+# @provide-api no (nothing found)
+#
+# @using-api no
+# @results HTML (using search portal)
+# @stable yes (HTML can change)
+# @parse url, title, content, seed, leech, magnetlink
+
from urlparse import urljoin
from cgi import escape
from urllib import quote
from lxml import html
from operator import itemgetter
-categories = ['videos', 'music']
+# engine dependent config
+categories = ['videos', 'music', 'files']
+paging = True
+# search-url
url = 'https://thepiratebay.se/'
search_url = url + 'search/{search_term}/{pageno}/99/{search_type}'
-search_types = {'videos': '200',
+
+# piratebay specific type-definitions
+search_types = {'files': '0',
'music': '100',
- 'files': '0'}
+ 'videos': '200'}
+# specific xpath variables
magnet_xpath = './/a[@title="Download this torrent using magnet"]'
content_xpath = './/font[@class="detDesc"]//text()'
-paging = True
-
+# do search-request
def request(query, params):
- search_type = search_types.get(params['category'], '200')
+ search_type = search_types.get(params['category'], '0')
+
params['url'] = search_url.format(search_term=quote(query),
search_type=search_type,
pageno=params['pageno'] - 1)
+
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.text)
+
search_res = dom.xpath('//table[@id="searchResult"]//tr')
+ # return empty array if nothing is found
if not search_res:
- return results
+ return []
+ # parse results
for result in search_res[1:]:
link = result.xpath('.//div[@class="detName"]//a')[0]
href = urljoin(url, link.attrib.get('href'))
@@ -41,17 +63,21 @@ def response(resp):
content = escape(' '.join(result.xpath(content_xpath)))
seed, leech = result.xpath('.//td[@align="right"]/text()')[:2]
+ # convert seed to int if possible
if seed.isdigit():
seed = int(seed)
else:
seed = 0
+ # convert leech to int if possible
if leech.isdigit():
leech = int(leech)
else:
leech = 0
magnetlink = result.xpath(magnet_xpath)[0]
+
+ # append result
results.append({'url': href,
'title': title,
'content': content,
@@ -60,4 +86,5 @@ def response(resp):
'magnetlink': magnetlink.attrib['href'],
'template': 'torrent.html'})
+ # return results sorted by seeder
return sorted(results, key=itemgetter('seed'), reverse=True)
diff --git a/searx/engines/soundcloud.py b/searx/engines/soundcloud.py
index 07cdbc273..aebea239f 100644
--- a/searx/engines/soundcloud.py
+++ b/searx/engines/soundcloud.py
@@ -1,30 +1,55 @@
+## Soundcloud (Music)
+#
+# @website https://soundcloud.com
+# @provide-api yes (https://developers.soundcloud.com/)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse url, title, content
+
from json import loads
from urllib import urlencode
+# engine dependent config
categories = ['music']
+paging = True
+# api-key
guest_client_id = 'b45b1aa10f1ac2941910a7f0d10f8e28'
-url = 'https://api.soundcloud.com/'
-search_url = url + 'search?{query}&facet=model&limit=20&offset={offset}&linked_partitioning=1&client_id='+guest_client_id # noqa
-paging = True
+# search-url
+url = 'https://api.soundcloud.com/'
+search_url = url + 'search?{query}&facet=model&limit=20&offset={offset}&linked_partitioning=1&client_id={client_id}'
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 20
+
params['url'] = search_url.format(query=urlencode({'q': query}),
- offset=offset)
+ offset=offset,
+ client_id=guest_client_id)
+
return params
+# get response from search-request
def response(resp):
results = []
+
search_res = loads(resp.text)
+
+ # parse results
for result in search_res.get('collection', []):
if result['kind'] in ('track', 'playlist'):
title = result['title']
content = result['description']
+
+ # append result
results.append({'url': result['permalink_url'],
'title': title,
'content': content})
+
+ # return results
return results
diff --git a/searx/engines/stackoverflow.py b/searx/engines/stackoverflow.py
index e24b309c1..edbe74a70 100644
--- a/searx/engines/stackoverflow.py
+++ b/searx/engines/stackoverflow.py
@@ -1,30 +1,58 @@
+## Stackoverflow (It)
+#
+# @website https://stackoverflow.com/
+# @provide-api not clear (https://api.stackexchange.com/docs/advanced-search)
+#
+# @using-api no
+# @results HTML
+# @stable no (HTML can change)
+# @parse url, title, content
+
from urlparse import urljoin
from cgi import escape
from urllib import urlencode
from lxml import html
+# engine dependent config
categories = ['it']
+paging = True
+# search-url
url = 'http://stackoverflow.com/'
search_url = url+'search?{query}&page={pageno}'
-result_xpath = './/div[@class="excerpt"]//text()'
-paging = True
+# specific xpath variables
+results_xpath = '//div[contains(@class,"question-summary")]'
+link_xpath = './/div[@class="result-link"]//a|.//div[@class="summary"]//h3//a'
+title_xpath = './/text()'
+content_xpath = './/div[@class="excerpt"]//text()'
+# do search-request
def request(query, params):
params['url'] = search_url.format(query=urlencode({'q': query}),
pageno=params['pageno'])
+
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.text)
- for result in dom.xpath('//div[@class="question-summary search-result"]'):
- link = result.xpath('.//div[@class="result-link"]//a')[0]
+
+ # parse results
+ for result in dom.xpath(results_xpath):
+ link = result.xpath(link_xpath)[0]
href = urljoin(url, link.attrib.get('href'))
- title = escape(' '.join(link.xpath('.//text()')))
- content = escape(' '.join(result.xpath(result_xpath)))
- results.append({'url': href, 'title': title, 'content': content})
+ title = escape(' '.join(link.xpath(title_xpath)))
+ content = escape(' '.join(result.xpath(content_xpath)))
+
+ # append result
+ results.append({'url': href,
+ 'title': title,
+ 'content': content})
+
+ # return results
return results
diff --git a/searx/engines/startpage.py b/searx/engines/startpage.py
index 8d44d05ab..2adbfb3e4 100644
--- a/searx/engines/startpage.py
+++ b/searx/engines/startpage.py
@@ -1,47 +1,79 @@
+## Startpage (Web)
+#
+# @website https://startpage.com
+# @provide-api no (nothing found)
+#
+# @using-api no
+# @results HTML
+# @stable no (HTML can change)
+# @parse url, title, content
+#
+# @todo paging
+
from urllib import urlencode
from lxml import html
from cgi import escape
+import re
+
+# engine dependent config
+categories = ['general']
+# there is a mechanism to block "bot" search (probably the parameter qid), require storing of qid's between mulitble search-calls
+#paging = False
+language_support = True
-base_url = None
-search_url = None
+# search-url
+base_url = 'https://startpage.com/'
+search_url = base_url + 'do/search'
-# TODO paging
-paging = False
-# TODO complete list of country mapping
-country_map = {'en_US': 'eng',
- 'en_UK': 'uk',
- 'nl_NL': 'ned'}
+# specific xpath variables
+# ads xpath //div[@id="results"]/div[@id="sponsored"]//div[@class="result"]
+# not ads: div[@class="result"] are the direct childs of div[@id="results"]
+results_xpath = '//div[@class="result"]'
+link_xpath = './/h3/a'
+# do search-request
def request(query, params):
+ offset = (params['pageno'] - 1) * 10
query = urlencode({'q': query})[2:]
+
params['url'] = search_url
params['method'] = 'POST'
params['data'] = {'query': query,
- 'startat': (params['pageno'] - 1) * 10} # offset
- country = country_map.get(params['language'], 'eng')
- params['cookies']['preferences'] = \
- 'lang_homepageEEEs/air/{country}/N1NsslEEE1N1Nfont_sizeEEEmediumN1Nrecent_results_filterEEE1N1Nlanguage_uiEEEenglishN1Ndisable_open_in_new_windowEEE0N1Ncolor_schemeEEEnewN1Nnum_of_resultsEEE10N1N'.format(country=country) # noqa
+ 'startat': offset}
+
+ # set language if specified
+ if params['language'] != 'all':
+ params['data']['with_language'] = 'lang_' + params['language'].split('_')[0]
+
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.content)
- # ads xpath //div[@id="results"]/div[@id="sponsored"]//div[@class="result"]
- # not ads: div[@class="result"] are the direct childs of div[@id="results"]
- for result in dom.xpath('//div[@class="result"]'):
- link = result.xpath('.//h3/a')[0]
+
+ # parse results
+ for result in dom.xpath(results_xpath):
+ link = result.xpath(link_xpath)[0]
url = link.attrib.get('href')
- if url.startswith('http://www.google.')\
- or url.startswith('https://www.google.'):
- continue
title = escape(link.text_content())
- content = ''
+ # block google-ad url's
+ if re.match("^http(s|)://www.google.[a-z]+/aclk.*$", url):
+ continue
+
if result.xpath('./p[@class="desc"]'):
content = escape(result.xpath('./p[@class="desc"]')[0].text_content())
+ else:
+ content = ''
- results.append({'url': url, 'title': title, 'content': content})
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content})
+ # return results
return results
diff --git a/searx/engines/twitter.py b/searx/engines/twitter.py
index c05c20fc2..8de78144e 100644
--- a/searx/engines/twitter.py
+++ b/searx/engines/twitter.py
@@ -1,30 +1,63 @@
+## Twitter (Social media)
+#
+# @website https://www.bing.com/news
+# @provide-api yes (https://dev.twitter.com/docs/using-search)
+#
+# @using-api no
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content
+#
+# @todo publishedDate
+
from urlparse import urljoin
from urllib import urlencode
from lxml import html
from cgi import escape
+# engine dependent config
categories = ['social media']
+language_support = True
+# search-url
base_url = 'https://twitter.com/'
search_url = base_url+'search?'
+
+# specific xpath variables
+results_xpath = '//li[@data-item-type="tweet"]'
+link_xpath = './/small[@class="time"]//a'
title_xpath = './/span[@class="username js-action-profile-name"]//text()'
content_xpath = './/p[@class="js-tweet-text tweet-text"]//text()'
+# do search-request
def request(query, params):
params['url'] = search_url + urlencode({'q': query})
+
+ # set language if specified
+ if params['language'] != 'all':
+ params['cookies']['lang'] = params['language'].split('_')[0]
+
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.text)
- for tweet in dom.xpath('//li[@data-item-type="tweet"]'):
- link = tweet.xpath('.//small[@class="time"]//a')[0]
+
+ # parse results
+ for tweet in dom.xpath(results_xpath):
+ link = tweet.xpath(link_xpath)[0]
url = urljoin(base_url, link.attrib.get('href'))
title = ''.join(tweet.xpath(title_xpath))
content = escape(''.join(tweet.xpath(content_xpath)))
+
+ # append result
results.append({'url': url,
'title': title,
'content': content})
+
+ # return results
return results
diff --git a/searx/engines/vimeo.py b/searx/engines/vimeo.py
index 94a6dd545..2a91e76fa 100644
--- a/searx/engines/vimeo.py
+++ b/searx/engines/vimeo.py
@@ -1,43 +1,58 @@
+## Vimeo (Videos)
+#
+# @website https://vimeo.com/
+# @provide-api yes (http://developer.vimeo.com/api), they have a maximum count of queries/hour
+#
+# @using-api no (TODO, rewrite to api)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, publishedDate, thumbnail
+#
+# @todo rewrite to api
+# @todo set content-parameter with correct data
+
from urllib import urlencode
from HTMLParser import HTMLParser
from lxml import html
from searx.engines.xpath import extract_text
from dateutil import parser
-base_url = 'http://vimeo.com'
-search_url = base_url + '/search?{query}'
-url_xpath = None
-content_xpath = None
-title_xpath = None
-results_xpath = ''
-content_tpl = '<a href="{0}"> <img src="{2}"/> </a>'
-publishedDate_xpath = './/p[@class="meta"]//attribute::datetime'
+# engine dependent config
+categories = ['videos']
+paging = True
-# the cookie set by vimeo contains all the following values,
-# but only __utma seems to be requiered
-cookie = {
- #'vuid':'918282893.1027205400'
- # 'ab_bs':'%7B%223%22%3A279%7D'
- '__utma': '00000000.000#0000000.0000000000.0000000000.0000000000.0'
- # '__utmb':'18302654.1.10.1388942090'
- #, '__utmc':'18302654'
- #, '__utmz':'18#302654.1388942090.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)' # noqa
- #, '__utml':'search'
-}
+# search-url
+base_url = 'https://vimeo.com'
+search_url = base_url + '/search/page:{pageno}?{query}'
+
+# specific xpath variables
+url_xpath = './a/@href'
+content_xpath = './a/img/@src'
+title_xpath = './a/div[@class="data"]/p[@class="title"]/text()'
+results_xpath = '//div[@id="browse_content"]/ol/li'
+publishedDate_xpath = './/p[@class="meta"]//attribute::datetime'
+# do search-request
def request(query, params):
- params['url'] = search_url.format(query=urlencode({'q': query}))
- params['cookies'] = cookie
+ params['url'] = search_url.format(pageno=params['pageno'] ,
+ query=urlencode({'q': query}))
+
+ # TODO required?
+ params['cookies']['__utma'] = '00000000.000#0000000.0000000000.0000000000.0000000000.0'
+
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.text)
p = HTMLParser()
+ # parse results
for result in dom.xpath(results_xpath):
url = base_url + result.xpath(url_xpath)[0]
title = p.unescape(extract_text(result.xpath(title_xpath)))
@@ -45,10 +60,13 @@ def response(resp):
publishedDate = parser.parse(extract_text(
result.xpath(publishedDate_xpath)[0]))
+ # append result
results.append({'url': url,
'title': title,
- 'content': content_tpl.format(url, title, thumbnail),
+ 'content': '',
'template': 'videos.html',
'publishedDate': publishedDate,
'thumbnail': thumbnail})
+
+ # return results
return results
diff --git a/searx/engines/wikidata.py b/searx/engines/wikidata.py
new file mode 100644
index 000000000..7877e1198
--- /dev/null
+++ b/searx/engines/wikidata.py
@@ -0,0 +1,238 @@
+import json
+from requests import get
+from urllib import urlencode
+
+resultCount=1
+urlSearch = 'https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srnamespace=0&srprop=sectiontitle&{query}'
+urlDetail = 'https://www.wikidata.org/w/api.php?action=wbgetentities&format=json&props=labels%7Cinfo%7Csitelinks%7Csitelinks%2Furls%7Cdescriptions%7Cclaims&{query}'
+urlMap = 'https://www.openstreetmap.org/?lat={latitude}&lon={longitude}&zoom={zoom}&layers=M'
+
+def request(query, params):
+ params['url'] = urlSearch.format(query=urlencode({'srsearch': query, 'srlimit': resultCount}))
+ return params
+
+
+def response(resp):
+ results = []
+ search_res = json.loads(resp.text)
+
+ wikidata_ids = set()
+ for r in search_res.get('query', {}).get('search', {}):
+ wikidata_ids.add(r.get('title', ''))
+
+ language = resp.search_params['language'].split('_')[0]
+ if language == 'all':
+ language = 'en'
+ url = urlDetail.format(query=urlencode({'ids': '|'.join(wikidata_ids), 'languages': language + '|en'}))
+
+ htmlresponse = get(url)
+ jsonresponse = json.loads(htmlresponse.content)
+ for wikidata_id in wikidata_ids:
+ results = results + getDetail(jsonresponse, wikidata_id, language)
+
+ return results
+
+def getDetail(jsonresponse, wikidata_id, language):
+ results = []
+ urls = []
+ attributes = []
+
+ result = jsonresponse.get('entities', {}).get(wikidata_id, {})
+
+ title = result.get('labels', {}).get(language, {}).get('value', None)
+ if title == None:
+ title = result.get('labels', {}).get('en', {}).get('value', None)
+ if title == None:
+ return results
+
+ description = result.get('descriptions', {}).get(language, {}).get('value', None)
+ if description == None:
+ description = result.get('descriptions', {}).get('en', {}).get('value', '')
+
+ claims = result.get('claims', {})
+ official_website = get_string(claims, 'P856', None)
+ if official_website != None:
+ urls.append({ 'title' : 'Official site', 'url': official_website })
+ results.append({ 'title': title, 'url' : official_website })
+
+ wikipedia_link_count = 0
+ if language != 'en':
+ wikipedia_link_count += add_url(urls, 'Wikipedia (' + language + ')', get_wikilink(result, language + 'wiki'))
+ wikipedia_en_link = get_wikilink(result, 'enwiki')
+ wikipedia_link_count += add_url(urls, 'Wikipedia (en)', wikipedia_en_link)
+ if wikipedia_link_count == 0:
+ misc_language = get_wiki_firstlanguage(result, 'wiki')
+ if misc_language != None:
+ add_url(urls, 'Wikipedia (' + misc_language + ')', get_wikilink(result, misc_language + 'wiki'))
+
+ if language != 'en':
+ add_url(urls, 'Wiki voyage (' + language + ')', get_wikilink(result, language + 'wikivoyage'))
+ add_url(urls, 'Wiki voyage (en)', get_wikilink(result, 'enwikivoyage'))
+
+ if language != 'en':
+ add_url(urls, 'Wikiquote (' + language + ')', get_wikilink(result, language + 'wikiquote'))
+ add_url(urls, 'Wikiquote (en)', get_wikilink(result, 'enwikiquote'))
+
+ add_url(urls, 'Commons wiki', get_wikilink(result, 'commonswiki'))
+
+ add_url(urls, 'Location', get_geolink(claims, 'P625', None))
+
+ add_url(urls, 'Wikidata', 'https://www.wikidata.org/wiki/' + wikidata_id + '?uselang='+ language)
+
+ musicbrainz_work_id = get_string(claims, 'P435')
+ if musicbrainz_work_id != None:
+ add_url(urls, 'MusicBrainz', 'http://musicbrainz.org/work/' + musicbrainz_work_id)
+
+ musicbrainz_artist_id = get_string(claims, 'P434')
+ if musicbrainz_artist_id != None:
+ add_url(urls, 'MusicBrainz', 'http://musicbrainz.org/artist/' + musicbrainz_artist_id)
+
+ musicbrainz_release_group_id = get_string(claims, 'P436')
+ if musicbrainz_release_group_id != None:
+ add_url(urls, 'MusicBrainz', 'http://musicbrainz.org/release-group/' + musicbrainz_release_group_id)
+
+ musicbrainz_label_id = get_string(claims, 'P966')
+ if musicbrainz_label_id != None:
+ add_url(urls, 'MusicBrainz', 'http://musicbrainz.org/label/' + musicbrainz_label_id)
+
+ # musicbrainz_area_id = get_string(claims, 'P982')
+ # P1407 MusicBrainz series ID
+ # P1004 MusicBrainz place ID
+ # P1330 MusicBrainz instrument ID
+ # P1407 MusicBrainz series ID
+
+ postal_code = get_string(claims, 'P281', None)
+ if postal_code != None:
+ attributes.append({'label' : 'Postal code(s)', 'value' : postal_code})
+
+ date_of_birth = get_time(claims, 'P569', None)
+ if date_of_birth != None:
+ attributes.append({'label' : 'Date of birth', 'value' : date_of_birth})
+
+ date_of_death = get_time(claims, 'P570', None)
+ if date_of_death != None:
+ attributes.append({'label' : 'Date of death', 'value' : date_of_death})
+
+ if len(attributes)==0 and len(urls)==2 and len(description)==0:
+ results.append({
+ 'url': urls[0]['url'],
+ 'title': title,
+ 'content': description
+ })
+ else:
+ results.append({
+ 'infobox' : title,
+ 'id' : wikipedia_en_link,
+ 'content' : description,
+ 'attributes' : attributes,
+ 'urls' : urls
+ })
+
+ return results
+
+
+def add_url(urls, title, url):
+ if url != None:
+ urls.append({'title' : title, 'url' : url})
+ return 1
+ else:
+ return 0
+
+def get_mainsnak(claims, propertyName):
+ propValue = claims.get(propertyName, {})
+ if len(propValue) == 0:
+ return None
+
+ propValue = propValue[0].get('mainsnak', None)
+ return propValue
+
+
+def get_string(claims, propertyName, defaultValue=None):
+ propValue = claims.get(propertyName, {})
+ if len(propValue) == 0:
+ return defaultValue
+
+ result = []
+ for e in propValue:
+ mainsnak = e.get('mainsnak', {})
+
+ datavalue = mainsnak.get('datavalue', {})
+ if datavalue != None:
+ result.append(datavalue.get('value', ''))
+
+ if len(result) == 0:
+ return defaultValue
+ else:
+ #TODO handle multiple urls
+ return result[0]
+
+
+def get_time(claims, propertyName, defaultValue=None):
+ propValue = claims.get(propertyName, {})
+ if len(propValue) == 0:
+ return defaultValue
+
+ result = []
+ for e in propValue:
+ mainsnak = e.get('mainsnak', {})
+
+ datavalue = mainsnak.get('datavalue', {})
+ if datavalue != None:
+ value = datavalue.get('value', '')
+ result.append(value.get('time', ''))
+
+ if len(result) == 0:
+ return defaultValue
+ else:
+ return ', '.join(result)
+
+
+def get_geolink(claims, propertyName, defaultValue=''):
+ mainsnak = get_mainsnak(claims, propertyName)
+
+ if mainsnak == None:
+ return defaultValue
+
+ datatype = mainsnak.get('datatype', '')
+ datavalue = mainsnak.get('datavalue', {})
+
+ if datatype != 'globe-coordinate':
+ return defaultValue
+
+ value = datavalue.get('value', {})
+
+ precision = value.get('precision', 0.0002)
+
+ # there is no zoom information, deduce from precision (error prone)
+ # samples :
+ # 13 --> 5
+ # 1 --> 6
+ # 0.016666666666667 --> 9
+ # 0.00027777777777778 --> 19
+ # wolframalpha : quadratic fit { {13, 5}, {1, 6}, {0.0166666, 9}, {0.0002777777,19}}
+ # 14.1186-8.8322 x+0.625447 x^2
+ if precision < 0.0003:
+ zoom = 19
+ else:
+ zoom = int(15 - precision*8.8322 + precision*precision*0.625447)
+
+ url = urlMap.replace('{latitude}', str(value.get('latitude',0))).replace('{longitude}', str(value.get('longitude',0))).replace('{zoom}', str(zoom))
+
+ return url
+
+
+def get_wikilink(result, wikiid):
+ url = result.get('sitelinks', {}).get(wikiid, {}).get('url', None)
+ if url == None:
+ return url
+ elif url.startswith('http://'):
+ url = url.replace('http://', 'https://')
+ elif url.startswith('//'):
+ url = 'https:' + url
+ return url
+
+def get_wiki_firstlanguage(result, wikipatternid):
+ for k in result.get('sitelinks', {}).keys():
+ if k.endswith(wikipatternid) and len(k)==(2+len(wikipatternid)):
+ return k[0:2]
+ return None
diff --git a/searx/engines/wikipedia.py b/searx/engines/wikipedia.py
deleted file mode 100644
index 1e2a798cc..000000000
--- a/searx/engines/wikipedia.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from json import loads
-from urllib import urlencode, quote
-
-url = 'https://{language}.wikipedia.org/'
-
-search_url = url + 'w/api.php?action=query&list=search&{query}&srprop=timestamp&format=json&sroffset={offset}' # noqa
-
-number_of_results = 10
-
-language_support = True
-
-
-def request(query, params):
- offset = (params['pageno'] - 1) * 10
- if params['language'] == 'all':
- language = 'en'
- else:
- language = params['language'].split('_')[0]
- params['language'] = language
- params['url'] = search_url.format(query=urlencode({'srsearch': query}),
- offset=offset,
- language=language)
- return params
-
-
-def response(resp):
- search_results = loads(resp.text)
- res = search_results.get('query', {}).get('search', [])
- return [{'url': url.format(language=resp.search_params['language']) + 'wiki/' + quote(result['title'].replace(' ', '_').encode('utf-8')), # noqa
- 'title': result['title']} for result in res[:int(number_of_results)]]
diff --git a/searx/engines/yacy.py b/searx/engines/yacy.py
index efdf846ac..2345b24f3 100644
--- a/searx/engines/yacy.py
+++ b/searx/engines/yacy.py
@@ -1,40 +1,89 @@
+## Yacy (Web, Images, Videos, Music, Files)
+#
+# @website http://yacy.net
+# @provide-api yes (http://www.yacy-websuche.de/wiki/index.php/Dev:APIyacysearch)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse (general) url, title, content, publishedDate
+# @parse (images) url, title, img_src
+#
+# @todo parse video, audio and file results
+
from json import loads
from urllib import urlencode
+from dateutil import parser
+
+# engine dependent config
+categories = ['general', 'images'] #TODO , 'music', 'videos', 'files'
+paging = True
+language_support = True
+number_of_results = 5
+
+# search-url
+base_url = 'http://localhost:8090'
+search_url = '/yacysearch.json?{query}&startRecord={offset}&maximumRecords={limit}&contentdom={search_type}&resource=global'
-url = 'http://localhost:8090'
-search_url = '/yacysearch.json?{query}&maximumRecords=10'
+# yacy specific type-definitions
+search_types = {'general': 'text',
+ 'images': 'image',
+ 'files': 'app',
+ 'music': 'audio',
+ 'videos': 'video'}
+# do search-request
def request(query, params):
- params['url'] = url + search_url.format(query=urlencode({'query': query}))
+ offset = (params['pageno'] - 1) * number_of_results
+ search_type = search_types.get(params['category'], '0')
+
+ params['url'] = base_url + search_url.format(query=urlencode({'query': query}),
+ offset=offset,
+ limit=number_of_results,
+ search_type=search_type)
+
+ # add language tag if specified
+ if params['language'] != 'all':
+ params['url'] += '&lr=lang_' + params['language'].split('_')[0]
+
return params
+# get response from search-request
def response(resp):
+ results = []
+
raw_search_results = loads(resp.text)
+ # return empty array if there are no results
if not raw_search_results:
return []
search_results = raw_search_results.get('channels', {})[0].get('items', [])
- results = []
-
- for result in search_results:
- tmp_result = {}
- tmp_result['title'] = result['title']
- tmp_result['url'] = result['link']
- tmp_result['content'] = ''
-
- if result['description']:
- tmp_result['content'] += result['description'] + "<br/>"
+ if resp.search_params['category'] == 'general':
+ # parse general results
+ for result in search_results:
+ publishedDate = parser.parse(result['pubDate'])
- if result['pubDate']:
- tmp_result['content'] += result['pubDate'] + "<br/>"
+ # append result
+ results.append({'url': result['link'],
+ 'title': result['title'],
+ 'content': result['description'],
+ 'publishedDate': publishedDate})
- if result['size'] != '-1':
- tmp_result['content'] += result['sizename']
+ elif resp.search_params['category'] == 'images':
+ # parse image results
+ for result in search_results:
+ # append result
+ results.append({'url': result['url'],
+ 'title': result['title'],
+ 'content': '',
+ 'img_src': result['image'],
+ 'template': 'images.html'})
- results.append(tmp_result)
+ #TODO parse video, audio and file results
+ # return results
return results
diff --git a/searx/engines/yahoo.py b/searx/engines/yahoo.py
index f89741839..5e34a2b07 100644
--- a/searx/engines/yahoo.py
+++ b/searx/engines/yahoo.py
@@ -1,64 +1,101 @@
-#!/usr/bin/env python
+## Yahoo (Web)
+#
+# @website https://search.yahoo.com/web
+# @provide-api yes (https://developer.yahoo.com/boss/search/), $0.80/1000 queries
+#
+# @using-api no (because pricing)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content, suggestion
from urllib import urlencode
from urlparse import unquote
from lxml import html
from searx.engines.xpath import extract_text, extract_url
+# engine dependent config
categories = ['general']
-search_url = 'http://search.yahoo.com/search?{query}&b={offset}'
+paging = True
+language_support = True
+
+# search-url
+search_url = 'https://search.yahoo.com/search?{query}&b={offset}&fl=1&vl=lang_{lang}'
+
+# specific xpath variables
results_xpath = '//div[@class="res"]'
url_xpath = './/h3/a/@href'
title_xpath = './/h3/a'
content_xpath = './/div[@class="abstr"]'
suggestion_xpath = '//div[@id="satat"]//a'
-paging = True
-
+# remove yahoo-specific tracking-url
def parse_url(url_string):
endings = ['/RS', '/RK']
endpositions = []
start = url_string.find('http', url_string.find('/RU=')+1)
+
for ending in endings:
endpos = url_string.rfind(ending)
if endpos > -1:
endpositions.append(endpos)
- end = min(endpositions)
- return unquote(url_string[start:end])
+ if start==0 or len(endpositions) == 0:
+ return url_string
+ else:
+ end = min(endpositions)
+ return unquote(url_string[start:end])
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 10 + 1
+
if params['language'] == 'all':
language = 'en'
else:
language = params['language'].split('_')[0]
+
params['url'] = search_url.format(offset=offset,
- query=urlencode({'p': query}))
+ query=urlencode({'p': query}),
+ lang=language)
+
+ # TODO required?
params['cookies']['sB'] = 'fl=1&vl=lang_{lang}&sh=1&rw=new&v=1'\
.format(lang=language)
+
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.text)
+ # parse results
for result in dom.xpath(results_xpath):
try:
url = parse_url(extract_url(result.xpath(url_xpath), search_url))
title = extract_text(result.xpath(title_xpath)[0])
except:
continue
+
content = extract_text(result.xpath(content_xpath)[0])
- results.append({'url': url, 'title': title, 'content': content})
+ # append result
+ results.append({'url': url,
+ 'title': title,
+ 'content': content})
+
+ # if no suggestion found, return results
if not suggestion_xpath:
return results
+ # parse suggestion
for suggestion in dom.xpath(suggestion_xpath):
+ # append suggestion
results.append({'suggestion': extract_text(suggestion)})
+ # return results
return results
diff --git a/searx/engines/yahoo_news.py b/searx/engines/yahoo_news.py
index 43da93ede..c07d7e185 100644
--- a/searx/engines/yahoo_news.py
+++ b/searx/engines/yahoo_news.py
@@ -1,4 +1,12 @@
-#!/usr/bin/env python
+## Yahoo (News)
+#
+# @website https://news.yahoo.com
+# @provide-api yes (https://developer.yahoo.com/boss/search/), $0.80/1000 queries
+#
+# @using-api no (because pricing)
+# @results HTML (using search portal)
+# @stable no (HTML can change)
+# @parse url, title, content, publishedDate
from urllib import urlencode
from lxml import html
@@ -8,8 +16,15 @@ from datetime import datetime, timedelta
import re
from dateutil import parser
+# engine dependent config
categories = ['news']
-search_url = 'http://news.search.yahoo.com/search?{query}&b={offset}'
+paging = True
+language_support = True
+
+# search-url
+search_url = 'https://news.search.yahoo.com/search?{query}&b={offset}&fl=1&vl=lang_{lang}'
+
+# specific xpath variables
results_xpath = '//div[@class="res"]'
url_xpath = './/h3/a/@href'
title_xpath = './/h3/a'
@@ -17,30 +32,39 @@ content_xpath = './/div[@class="abstr"]'
publishedDate_xpath = './/span[@class="timestamp"]'
suggestion_xpath = '//div[@id="satat"]//a'
-paging = True
-
+# do search-request
def request(query, params):
offset = (params['pageno'] - 1) * 10 + 1
+
if params['language'] == 'all':
language = 'en'
else:
language = params['language'].split('_')[0]
+
params['url'] = search_url.format(offset=offset,
- query=urlencode({'p': query}))
+ query=urlencode({'p': query}),
+ lang=language)
+
+ # TODO required?
params['cookies']['sB'] = 'fl=1&vl=lang_{lang}&sh=1&rw=new&v=1'\
.format(lang=language)
return params
+# get response from search-request
def response(resp):
results = []
+
dom = html.fromstring(resp.text)
+ # parse results
for result in dom.xpath(results_xpath):
url = parse_url(extract_url(result.xpath(url_xpath), search_url))
title = extract_text(result.xpath(title_xpath)[0])
content = extract_text(result.xpath(content_xpath)[0])
+
+ # parse publishedDate
publishedDate = extract_text(result.xpath(publishedDate_xpath)[0])
if re.match("^[0-9]+ minute(s|) ago$", publishedDate):
@@ -58,15 +82,11 @@ def response(resp):
if publishedDate.year == 1900:
publishedDate = publishedDate.replace(year=datetime.now().year)
+ # append result
results.append({'url': url,
'title': title,
'content': content,
'publishedDate': publishedDate})
- if not suggestion_xpath:
- return results
-
- for suggestion in dom.xpath(suggestion_xpath):
- results.append({'suggestion': extract_text(suggestion)})
-
+ # return results
return results
diff --git a/searx/engines/youtube.py b/searx/engines/youtube.py
index 895b55918..794028845 100644
--- a/searx/engines/youtube.py
+++ b/searx/engines/youtube.py
@@ -1,42 +1,69 @@
+## Youtube (Videos)
+#
+# @website https://www.youtube.com/
+# @provide-api yes (http://gdata-samples-youtube-search-py.appspot.com/)
+#
+# @using-api yes
+# @results JSON
+# @stable yes
+# @parse url, title, content, publishedDate, thumbnail
+
from json import loads
from urllib import urlencode
from dateutil import parser
-categories = ['videos']
-
-search_url = ('https://gdata.youtube.com/feeds/api/videos'
- '?alt=json&{query}&start-index={index}&max-results=25') # noqa
-
+# engine dependent config
+categories = ['videos', 'music']
paging = True
+language_support = True
+
+# search-url
+base_url = 'https://gdata.youtube.com/feeds/api/videos'
+search_url = base_url + '?alt=json&{query}&start-index={index}&max-results=5' # noqa
+# do search-request
def request(query, params):
- index = (params['pageno'] - 1) * 25 + 1
+ index = (params['pageno'] - 1) * 5 + 1
+
params['url'] = search_url.format(query=urlencode({'q': query}),
index=index)
+
+ # add language tag if specified
+ if params['language'] != 'all':
+ params['url'] += '&lr=' + params['language'].split('_')[0]
+
return params
+# get response from search-request
def response(resp):
results = []
+
search_results = loads(resp.text)
+
+ # return empty array if there are no results
if not 'feed' in search_results:
- return results
+ return []
+
feed = search_results['feed']
+ # parse results
for result in feed['entry']:
url = [x['href'] for x in result['link'] if x['type'] == 'text/html']
+
if not url:
return
+
# remove tracking
url = url[0].replace('feature=youtube_gdata', '')
if url.endswith('&'):
url = url[:-1]
+
title = result['title']['$t']
content = ''
thumbnail = ''
-#"2013-12-31T15:22:51.000Z"
pubdate = result['published']['$t']
publishedDate = parser.parse(pubdate)
@@ -49,6 +76,7 @@ def response(resp):
else:
content = result['content']['$t']
+ # append result
results.append({'url': url,
'title': title,
'content': content,
@@ -56,4 +84,5 @@ def response(resp):
'publishedDate': publishedDate,
'thumbnail': thumbnail})
+ # return results
return results
diff --git a/searx/languages.py b/searx/languages.py
index 8b12e5ffe..df5fabf74 100644
--- a/searx/languages.py
+++ b/searx/languages.py
@@ -1,3 +1,21 @@
+'''
+searx is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+searx is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with searx. If not, see < http://www.gnu.org/licenses/ >.
+
+(C) 2013- by Adam Tauber, <asciimoo@gmail.com>
+'''
+
+# list of language codes
language_codes = (
("ar_XA", "Arabic", "Arabia"),
("bg_BG", "Bulgarian", "Bulgaria"),
diff --git a/searx/query.py b/searx/query.py
new file mode 100644
index 000000000..612d46f4b
--- /dev/null
+++ b/searx/query.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+
+'''
+searx is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+searx is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with searx. If not, see < http://www.gnu.org/licenses/ >.
+
+(C) 2014 by Thomas Pointhuber, <thomas.pointhuber@gmx.at>
+'''
+
+from searx.languages import language_codes
+from searx.engines import (
+ categories, engines, engine_shortcuts
+)
+import string
+import re
+
+
+class Query(object):
+ """parse query"""
+
+ def __init__(self, query, blocked_engines):
+ self.query = query
+ self.blocked_engines = []
+
+ if blocked_engines:
+ self.blocked_engines = blocked_engines
+
+ self.query_parts = []
+ self.engines = []
+ self.languages = []
+
+ # parse query, if tags are set, which change the serch engine or search-language
+ def parse_query(self):
+ self.query_parts = []
+
+ # split query, including whitespaces
+ raw_query_parts = re.split(r'(\s+)', self.query)
+
+ parse_next = True
+
+ for query_part in raw_query_parts:
+ if not parse_next:
+ self.query_parts[-1] += query_part
+ continue
+
+ parse_next = False
+
+ # part does only contain spaces, skip
+ if query_part.isspace()\
+ or query_part == '':
+ parse_next = True
+ self.query_parts.append(query_part)
+ continue
+
+ # this force a language
+ if query_part[0] == ':':
+ lang = query_part[1:].lower()
+
+ # check if any language-code is equal with declared language-codes
+ for lc in language_codes:
+ lang_id, lang_name, country = map(str.lower, lc)
+
+ # if correct language-code is found, set it as new search-language
+ if lang == lang_id\
+ or lang_id.startswith(lang)\
+ or lang == lang_name\
+ or lang == country:
+ parse_next = True
+ self.languages.append(lang)
+ break
+
+ # this force a engine or category
+ if query_part[0] == '!':
+ prefix = query_part[1:].replace('_', ' ')
+
+ # check if prefix is equal with engine shortcut
+ if prefix in engine_shortcuts\
+ and not engine_shortcuts[prefix] in self.blocked_engines:
+ parse_next = True
+ self.engines.append({'category': 'none',
+ 'name': engine_shortcuts[prefix]})
+
+ # check if prefix is equal with engine name
+ elif prefix in engines\
+ and not prefix in self.blocked_engines:
+ parse_next = True
+ self.engines.append({'category': 'none',
+ 'name': prefix})
+
+ # check if prefix is equal with categorie name
+ elif prefix in categories:
+ # using all engines for that search, which are declared under that categorie name
+ parse_next = True
+ self.engines.extend({'category': prefix,
+ 'name': engine.name}
+ for engine in categories[prefix]
+ if not engine in self.blocked_engines)
+
+ # append query part to query_part list
+ self.query_parts.append(query_part)
+
+ def changeSearchQuery(self, search_query):
+ if len(self.query_parts):
+ self.query_parts[-1] = search_query
+ else:
+ self.query_parts.append(search_query)
+
+ def getSearchQuery(self):
+ if len(self.query_parts):
+ return self.query_parts[-1]
+ else:
+ return ''
+
+ def getFullQuery(self):
+ # get full querry including whitespaces
+ return string.join(self.query_parts, '')
+
diff --git a/searx/search.py b/searx/search.py
index 19b286d4d..064c68844 100644
--- a/searx/search.py
+++ b/searx/search.py
@@ -1,4 +1,22 @@
+'''
+searx is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+searx is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with searx. If not, see < http://www.gnu.org/licenses/ >.
+
+(C) 2013- by Adam Tauber, <asciimoo@gmail.com>
+'''
+
import grequests
+import re
from itertools import izip_longest, chain
from datetime import datetime
from operator import itemgetter
@@ -8,48 +26,91 @@ from searx.engines import (
)
from searx.languages import language_codes
from searx.utils import gen_useragent
+from searx.query import Query
+
number_of_searches = 0
+# get default reqest parameter
def default_request_params():
return {
'method': 'GET', 'headers': {}, 'data': {}, 'url': '', 'cookies': {}}
-def make_callback(engine_name, results, suggestions, callback, params):
+# create a callback wrapper for the search engine results
+def make_callback(engine_name, results, suggestions, answers, infoboxes, callback, params):
+
# creating a callback wrapper for the search engine results
def process_callback(response, **kwargs):
cb_res = []
response.search_params = params
- engines[engine_name].stats['page_load_time'] += \
- (datetime.now() - params['started']).total_seconds()
+
+ # callback
try:
search_results = callback(response)
except Exception, e:
+ # increase errors stats
engines[engine_name].stats['errors'] += 1
results[engine_name] = cb_res
+
+ # print engine name and specific error message
print '[E] Error with engine "{0}":\n\t{1}'.format(
engine_name, str(e))
return
+
+ # add results
for result in search_results:
result['engine'] = engine_name
+
+ # if it is a suggestion, add it to list of suggestions
if 'suggestion' in result:
# TODO type checks
suggestions.add(result['suggestion'])
continue
+
+ # if it is an answer, add it to list of answers
+ if 'answer' in result:
+ answers.add(result['answer'])
+ continue
+
+ # if it is an infobox, add it to list of infoboxes
+ if 'infobox' in result:
+ infoboxes.append(result)
+ continue
+
+ # append result
cb_res.append(result)
+
results[engine_name] = cb_res
+
+ # update stats with current page-load-time
+ engines[engine_name].stats['page_load_time'] += \
+ (datetime.now() - params['started']).total_seconds()
+
return process_callback
+# return the meaningful length of the content for a result
+def content_result_len(content):
+ if isinstance(content, basestring):
+ content = re.sub('[,;:!?\./\\\\ ()-_]', '', content)
+ return len(content)
+ else:
+ return 0
+
+
+# score results and remove duplications
def score_results(results):
+ # calculate scoring parameters
flat_res = filter(
None, chain.from_iterable(izip_longest(*results.values())))
flat_len = len(flat_res)
engines_len = len(results)
+
results = []
- # deduplication + scoring
+
+ # pass 1: deduplication + scoring
for i, res in enumerate(flat_res):
res['parsed_url'] = urlparse(res['url'])
@@ -60,37 +121,153 @@ def score_results(results):
res['host'] = res['host'].replace('www.', '', 1)
res['engines'] = [res['engine']]
+
weight = 1.0
+ # strip multiple spaces and cariage returns from content
+ if 'content' in res:
+ res['content'] = re.sub(' +', ' ', res['content'].strip().replace('\n', ''))
+
+ # get weight of this engine if possible
if hasattr(engines[res['engine']], 'weight'):
weight = float(engines[res['engine']].weight)
+ # calculate score for that engine
score = int((flat_len - i) / engines_len) * weight + 1
- duplicated = False
+ # check for duplicates
+ duplicated = False
for new_res in results:
+ # remove / from the end of the url if required
p1 = res['parsed_url'].path[:-1] if res['parsed_url'].path.endswith('/') else res['parsed_url'].path # noqa
p2 = new_res['parsed_url'].path[:-1] if new_res['parsed_url'].path.endswith('/') else new_res['parsed_url'].path # noqa
+
+ # check if that result is a duplicate
if res['host'] == new_res['host'] and\
unquote(p1) == unquote(p2) and\
res['parsed_url'].query == new_res['parsed_url'].query and\
res.get('template') == new_res.get('template'):
duplicated = new_res
break
+
+ # merge duplicates together
if duplicated:
- if res.get('content') > duplicated.get('content'):
+ # using content with more text
+ if content_result_len(res.get('content', '')) > content_result_len(duplicated.get('content', '')):
duplicated['content'] = res['content']
+
+ # increase result-score
duplicated['score'] += score
+
+ # add engine to list of result-engines
duplicated['engines'].append(res['engine'])
+
+ # using https if possible
if duplicated['parsed_url'].scheme == 'https':
continue
elif res['parsed_url'].scheme == 'https':
duplicated['url'] = res['parsed_url'].geturl()
duplicated['parsed_url'] = res['parsed_url']
+
+ # if there is no duplicate found, append result
else:
res['score'] = score
results.append(res)
- return sorted(results, key=itemgetter('score'), reverse=True)
+
+ results = sorted(results, key=itemgetter('score'), reverse=True)
+
+ # pass 2 : group results by category and template
+ gresults = []
+ categoryPositions = {}
+
+ for i, res in enumerate(results):
+ # FIXME : handle more than one category per engine
+ category = engines[res['engine']].categories[0] + ':' + '' if 'template' not in res else res['template']
+
+ current = None if category not in categoryPositions else categoryPositions[category]
+
+ # group with previous results using the same category if the group can accept more result and is not too far from the current position
+ if current != None and (current['count'] > 0) and (len(gresults) - current['index'] < 20):
+ # group with the previous results using the same category with this one
+ index = current['index']
+ gresults.insert(index, res)
+
+ # update every index after the current one (including the current one)
+ for k in categoryPositions:
+ v = categoryPositions[k]['index']
+ if v >= index:
+ categoryPositions[k]['index'] = v+1
+
+ # update this category
+ current['count'] -= 1
+
+ else:
+ # same category
+ gresults.append(res)
+
+ # update categoryIndex
+ categoryPositions[category] = { 'index' : len(gresults), 'count' : 8 }
+
+ # return gresults
+ return gresults
+
+
+def merge_two_infoboxes(infobox1, infobox2):
+ if 'urls' in infobox2:
+ urls1 = infobox1.get('urls', None)
+ if urls1 == None:
+ urls1 = []
+ infobox1.set('urls', urls1)
+
+ urlSet = set()
+ for url in infobox1.get('urls', []):
+ urlSet.add(url.get('url', None))
+
+ for url in infobox2.get('urls', []):
+ if url.get('url', None) not in urlSet:
+ urls1.append(url)
+
+ if 'attributes' in infobox2:
+ attributes1 = infobox1.get('attributes', None)
+ if attributes1 == None:
+ attributes1 = []
+ infobox1.set('attributes', attributes1)
+
+ attributeSet = set()
+ for attribute in infobox1.get('attributes', []):
+ if attribute.get('label', None) not in attributeSet:
+ attributeSet.add(attribute.get('label', None))
+
+ for attribute in infobox2.get('attributes', []):
+ attributes1.append(attribute)
+
+ if 'content' in infobox2:
+ content1 = infobox1.get('content', None)
+ content2 = infobox2.get('content', '')
+ if content1 != None:
+ if content_result_len(content2) > content_result_len(content1):
+ infobox1['content'] = content2
+ else:
+ infobox1.set('content', content2)
+
+
+def merge_infoboxes(infoboxes):
+ results = []
+ infoboxes_id = {}
+ for infobox in infoboxes:
+ add_infobox = True
+ infobox_id = infobox.get('id', None)
+ if infobox_id != None:
+ existingIndex = infoboxes_id.get(infobox_id, None)
+ if existingIndex != None:
+ merge_two_infoboxes(results[existingIndex], infobox)
+ add_infobox=False
+
+ if add_infobox:
+ results.append(infobox)
+ infoboxes_id[infobox_id] = len(results)-1
+
+ return results
class Search(object):
@@ -98,6 +275,7 @@ class Search(object):
"""Search information container"""
def __init__(self, request):
+ # init vars
super(Search, self).__init__()
self.query = None
self.engines = []
@@ -105,18 +283,25 @@ class Search(object):
self.paging = False
self.pageno = 1
self.lang = 'all'
+
+ # set blocked engines
if request.cookies.get('blocked_engines'):
self.blocked_engines = request.cookies['blocked_engines'].split(',') # noqa
else:
self.blocked_engines = []
+
self.results = []
self.suggestions = []
+ self.answers = []
+ self.infoboxes = []
self.request_data = {}
+ # set specific language if set
if request.cookies.get('language')\
and request.cookies['language'] in (x[0] for x in language_codes):
self.lang = request.cookies['language']
+ # set request method
if request.method == 'POST':
self.request_data = request.form
else:
@@ -126,109 +311,108 @@ class Search(object):
if not self.request_data.get('q'):
raise Exception('noquery')
- self.query = self.request_data['q']
-
+ # set pagenumber
pageno_param = self.request_data.get('pageno', '1')
if not pageno_param.isdigit() or int(pageno_param) < 1:
raise Exception('wrong pagenumber')
self.pageno = int(pageno_param)
- self.parse_query()
+ # parse query, if tags are set, which change the serch engine or search-language
+ query_obj = Query(self.request_data['q'], self.blocked_engines)
+ query_obj.parse_query()
+
+ # set query
+ self.query = query_obj.getSearchQuery()
+
+ # get last selected language in query, if possible
+ # TODO support search with multible languages
+ if len(query_obj.languages):
+ self.lang = query_obj.languages[-1]
+
+ self.engines = query_obj.engines
self.categories = []
+ # if engines are calculated from query, set categories by using that informations
if self.engines:
self.categories = list(set(engine['category']
for engine in self.engines))
+
+ # otherwise, using defined categories to calculate which engines should be used
else:
+ # set used categories
for pd_name, pd in self.request_data.items():
if pd_name.startswith('category_'):
category = pd_name[9:]
+ # if category is not found in list, skip
if not category in categories:
continue
+
+ # add category to list
self.categories.append(category)
+
+ # if no category is specified for this search, using user-defined default-configuration which (is stored in cookie)
if not self.categories:
cookie_categories = request.cookies.get('categories', '')
cookie_categories = cookie_categories.split(',')
for ccateg in cookie_categories:
if ccateg in categories:
self.categories.append(ccateg)
+
+ # if still no category is specified, using general as default-category
if not self.categories:
self.categories = ['general']
+ # using all engines for that search, which are declared under the specific categories
for categ in self.categories:
self.engines.extend({'category': categ,
'name': x.name}
for x in categories[categ]
if not x.name in self.blocked_engines)
- def parse_query(self):
- query_parts = self.query.split()
- modified = False
- if query_parts[0].startswith(':'):
- lang = query_parts[0][1:].lower()
-
- for lc in language_codes:
- lang_id, lang_name, country = map(str.lower, lc)
- if lang == lang_id\
- or lang_id.startswith(lang)\
- or lang == lang_name\
- or lang == country:
- self.lang = lang
- modified = True
- break
-
- elif query_parts[0].startswith('!'):
- prefix = query_parts[0][1:].replace('_', ' ')
-
- if prefix in engine_shortcuts\
- and not engine_shortcuts[prefix] in self.blocked_engines:
- modified = True
- self.engines.append({'category': 'none',
- 'name': engine_shortcuts[prefix]})
- elif prefix in engines\
- and not prefix in self.blocked_engines:
- modified = True
- self.engines.append({'category': 'none',
- 'name': prefix})
- elif prefix in categories:
- modified = True
- self.engines.extend({'category': prefix,
- 'name': engine.name}
- for engine in categories[prefix]
- if not engine in self.blocked_engines)
- if modified:
- self.query = self.query.replace(query_parts[0], '', 1).strip()
- self.parse_query()
-
+ # do search-request
def search(self, request):
global number_of_searches
+
+ # init vars
requests = []
results = {}
suggestions = set()
+ answers = set()
+ infoboxes = []
+
+ # increase number of searches
number_of_searches += 1
+
+ # set default useragent
#user_agent = request.headers.get('User-Agent', '')
user_agent = gen_useragent()
+ # start search-reqest for all selected engines
for selected_engine in self.engines:
if selected_engine['name'] not in engines:
continue
engine = engines[selected_engine['name']]
+ # if paging is not supported, skip
if self.pageno > 1 and not engine.paging:
continue
+ # if search-language is set and engine does not provide language-support, skip
if self.lang != 'all' and not engine.language_support:
continue
+ # set default request parameters
request_params = default_request_params()
request_params['headers']['User-Agent'] = user_agent
request_params['category'] = selected_engine['category']
request_params['started'] = datetime.now()
request_params['pageno'] = self.pageno
request_params['language'] = self.lang
+
+ # update request parameters dependent on search-engine (contained in engines folder)
request_params = engine.request(self.query.encode('utf-8'),
request_params)
@@ -236,14 +420,18 @@ class Search(object):
# TODO add support of offline engines
pass
+ # create a callback wrapper for the search engine results
callback = make_callback(
selected_engine['name'],
results,
suggestions,
+ answers,
+ infoboxes,
engine.response,
request_params
)
+ # create dictionary which contain all informations about the request
request_args = dict(
headers=request_params['headers'],
hooks=dict(response=callback),
@@ -251,6 +439,7 @@ class Search(object):
timeout=engine.timeout
)
+ # specific type of request (GET or POST)
if request_params['method'] == 'GET':
req = grequests.get
else:
@@ -261,17 +450,28 @@ class Search(object):
if not request_params['url']:
continue
+ # append request to list
requests.append(req(request_params['url'], **request_args))
+
+ # send all search-request
grequests.map(requests)
+
+ # update engine-specific stats
for engine_name, engine_results in results.items():
engines[engine_name].stats['search_count'] += 1
engines[engine_name].stats['result_count'] += len(engine_results)
+ # score results and remove duplications
results = score_results(results)
+ # merge infoboxes according to their ids
+ infoboxes = merge_infoboxes(infoboxes)
+
+ # update engine stats, using calculated score
for result in results:
for res_engine in result['engines']:
engines[result['engine']]\
.stats['score_count'] += result['score']
- return results, suggestions
+ # return results, suggestions, answers and infoboxes
+ return results, suggestions, answers, infoboxes
diff --git a/searx/settings.yml b/searx/settings.yml
index 0277b7915..8586b5069 100644
--- a/searx/settings.yml
+++ b/searx/settings.yml
@@ -1,25 +1,30 @@
server:
port : 8888
secret_key : "ultrasecretkey" # change this!
- debug : False
+ debug : False # Debug mode, only for development
request_timeout : 2.0 # seconds
- base_url : False
- themes_path : ""
- default_theme : default
- https_rewrite : True
+ base_url : False # Set custom base_url. Possible values: False or "https://your.custom.host/location/"
+ themes_path : "" # Custom ui themes path
+ default_theme : default # ui theme
+ https_rewrite : True # Force rewrite result urls. See searx/https_rewrite.py
engines:
- name : wikipedia
- engine : wikipedia
- number_of_results : 1
- paging : False
+ engine : mediawiki
shortcut : wp
+ base_url : 'https://{language}.wikipedia.org/'
+ number_of_results : 1
- name : bing
engine : bing
locale : en-US
shortcut : bi
+ - name : bing images
+ engine : bing_images
+ locale : en-US
+ shortcut : bii
+
- name : bing news
engine : bing_news
locale : en-US
@@ -32,7 +37,6 @@ engines:
- name : deviantart
engine : deviantart
- categories : images
shortcut : da
timeout: 3.0
@@ -40,9 +44,12 @@ engines:
engine : duckduckgo_definitions
shortcut : ddd
+ - name : wikidata
+ engine : wikidata
+ shortcut : wd
+
- name : duckduckgo
engine : duckduckgo
- locale : en-us
shortcut : ddg
# down - website is under criminal investigation by the UK
@@ -59,12 +66,10 @@ engines:
- name : general-file
engine : generalfile
- categories : files
shortcut : gf
- name : github
engine : github
- categories : it
shortcut : gh
- name : google
@@ -79,25 +84,24 @@ engines:
engine : google_news
shortcut : gon
+ - name : openstreetmap
+ engine : openstreetmap
+ shortcut : osm
+
- name : piratebay
engine : piratebay
- categories : videos, music, files
shortcut : tpb
- name : soundcloud
engine : soundcloud
- categories : music
shortcut : sc
- name : stackoverflow
engine : stackoverflow
- categories : it
shortcut : st
- name : startpage
engine : startpage
- base_url : 'https://startpage.com/'
- search_url : 'https://startpage.com/do/search'
shortcut : sp
# +30% page load time
@@ -108,15 +112,14 @@ engines:
- name : twitter
engine : twitter
- categories : social media
shortcut : tw
# maybe in a fun category
# - name : uncyclopedia
# engine : mediawiki
-# categories : general
# shortcut : unc
-# url : https://uncyclopedia.wikia.com/
+# base_url : https://uncyclopedia.wikia.com/
+# number_of_results : 5
# tmp suspended - too slow, too many errors
# - name : urbandictionary
@@ -137,24 +140,24 @@ engines:
- name : youtube
engine : youtube
- categories : videos
shortcut : yt
- name : dailymotion
engine : dailymotion
- locale : en_US
- categories : videos
shortcut : dm
- name : vimeo
engine : vimeo
- categories : videos
- results_xpath : //div[@id="browse_content"]/ol/li
- url_xpath : ./a/@href
- title_xpath : ./a/div[@class="data"]/p[@class="title"]/text()
- content_xpath : ./a/img/@src
+ locale : en-US
shortcut : vm
+# - name : yacy
+# engine : yacy
+# shortcut : ya
+# base_url : 'http://localhost:8090'
+# number_of_results : 5
+# timeout: 3.0
+
locales:
en : English
de : Deutsch
@@ -163,3 +166,4 @@ locales:
es : Español
it : Italiano
nl : Nederlands
+ ja : 日本語 (Japanese)
diff --git a/searx/static/courgette/css/style.css b/searx/static/courgette/css/style.css
index c54d5cbb8..6c5c99053 100644
--- a/searx/static/courgette/css/style.css
+++ b/searx/static/courgette/css/style.css
@@ -53,10 +53,12 @@ html {
text-align: center;
background: rgba(255,255,255,0.6);
padding: 4em 2em;
- position: absolute;
+ margin: 7% auto 0;
+ position: relative;
+ /*position: absolute;
top: 50%;
left: 50%;
- margin:-220px 0 0 -408px;
+ margin:-220px 0 0 -408px;*/
}
.center.search {
@@ -67,6 +69,21 @@ html {
padding-top: 1.8em;
}
+.autocompleter-choices {
+ position: absolute;
+ margin: 0;
+ padding: 0;
+ background: #FFF;
+}
+ .autocompleter-choices li {
+ padding: 0.5em 1em;
+ }
+ .autocompleter-choices li:hover {
+ background: #3498DB;
+ color: #FFF;
+ cursor: pointer;
+ }
+
#categories {
text-align: center;
}
diff --git a/searx/static/default/css/style.css b/searx/static/default/css/style.css
index 43f8f8383..70265b072 100644
--- a/searx/static/default/css/style.css
+++ b/searx/static/default/css/style.css
@@ -38,9 +38,11 @@ a{text-decoration:none;color:#1a11be}a:visited{color:#8e44ad}
.result{margin:19px 0 18px 0;padding:0;clear:both}
.result_title{margin-bottom:0}.result_title a{color:#2980b9;font-weight:normal;font-size:1.1em}.result_title a:hover{text-decoration:underline}
.result_title a:visited{color:#8e44ad}
+.cache_link{font-size:10px !important}
.result h3{font-size:1em;word-wrap:break-word;margin:5px 0 1px 0;padding:0}
-.result .content{font-size:.8em;margin:0;padding:0;max-width:54em;word-wrap:break-word;line-height:1.24}
-.result .url{font-size:.8em;margin:3px 0 0 0;padding:0;max-width:54em;word-wrap:break-word;color:#c0392b}
+.result .content{font-size:.8em;margin:0;padding:0;max-width:54em;word-wrap:break-word;line-height:1.24}.result .content img{float:left;margin-right:5px;max-width:200px;max-height:100px}
+.result .content br.last{clear:both}
+.result .url{font-size:.8em;margin:0 0 3px 0;padding:0;max-width:54em;word-wrap:break-word;color:#c0392b}
.result .published_date{font-size:.8em;color:#888;margin:5px 20px}
.engines{color:#888}
.small_font{font-size:.8em}
@@ -60,15 +62,20 @@ table{width:100%}
td{padding:0 4px}
tr:hover{background:#ddd}
#results{margin:auto;padding:0;width:50em;margin-bottom:20px}
-#sidebar{position:absolute;top:100px;right:10px;margin:0 2px 5px 5px;padding:0 2px 2px 2px;width:14em}#sidebar input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}
+#sidebar{position:fixed;bottom:10px;left:10px;margin:0 2px 5px 5px;padding:0 2px 2px 2px;width:14em}#sidebar input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}
#sidebar input[type="submit"]{text-decoration:underline}
-#suggestions{margin-top:20px}#suggestions span{display:inline;margin:0 2px 2px 2px;padding:0}
-#suggestions input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}
-#suggestions input[type="submit"]{text-decoration:underline}
-#suggestions form{display:inline}
+#suggestions,#answers{margin-top:20px}
+#suggestions input,#answers input,#infoboxes input{padding:0;margin:3px;font-size:.8em;display:inline-block;background:transparent;color:#444;cursor:pointer}
+#suggestions input[type="submit"],#answers input[type="submit"],#infoboxes input[type="submit"]{text-decoration:underline}
+#suggestions form,#answers form,#infoboxes form{display:inline}
+#infoboxes{position:absolute;top:100px;right:20px;margin:0 2px 5px 5px;padding:0 2px 2px;max-width:21em}#infoboxes .infobox{margin:10px 0 10px;border:1px solid #ddd;padding:5px;font-size:.8em}#infoboxes .infobox img{max-width:20em;max-heigt:12em;display:block;margin:5px;padding:5px}
+#infoboxes .infobox h2{margin:0}
+#infoboxes .infobox table{width:auto}#infoboxes .infobox table td{vertical-align:top}
+#infoboxes .infobox input{font-size:1em}
+#infoboxes .infobox br{clear:both}
#search_url{margin-top:8px}#search_url input{border:1px solid #888;padding:4px;color:#444;width:14em;display:block;margin:4px;font-size:.8em}
#preferences{top:10px;padding:0;border:0;background:url('../img/preference-icon.png') no-repeat;background-size:28px 28px;opacity:.8;width:28px;height:30px;display:block}#preferences *{display:none}
#pagination{clear:both;width:40em}
#apis{margin-top:8px;clear:both}
-@media screen and (max-width:50em){#categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto} #results{margin:auto;padding:0;width:90%} .github{display:none} .checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0}}@media screen and (max-width:70em){.right{display:none;postion:fixed !important;top:100px;right:0} #sidebar{position:static;max-width:50em;margin:0 0 2px 0;padding:0;float:none;border:none;width:auto}#sidebar input{border:0} #apis{display:none} #search_url{display:none} .result{border-top:1px solid #e8e7e6;margin:7px 0 6px 0}.result img{max-width:90%;width:auto;height:auto}}.favicon{float:left;margin-right:4px;margin-top:2px}
+@media screen and (max-width:50em){#results{margin:auto;padding:0;width:90%} .github{display:none} .checkbox_container{display:block;width:90%}.checkbox_container label{border-bottom:0} .right{display:none;postion:fixed !important;top:100px;right:0}}@media screen and (max-width:75em){#infoboxes{position:inherit;max-width:inherit}#infoboxes .infobox{clear:both}#infoboxes .infobox img{float:left;max-width:10em} #categories{font-size:90%;clear:both}#categories .checkbox_container{margin-top:2px;margin:auto} #sidebar{position:static;max-width:50em;margin:0 0 2px 0;padding:0;float:none;border:none;width:auto}#sidebar input{border:0} #apis{display:none} #search_url{display:none} .result{border-top:1px solid #e8e7e6;margin:7px 0 6px 0}}.favicon{float:left;margin-right:4px;margin-top:2px}
.preferences_back{background:none repeat scroll 0 0 #3498db;border:0 none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;cursor:pointer;display:inline-block;margin:2px 4px;padding:4px 6px}.preferences_back a{color:#fff}
diff --git a/searx/static/default/less/style.less b/searx/static/default/less/style.less
index aa30c8ccb..c43c0fe72 100644
--- a/searx/static/default/less/style.less
+++ b/searx/static/default/less/style.less
@@ -216,6 +216,10 @@ a {
}
}
+.cache_link {
+ font-size: 10px !important;
+}
+
.result {
h3 {
font-size: 1em;
@@ -231,11 +235,22 @@ a {
max-width: 54em;
word-wrap:break-word;
line-height: 1.24;
+
+ img {
+ float: left;
+ margin-right: 5px;
+ max-width: 200px;
+ max-height: 100px;
+ }
+
+ br.last {
+ clear: both;
+ }
}
.url {
font-size: 0.8em;
- margin: 3px 0 0 0;
+ margin: 0 0 3px 0;
padding: 0;
max-width: 54em;
word-wrap:break-word;
@@ -359,9 +374,9 @@ tr {
}
#sidebar {
- position: absolute;
- top: 100px;
- right: 10px;
+ position: fixed;
+ bottom: 10px;
+ left: 10px;
margin: 0 2px 5px 5px;
padding: 0 2px 2px 2px;
width: 14em;
@@ -380,33 +395,80 @@ tr {
}
}
-#suggestions {
+#suggestions, #answers {
- margin-top: 20px;
+ margin-top: 20px;
+
+}
+
+#suggestions, #answers, #infoboxes {
- span {
- display: inline;
- margin: 0 2px 2px 2px;
- padding: 0;
- }
input {
padding: 0;
margin: 3px;
font-size: 0.8em;
display: inline-block;
- background: transparent;
- color: @color-result-search-url-font;
+ background: transparent;
+ color: @color-result-search-url-font;
cursor: pointer;
}
- input[type="submit"] {
+
+ input[type="submit"] {
text-decoration: underline;
- }
+ }
form {
display: inline;
}
}
+
+#infoboxes {
+ position: absolute;
+ top: 100px;
+ right: 20px;
+ margin: 0px 2px 5px 5px;
+ padding: 0px 2px 2px;
+ max-width: 21em;
+
+ .infobox {
+ margin: 10px 0 10px;
+ border: 1px solid #ddd;
+ padding: 5px;
+ font-size: 0.8em;
+
+ img {
+ max-width: 20em;
+ max-heigt: 12em;
+ display: block;
+ margin: 5px;
+ padding: 5px;
+ }
+
+ h2 {
+ margin: 0;
+ }
+
+ table {
+ width: auto;
+
+ td {
+ vertical-align: top;
+ }
+
+ }
+
+ input {
+ font-size: 1em;
+ }
+
+ br {
+ clear: both;
+ }
+
+ }
+}
+
#search_url {
margin-top: 8px;
@@ -449,16 +511,6 @@ tr {
@media screen and (max-width: @results-width) {
- #categories {
- font-size: 90%;
- clear: both;
-
- .checkbox_container {
- margin-top: 2px;
- margin: auto;
- }
- }
-
#results {
margin: auto;
padding: 0;
@@ -477,9 +529,7 @@ tr {
border-bottom: 0;
}
}
-}
-@media screen and (max-width: 70em) {
.right {
display: none;
postion: fixed !important;
@@ -487,6 +537,35 @@ tr {
right: 0px;
}
+}
+
+@media screen and (max-width: 75em) {
+
+ #infoboxes {
+ position: inherit;
+ max-width: inherit;
+
+ .infobox {
+ clear:both;
+
+ img {
+ float: left;
+ max-width: 10em;
+ }
+ }
+
+ }
+
+ #categories {
+ font-size: 90%;
+ clear: both;
+
+ .checkbox_container {
+ margin-top: 2px;
+ margin: auto;
+ }
+ }
+
#sidebar {
position: static;
max-width: @results-width;
@@ -511,12 +590,6 @@ tr {
.result {
border-top: 1px solid @color-result-top-border;
margin: 7px 0 6px 0;
-
- img {
- max-width: 90%;
- width: auto;
- height: auto
- }
}
}
diff --git a/searx/templates/default/infobox.html b/searx/templates/default/infobox.html
new file mode 100644
index 000000000..f963e898c
--- /dev/null
+++ b/searx/templates/default/infobox.html
@@ -0,0 +1,44 @@
+<div class="infobox">
+ <h2>{{ infobox.infobox }}</h2>
+ {% if infobox.img_src %}<img src="{{ infobox.img_src }}" />{% endif %}
+ <p>{{ infobox.entity }}</p>
+ <p>{{ infobox.content }}</p>
+ {% if infobox.attributes %}
+ <div class="attributes">
+ <table>
+ {% for attribute in infobox.attributes %}
+ <tr><td>{{ attribute.label }}</td><td>{{ attribute.value }}</td></tr>
+ {% endfor %}
+ </table>
+ </div>
+ {% endif %}
+
+ {% if infobox.urls %}
+ <div class="urls">
+ <ul>
+ {% for url in infobox.urls %}
+ <li class="url"><a href="{{ url.url }}">{{ url.title }}</a></li>
+ {% endfor %}
+ </ul>
+ </div>
+ {% endif %}
+
+ {% if infobox.relatedTopics %}
+ <div class="relatedTopics">
+ {% for topic in infobox.relatedTopics %}
+ <div>
+ <h3>{{ topic.name }}</h3>
+ {% for suggestion in topic.suggestions %}
+ <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}">
+ <input type="hidden" name="q" value="{{ suggestion }}">
+ <input type="submit" value="{{ suggestion }}" />
+ </form>
+ {% endfor %}
+ </div>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ <br />
+
+</div>
diff --git a/searx/templates/default/result_templates/default.html b/searx/templates/default/result_templates/default.html
index 734f9066c..78221aa01 100644
--- a/searx/templates/default/result_templates/default.html
+++ b/searx/templates/default/result_templates/default.html
@@ -6,8 +6,8 @@
<div>
<h3 class="result_title"><a href="{{ result.url }}">{{ result.title|safe }}</a></h3>
+ <p class="url">{{ result.pretty_url }} <a class="cache_link" href="https://web.archive.org/web/{{ result.url }}">cached</a></p>
{% if result.publishedDate %}<p class="published_date">{{ result.publishedDate }}</p>{% endif %}
- <p class="content">{% if result.content %}{{ result.content|safe }}<br />{% endif %}</p>
- <p class="url">{{ result.pretty_url }}</p>
+ <p class="content">{% if result.img_src %}<img src="{{ result.img_src }}" class="image" />{% endif %}{% if result.content %}{{ result.content|safe }}<br class="last"/>{% endif %}</p>
</div>
</div>
diff --git a/searx/templates/default/results.html b/searx/templates/default/results.html
index d0b53b48a..b66d6e2af 100644
--- a/searx/templates/default/results.html
+++ b/searx/templates/default/results.html
@@ -30,6 +30,14 @@
</div>
</div>
+ {% if answers %}
+ <div id="answers"><span>{{ _('Answers') }}</span>
+ {% for answer in answers %}
+ <span>{{ answer }}</span>
+ {% endfor %}
+ </div>
+ {% endif %}
+
{% if suggestions %}
<div id="suggestions"><span>{{ _('Suggestions') }}</span>
{% for suggestion in suggestions %}
@@ -41,6 +49,14 @@
</div>
{% endif %}
+ {% if infoboxes %}
+ <div id="infoboxes">
+ {% for infobox in infoboxes %}
+ {% include 'default/infobox.html' %}
+ {% endfor %}
+ </div>
+ {% endif %}
+
{% for result in results %}
{% if result['template'] %}
{% include 'default/result_templates/'+result['template'] %}
diff --git a/searx/tests/test_webapp.py b/searx/tests/test_webapp.py
index 9d1722eeb..9cf586180 100644
--- a/searx/tests/test_webapp.py
+++ b/searx/tests/test_webapp.py
@@ -43,6 +43,8 @@ class ViewsTestCase(SearxTestCase):
def test_index_html(self, search):
search.return_value = (
self.test_results,
+ set(),
+ set(),
set()
)
result = self.app.post('/', data={'q': 'test'})
@@ -51,7 +53,7 @@ class ViewsTestCase(SearxTestCase):
result.data
)
self.assertIn(
- '<p class="content">first <span class="highlight">test</span> content<br /></p>', # noqa
+ '<p class="content">first <span class="highlight">test</span> content<br class="last"/></p>', # noqa
result.data
)
@@ -59,6 +61,8 @@ class ViewsTestCase(SearxTestCase):
def test_index_json(self, search):
search.return_value = (
self.test_results,
+ set(),
+ set(),
set()
)
result = self.app.post('/', data={'q': 'test', 'format': 'json'})
@@ -75,6 +79,8 @@ class ViewsTestCase(SearxTestCase):
def test_index_csv(self, search):
search.return_value = (
self.test_results,
+ set(),
+ set(),
set()
)
result = self.app.post('/', data={'q': 'test', 'format': 'csv'})
@@ -90,6 +96,8 @@ class ViewsTestCase(SearxTestCase):
def test_index_rss(self, search):
search.return_value = (
self.test_results,
+ set(),
+ set(),
set()
)
result = self.app.post('/', data={'q': 'test', 'format': 'rss'})
diff --git a/searx/translations/de/LC_MESSAGES/messages.mo b/searx/translations/de/LC_MESSAGES/messages.mo
index dc2922786..553d3d9d0 100644
--- a/searx/translations/de/LC_MESSAGES/messages.mo
+++ b/searx/translations/de/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/de/LC_MESSAGES/messages.po b/searx/translations/de/LC_MESSAGES/messages.po
index bd4f44d0b..c28759470 100644
--- a/searx/translations/de/LC_MESSAGES/messages.po
+++ b/searx/translations/de/LC_MESSAGES/messages.po
@@ -1,152 +1,188 @@
-# English translations for .
+# English translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
-# This file is distributed under the same license as the project.
-#
+# This file is distributed under the same license as the PROJECT project.
+#
# Translators:
# pointhi, 2014
# stf <stefan.marsiske@gmail.com>, 2014
+# rike, 2014
msgid ""
msgstr ""
-"Project-Id-Version: searx\n"
+"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
-"PO-Revision-Date: 2014-03-15 18:40+0000\n"
+"POT-Creation-Date: 2014-09-07 21:47+0200\n"
+"PO-Revision-Date: 2014-09-08 07:00+0000\n"
"Last-Translator: pointhi\n"
-"Language-Team: German "
-"(http://www.transifex.com/projects/p/searx/language/de/)\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Language-Team: German (http://www.transifex.com/projects/p/searx/language/de/)\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
+"Language: de\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:250
msgid "{minutes} minute(s) ago"
-msgstr ""
+msgstr "vor {minutes} Minute(n)"
-#: searx/webapp.py:169
+#: searx/webapp.py:252
msgid "{hours} hour(s), {minutes} minute(s) ago"
-msgstr ""
+msgstr "vor {hours} Stunde(n), {minutes} Minute(n)"
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:164
msgid "Page loads (sec)"
msgstr "Ladezeit (sek)"
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:168
msgid "Number of results"
msgstr "Trefferanzahl"
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:172
msgid "Scores"
msgstr "Punkte"
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:176
msgid "Scores per result"
msgstr "Punkte pro Treffer"
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:180
msgid "Errors"
msgstr "Fehler"
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr "Über uns"
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr "Einstellungen"
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr "Einstellungen"
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
-msgstr "Standard Kategorien"
+msgstr "Standardkategorien"
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr "Suchsprache"
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr "Automatisch"
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr "Oberflächensprache"
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr "Autovervollständigung"
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr "Methode"
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr "Designs"
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
msgstr "Aktuell benutzte Suchmaschinen"
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr "Suchmaschinenname"
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr "Kategorie"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
msgid "Allow"
msgstr "Erlauben"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
msgid "Block"
msgstr "Blockieren"
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
msgid ""
-"These settings are stored in your cookies, this allows us not to store "
-"this data about you."
-msgstr ""
-"Diese Informationen werden in Cookies gespeichert, damit wir keine ihrer "
-"persönlichen Daten speichern müssen."
+"These settings are stored in your cookies, this allows us not to store this "
+"data about you."
+msgstr "Diese Informationen werden in Cookies auf Ihrem Rechner gespeichert, damit wir keine Ihrer persönlichen Daten speichern müssen."
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
-msgstr ""
-"Diese Cookies dienen ihrer Gemütlichkeit, wir verwenden sie nicht zum "
-"überwachen."
+msgstr "Diese Cookies dienen einzig Ihrem Komfort, wir verwenden sie nicht, um Sie zu überwachen."
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
msgid "save"
msgstr "Speichern"
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
msgid "back"
msgstr "Zurück"
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr "Vorschläge"
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr "Such-URL"
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr "Ergebnisse herunterladen"
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr "Vorschläge"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr "vorherige Seite"
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr "nächste Seite"
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
msgstr "Suche nach..."
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
-msgstr "Suchmaschienen Statistiken"
+msgstr "Suchmaschinenstatistik"
# categories - manually added
# TODO - automatically add
@@ -174,3 +210,5 @@ msgstr "IT"
msgid "news"
msgstr "Neuigkeiten"
+msgid "map"
+msgstr "Karte"
diff --git a/searx/translations/en/LC_MESSAGES/messages.mo b/searx/translations/en/LC_MESSAGES/messages.mo
index 4f81aa4ba..13936f4fb 100644
--- a/searx/translations/en/LC_MESSAGES/messages.mo
+++ b/searx/translations/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/en/LC_MESSAGES/messages.po b/searx/translations/en/LC_MESSAGES/messages.po
index 7141c4db5..7f2ef09e9 100644
--- a/searx/translations/en/LC_MESSAGES/messages.po
+++ b/searx/translations/en/LC_MESSAGES/messages.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
+"POT-Creation-Date: 2014-10-01 19:24+0200\n"
"PO-Revision-Date: 2014-01-30 15:22+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: en <LL@li.org>\n"
@@ -17,130 +17,181 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:251
msgid "{minutes} minute(s) ago"
msgstr ""
-#: searx/webapp.py:169
+#: searx/webapp.py:253
msgid "{hours} hour(s), {minutes} minute(s) ago"
msgstr ""
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:176
msgid "Page loads (sec)"
msgstr ""
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:180
msgid "Number of results"
msgstr ""
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:184
msgid "Scores"
msgstr ""
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:188
msgid "Scores per result"
msgstr ""
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:192
msgid "Errors"
msgstr ""
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr ""
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr ""
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr ""
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
msgstr ""
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr ""
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr ""
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr ""
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
msgstr ""
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr ""
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr ""
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:73
+#: searx/templates/default/preferences.html:85
msgid "Allow"
msgstr ""
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:73
+#: searx/templates/default/preferences.html:86
msgid "Block"
msgstr ""
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:94
msgid ""
"These settings are stored in your cookies, this allows us not to store "
"this data about you."
msgstr ""
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:96
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
msgstr ""
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:99
msgid "save"
msgstr ""
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:100
msgid "back"
msgstr ""
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr ""
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr ""
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr ""
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr ""
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr ""
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr ""
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
msgstr ""
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
msgstr ""
+#: searx/templates/default/preferences.html:72
+msgid "Localization"
+msgstr ""
+
+#: searx/templates/default/preferences.html:82
+msgid "Yes"
+msgstr ""
+
+#: searx/templates/default/preferences.html:82
+msgid "No"
+msgstr ""
+
# categories - manually added
# TODO - automatically add
msgid "files"
@@ -167,3 +218,6 @@ msgstr ""
msgid "news"
msgstr ""
+msgid "map"
+msgstr ""
+
diff --git a/searx/translations/es/LC_MESSAGES/messages.mo b/searx/translations/es/LC_MESSAGES/messages.mo
index 69c0fdfd8..2d0b16527 100644
--- a/searx/translations/es/LC_MESSAGES/messages.mo
+++ b/searx/translations/es/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/es/LC_MESSAGES/messages.po b/searx/translations/es/LC_MESSAGES/messages.po
index bd9ecab46..f2a900953 100644
--- a/searx/translations/es/LC_MESSAGES/messages.po
+++ b/searx/translations/es/LC_MESSAGES/messages.po
@@ -1,149 +1,184 @@
-# English translations for .
+# English translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
-# This file is distributed under the same license as the project.
-#
+# This file is distributed under the same license as the PROJECT project.
+#
# Translators:
-# niazle, 2014
+# Alejandro León Aznar, 2014
msgid ""
msgstr ""
-"Project-Id-Version: searx\n"
+"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
-"PO-Revision-Date: 2014-03-04 20:40+0000\n"
-"Last-Translator: niazle\n"
-"Language-Team: Spanish "
-"(http://www.transifex.com/projects/p/searx/language/es/)\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"POT-Creation-Date: 2014-09-07 21:47+0200\n"
+"PO-Revision-Date: 2014-09-08 11:01+0000\n"
+"Last-Translator: Alejandro León Aznar\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/searx/language/es/)\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
+"Language: es\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:250
msgid "{minutes} minute(s) ago"
-msgstr ""
+msgstr "hace {minutes} minuto(s)"
-#: searx/webapp.py:169
+#: searx/webapp.py:252
msgid "{hours} hour(s), {minutes} minute(s) ago"
-msgstr ""
+msgstr "hace {hours} hora(s) y {minutes} minuto(s)"
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:164
msgid "Page loads (sec)"
msgstr "Tiempo de carga (segundos)"
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:168
msgid "Number of results"
msgstr "Número de resultados"
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:172
msgid "Scores"
msgstr "Puntuaciones"
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:176
msgid "Scores per result"
msgstr "Puntuaciones por resultado"
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:180
msgid "Errors"
msgstr "Errores"
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr "acerca de"
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr "preferencias"
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr "Preferencias"
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
msgstr "Categorías predeterminadas"
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr "Buscar idioma"
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr "Automático"
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr "Idioma de la interfaz"
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr "Autocompletar"
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr "Método"
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr "Temas"
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
msgstr "Motores de búsqueda actualmente en uso"
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr "Nombre del motor de búsqueda"
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr "Categoría"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
msgid "Allow"
msgstr "Permitir"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
msgid "Block"
msgstr "Bloquear"
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
msgid ""
-"These settings are stored in your cookies, this allows us not to store "
-"this data about you."
-msgstr ""
-"Esta configuración se guarda en sus cookies, lo que nos permite no "
-"almacenar dicha información sobre usted."
+"These settings are stored in your cookies, this allows us not to store this "
+"data about you."
+msgstr "Esta configuración se guarda en sus cookies, lo que nos permite no almacenar dicha información sobre usted."
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
-msgstr ""
-"Estas cookies son para su propia comodidad, no las utilizamos para "
-"rastrearle."
+msgstr "Estas cookies son para su propia comodidad, no las utilizamos para rastrearle."
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
msgid "save"
msgstr "Guardar"
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
msgid "back"
msgstr "Atrás"
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr "Sugerencias"
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr "Buscar URL"
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr "Descargar resultados"
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr "Sugerencias"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr "Página anterior"
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr "Página siguiente"
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
-msgstr ""
+msgstr "Buscar..."
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
msgstr "Estadísticas del motor de búsqueda"
@@ -173,3 +208,5 @@ msgstr "TIC"
msgid "news"
msgstr "noticias"
+msgid "map"
+msgstr "mapa"
diff --git a/searx/translations/fr/LC_MESSAGES/messages.mo b/searx/translations/fr/LC_MESSAGES/messages.mo
index 09022d0c9..7171b9af0 100644
--- a/searx/translations/fr/LC_MESSAGES/messages.mo
+++ b/searx/translations/fr/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/fr/LC_MESSAGES/messages.po b/searx/translations/fr/LC_MESSAGES/messages.po
index 37dc5e47f..a52f4c11a 100644
--- a/searx/translations/fr/LC_MESSAGES/messages.po
+++ b/searx/translations/fr/LC_MESSAGES/messages.po
@@ -1,151 +1,186 @@
-# English translations for .
+# English translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
-# This file is distributed under the same license as the project.
-#
+# This file is distributed under the same license as the PROJECT project.
+#
# Translators:
# Benjamin Sonntag <benjamin@sonntag.fr>, 2014
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014
-# rike <u@451f.org>, 2014
+# rike, 2014
msgid ""
msgstr ""
-"Project-Id-Version: searx\n"
+"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
-"PO-Revision-Date: 2014-03-16 07:40+0000\n"
-"Last-Translator: Benjamin Sonntag <benjamin@sonntag.fr>\n"
-"Language-Team: French "
-"(http://www.transifex.com/projects/p/searx/language/fr/)\n"
-"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+"POT-Creation-Date: 2014-09-07 21:47+0200\n"
+"PO-Revision-Date: 2014-09-07 21:24+0000\n"
+"Last-Translator: Adam Tauber <asciimoo@gmail.com>\n"
+"Language-Team: French (http://www.transifex.com/projects/p/searx/language/fr/)\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:250
msgid "{minutes} minute(s) ago"
-msgstr ""
+msgstr "il y a {minutes} minute(s)"
-#: searx/webapp.py:169
+#: searx/webapp.py:252
msgid "{hours} hour(s), {minutes} minute(s) ago"
-msgstr ""
+msgstr "il y a {hours} heure(s), {minutes} minute(s)"
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:164
msgid "Page loads (sec)"
msgstr "Chargement de la page (sec)"
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:168
msgid "Number of results"
msgstr "Nombre de résultats"
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:172
msgid "Scores"
msgstr "Score"
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:176
msgid "Scores per result"
msgstr "Score par résultat"
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:180
msgid "Errors"
msgstr "Erreurs"
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr "À propos"
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr "préférences"
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr "Préférences"
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
msgstr "Catégories par défaut"
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr "Langue de recherche"
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr "Automatique"
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr "Langue de l'interface"
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
-msgstr "Moteurs actuellement utilisés"
+msgstr "Moteurs de recherche actuellement utilisés"
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr "Nom du moteur"
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr "Catégorie"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
msgid "Allow"
msgstr "Autoriser"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
msgid "Block"
msgstr "Bloquer"
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
msgid ""
-"These settings are stored in your cookies, this allows us not to store "
-"this data about you."
-msgstr ""
-"Ces paramètres sont stockés dans vos cookies ; ceci nous permet de ne pas"
-" collecter vos données."
+"These settings are stored in your cookies, this allows us not to store this "
+"data about you."
+msgstr "Ces paramètres sont stockés dans vos cookies ; ceci nous permet de ne pas collecter vos données."
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
-msgstr ""
-"Ces cookies existent pour votre confort d'utilisation, nous ne les "
-"utilisons pas pour vous espionner."
+msgstr "Ces cookies existent pour votre confort d'utilisation, nous ne les utilisons pas pour vous espionner."
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
msgid "save"
msgstr "enregistrer"
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
msgid "back"
msgstr "retour"
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr "Suggestions"
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr "URL de recherche"
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr "Télécharger les résultats"
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr "Suggestions"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr "page précédente"
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr "page suivante"
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
msgstr "Rechercher..."
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
msgstr "Statistiques du moteur"
@@ -175,3 +210,5 @@ msgstr "Informatique"
msgid "news"
msgstr "actus"
+msgid "map"
+msgstr ""
diff --git a/searx/translations/hu/LC_MESSAGES/messages.mo b/searx/translations/hu/LC_MESSAGES/messages.mo
index 64beee1af..a60b3ee22 100644
--- a/searx/translations/hu/LC_MESSAGES/messages.mo
+++ b/searx/translations/hu/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/hu/LC_MESSAGES/messages.po b/searx/translations/hu/LC_MESSAGES/messages.po
index 52fd2ee95..c34e56db9 100644
--- a/searx/translations/hu/LC_MESSAGES/messages.po
+++ b/searx/translations/hu/LC_MESSAGES/messages.po
@@ -1,145 +1,185 @@
-# Hungarian translations for PROJECT.
+# English translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
-#
+#
+# Translators:
+# Adam Tauber <asciimoo@gmail.com>, 2014
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014
msgid ""
msgstr ""
-"Project-Id-Version: PROJECT VERSION\n"
+"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
-"PO-Revision-Date: 2014-01-21 23:33+0100\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: hu <LL@li.org>\n"
-"Plural-Forms: nplurals=1; plural=0\n"
+"POT-Creation-Date: 2014-09-07 21:47+0200\n"
+"PO-Revision-Date: 2014-09-07 21:30+0000\n"
+"Last-Translator: Adam Tauber <asciimoo@gmail.com>\n"
+"Language-Team: Hungarian (http://www.transifex.com/projects/p/searx/language/hu/)\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
+"Language: hu\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:250
msgid "{minutes} minute(s) ago"
msgstr "{minutes} perce"
-#: searx/webapp.py:169
+#: searx/webapp.py:252
msgid "{hours} hour(s), {minutes} minute(s) ago"
msgstr "{hours} óra, {minutes} perce"
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:164
msgid "Page loads (sec)"
msgstr "Válaszidők (sec)"
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:168
msgid "Number of results"
msgstr "Találatok száma"
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:172
msgid "Scores"
msgstr "Pontszámok"
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:176
msgid "Scores per result"
msgstr "Pontszámok találatonként"
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:180
msgid "Errors"
msgstr "Hibák"
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr "rólunk"
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr "beállítások"
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr "Beállítások"
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
msgstr "Alapértelmezett kategóriák"
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr "Keresés nyelve"
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr "Automatikus"
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr "Felület nyelve"
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr "Automatikus kiegészítés"
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr "Method"
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr "Megjelenés"
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
msgstr "Jelenleg használt keresők"
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr "Kereső neve"
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr "Kategória"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
msgid "Allow"
msgstr "Engedélyezés"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
msgid "Block"
msgstr "Tiltás"
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
msgid ""
-"These settings are stored in your cookies, this allows us not to store "
-"this data about you."
+"These settings are stored in your cookies, this allows us not to store this "
+"data about you."
msgstr "Ezek a beállítások csak a böngésző cookie-jaiban tárolódnak."
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
-msgstr ""
-"Ezek a cookie-k csak kényelmi funkciókat látnak el, nem használjuk a "
-"felhasználók követésére."
+msgstr "Ezek a cookie-k csak kényelmi funkciókat látnak el, nem használjuk a felhasználók követésére."
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
msgid "save"
msgstr "mentés"
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
msgid "back"
msgstr "vissza"
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr "Javaslatok"
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr "Keresési URL"
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr "Találatok letöltése"
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr "Javaslatok"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr "előző oldal"
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr "következő oldal"
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
msgstr "Keresés..."
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
msgstr "Kereső statisztikák"
@@ -169,3 +209,5 @@ msgstr "it"
msgid "news"
msgstr "hírek"
+msgid "map"
+msgstr "térkép"
diff --git a/searx/translations/it/LC_MESSAGES/messages.mo b/searx/translations/it/LC_MESSAGES/messages.mo
index ffd0dc9e5..77b5503d0 100644
--- a/searx/translations/it/LC_MESSAGES/messages.mo
+++ b/searx/translations/it/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/it/LC_MESSAGES/messages.po b/searx/translations/it/LC_MESSAGES/messages.po
index b83ef440d..4eeb15fa4 100644
--- a/searx/translations/it/LC_MESSAGES/messages.po
+++ b/searx/translations/it/LC_MESSAGES/messages.po
@@ -1,149 +1,184 @@
-# English translations for .
+# English translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
-# This file is distributed under the same license as the project.
-#
+# This file is distributed under the same license as the PROJECT project.
+#
# Translators:
# dp <d.pitrolo@gmx.com>, 2014
msgid ""
msgstr ""
-"Project-Id-Version: searx\n"
+"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
-"PO-Revision-Date: 2014-03-05 13:30+0000\n"
+"POT-Creation-Date: 2014-09-07 21:47+0200\n"
+"PO-Revision-Date: 2014-09-08 08:19+0000\n"
"Last-Translator: dp <d.pitrolo@gmx.com>\n"
-"Language-Team: Italian "
-"(http://www.transifex.com/projects/p/searx/language/it/)\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Language-Team: Italian (http://www.transifex.com/projects/p/searx/language/it/)\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:250
msgid "{minutes} minute(s) ago"
-msgstr ""
+msgstr "di {minutes} minuti fa"
-#: searx/webapp.py:169
+#: searx/webapp.py:252
msgid "{hours} hour(s), {minutes} minute(s) ago"
-msgstr ""
+msgstr "di {ore} h e {minutes} minuti fa"
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:164
msgid "Page loads (sec)"
msgstr " Caricamento della pagina (secondi)"
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:168
msgid "Number of results"
msgstr "Risultati ottenuti"
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:172
msgid "Scores"
msgstr "Punteggio"
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:176
msgid "Scores per result"
msgstr "Punteggio per risultato"
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:180
msgid "Errors"
msgstr "Errori"
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr "informazioni"
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr "preferenze"
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr "Preferenze"
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
msgstr "Categorie predefinite"
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr "Lingua di ricerca"
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr "Automatico"
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr "Linguaggio dell'interfaccia"
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr "Completamento automatico"
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr "Metodo"
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr "Grafica"
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
msgstr "Motori di ricerca attualmente in uso"
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr "Nome del motore"
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr "Categoria"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
msgid "Allow"
msgstr "Autorizza"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
msgid "Block"
msgstr "Blocca"
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
msgid ""
-"These settings are stored in your cookies, this allows us not to store "
-"this data about you."
-msgstr ""
-"Queste impostazioni sono salvate nei tuoi cookie, consentendoci di non "
-"conservare dati su di te."
+"These settings are stored in your cookies, this allows us not to store this "
+"data about you."
+msgstr "Queste impostazioni sono salvate nei tuoi cookie, consentendoci di non conservare dati su di te."
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
-msgstr ""
-"I cookie sono funzionali ad un servizio migliore. Non usiamo i cookie per"
-" sorvegliarti."
+msgstr "I cookie sono funzionali ad un servizio migliore. Non usiamo i cookie per sorvegliarti."
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
msgid "save"
msgstr "salva"
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
msgid "back"
msgstr "indietro"
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr "Suggerimenti"
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr "URL della ricerca"
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr "Scarica i risultati"
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr "Suggerimenti"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr "pagina precedente"
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr "pagina successiva"
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
-msgstr ""
+msgstr "Cerca…"
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
msgstr "Statistiche dei motori"
@@ -173,3 +208,5 @@ msgstr "it"
msgid "news"
msgstr "notizie"
+msgid "map"
+msgstr "mappe"
diff --git a/searx/translations/ja/LC_MESSAGES/messages.mo b/searx/translations/ja/LC_MESSAGES/messages.mo
new file mode 100644
index 000000000..d91b5b1da
--- /dev/null
+++ b/searx/translations/ja/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/ja/LC_MESSAGES/messages.po b/searx/translations/ja/LC_MESSAGES/messages.po
new file mode 100644
index 000000000..510a77eae
--- /dev/null
+++ b/searx/translations/ja/LC_MESSAGES/messages.po
@@ -0,0 +1,215 @@
+# Japanese translations for PROJECT.
+# Copyright (C) 2014 ORGANIZATION
+# This file is distributed under the same license as the PROJECT project.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PROJECT VERSION\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2014-10-05 16:45+0200\n"
+"PO-Revision-Date: 2014-10-05 16:38+0200\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: ja <LL@li.org>\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: Babel 1.3\n"
+
+#: searx/webapp.py:251
+msgid "{minutes} minute(s) ago"
+msgstr ""
+
+#: searx/webapp.py:253
+msgid "{hours} hour(s), {minutes} minute(s) ago"
+msgstr ""
+
+#: searx/engines/__init__.py:176
+msgid "Page loads (sec)"
+msgstr ""
+
+#: searx/engines/__init__.py:180
+msgid "Number of results"
+msgstr ""
+
+#: searx/engines/__init__.py:184
+msgid "Scores"
+msgstr ""
+
+#: searx/engines/__init__.py:188
+msgid "Scores per result"
+msgstr ""
+
+#: searx/engines/__init__.py:192
+msgid "Errors"
+msgstr ""
+
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
+msgid "about"
+msgstr "に関する"
+
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
+msgid "preferences"
+msgstr "設定"
+
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
+msgid "Preferences"
+msgstr "設定"
+
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
+msgid "Default categories"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
+msgid "Search language"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
+msgid "Automatic"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
+msgid "Interface language"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
+msgid "Currently used search engines"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
+msgid "Engine name"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
+msgid "Category"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
+msgid "Allow"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
+msgid "Block"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
+msgid ""
+"These settings are stored in your cookies, this allows us not to store "
+"this data about you."
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
+msgid ""
+"These cookies serve your sole convenience, we don't use these cookies to "
+"track you."
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
+msgid "save"
+msgstr ""
+
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
+msgid "back"
+msgstr ""
+
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
+msgid "Search URL"
+msgstr ""
+
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
+msgid "Download results"
+msgstr ""
+
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:42
+msgid "Suggestions"
+msgstr "提案"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:78
+msgid "previous page"
+msgstr "前のページ"
+
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:89
+msgid "next page"
+msgstr "次のページ"
+
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
+msgid "Search for..."
+msgstr "検索する..."
+
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
+msgid "Engine stats"
+msgstr ""
+
+#: searx/templates/default/results.html:34
+msgid "Answers"
+msgstr ""
+
+# categories - manually added
+# TODO - automatically add
+msgid "files"
+msgstr "ファイル"
+
+msgid "general"
+msgstr "ウェブ"
+
+msgid "map"
+msgstr "地図"
+
+msgid "music"
+msgstr "音楽"
+
+msgid "social media"
+msgstr "ソーシャルメディア"
+
+msgid "images"
+msgstr "画像"
+
+msgid "videos"
+msgstr "動画"
+
+msgid "it"
+msgstr "情報技術"
+
+msgid "news"
+msgstr "ニュース"
+
diff --git a/searx/translations/nl/LC_MESSAGES/messages.mo b/searx/translations/nl/LC_MESSAGES/messages.mo
index 6f456e165..2d55fb352 100644
--- a/searx/translations/nl/LC_MESSAGES/messages.mo
+++ b/searx/translations/nl/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/searx/translations/nl/LC_MESSAGES/messages.po b/searx/translations/nl/LC_MESSAGES/messages.po
index 78d848387..46c975c77 100644
--- a/searx/translations/nl/LC_MESSAGES/messages.po
+++ b/searx/translations/nl/LC_MESSAGES/messages.po
@@ -1,149 +1,184 @@
-# English translations for .
+# English translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
-# This file is distributed under the same license as the project.
-#
+# This file is distributed under the same license as the PROJECT project.
+#
# Translators:
# André Koot <meneer@tken.net>, 2014
msgid ""
msgstr ""
-"Project-Id-Version: searx\n"
+"Project-Id-Version: searx\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2014-03-18 17:10+0100\n"
-"PO-Revision-Date: 2014-03-15 20:20+0000\n"
+"POT-Creation-Date: 2014-09-07 21:47+0200\n"
+"PO-Revision-Date: 2014-09-09 15:33+0000\n"
"Last-Translator: André Koot <meneer@tken.net>\n"
-"Language-Team: Dutch "
-"(http://www.transifex.com/projects/p/searx/language/nl/)\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Language-Team: Dutch (http://www.transifex.com/projects/p/searx/language/nl/)\n"
"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=utf-8\n"
+"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"
+"Language: nl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: searx/webapp.py:167
+#: searx/webapp.py:250
msgid "{minutes} minute(s) ago"
-msgstr ""
+msgstr "{minutes} min geleden"
-#: searx/webapp.py:169
+#: searx/webapp.py:252
msgid "{hours} hour(s), {minutes} minute(s) ago"
-msgstr ""
+msgstr "{hours} uur, {minutes} min geleden"
-#: searx/engines/__init__.py:311
+#: searx/engines/__init__.py:164
msgid "Page loads (sec)"
msgstr "Pagina laadt (sec)"
-#: searx/engines/__init__.py:315
+#: searx/engines/__init__.py:168
msgid "Number of results"
msgstr "Aantal zoekresultaten"
-#: searx/engines/__init__.py:319
+#: searx/engines/__init__.py:172
msgid "Scores"
msgstr "Scores"
-#: searx/engines/__init__.py:323
+#: searx/engines/__init__.py:176
msgid "Scores per result"
msgstr "Scores per zoekresultaat"
-#: searx/engines/__init__.py:327
+#: searx/engines/__init__.py:180
msgid "Errors"
msgstr "Fouten"
-#: searx/templates/index.html:7
+#: searx/templates/courgette/index.html:8 searx/templates/default/index.html:8
msgid "about"
msgstr "over"
-#: searx/templates/index.html:8
+#: searx/templates/courgette/index.html:9 searx/templates/default/index.html:9
msgid "preferences"
msgstr "voorkeuren"
-#: searx/templates/preferences.html:5
+#: searx/templates/courgette/preferences.html:5
+#: searx/templates/default/preferences.html:5
msgid "Preferences"
msgstr "Voorkeuren"
-#: searx/templates/preferences.html:9
+#: searx/templates/courgette/preferences.html:9
+#: searx/templates/default/preferences.html:9
msgid "Default categories"
msgstr "Standaardcategorieën"
-#: searx/templates/preferences.html:15
+#: searx/templates/courgette/preferences.html:15
+#: searx/templates/default/preferences.html:15
msgid "Search language"
msgstr "Zoektaal"
-#: searx/templates/preferences.html:18
+#: searx/templates/courgette/preferences.html:18
+#: searx/templates/default/preferences.html:18
msgid "Automatic"
msgstr "Automatisch"
-#: searx/templates/preferences.html:26
+#: searx/templates/courgette/preferences.html:26
+#: searx/templates/default/preferences.html:26
msgid "Interface language"
msgstr "Interfacetaal"
-#: searx/templates/preferences.html:36
+#: searx/templates/courgette/preferences.html:36
+#: searx/templates/default/preferences.html:36
+msgid "Autocomplete"
+msgstr "Auto-aanvullen"
+
+#: searx/templates/courgette/preferences.html:47
+#: searx/templates/default/preferences.html:47
+msgid "Method"
+msgstr "Methode"
+
+#: searx/templates/courgette/preferences.html:56
+#: searx/templates/default/preferences.html:56
+msgid "Themes"
+msgstr "Thema's"
+
+#: searx/templates/courgette/preferences.html:66
+#: searx/templates/default/preferences.html:66
msgid "Currently used search engines"
msgstr "Momenteel gebruikte zoekmachines"
-#: searx/templates/preferences.html:40
+#: searx/templates/courgette/preferences.html:70
+#: searx/templates/default/preferences.html:70
msgid "Engine name"
msgstr "Naam zoekmachine"
-#: searx/templates/preferences.html:41
+#: searx/templates/courgette/preferences.html:71
+#: searx/templates/default/preferences.html:71
msgid "Category"
msgstr "Categorie"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:53
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:83
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:83
msgid "Allow"
msgstr "Toestaan"
-#: searx/templates/preferences.html:42 searx/templates/preferences.html:54
+#: searx/templates/courgette/preferences.html:72
+#: searx/templates/courgette/preferences.html:84
+#: searx/templates/default/preferences.html:72
+#: searx/templates/default/preferences.html:84
msgid "Block"
msgstr "Blokkeren"
-#: searx/templates/preferences.html:62
+#: searx/templates/courgette/preferences.html:92
+#: searx/templates/default/preferences.html:92
msgid ""
-"These settings are stored in your cookies, this allows us not to store "
-"this data about you."
-msgstr ""
-"Deze instellingen worden bewaard in je cookies. Hierdoor hoeven wij niets"
-" over jou te bewaren."
+"These settings are stored in your cookies, this allows us not to store this "
+"data about you."
+msgstr "Deze instellingen worden bewaard in je cookies. Hierdoor hoeven wij niets over jou te bewaren."
-#: searx/templates/preferences.html:64
+#: searx/templates/courgette/preferences.html:94
+#: searx/templates/default/preferences.html:94
msgid ""
"These cookies serve your sole convenience, we don't use these cookies to "
"track you."
-msgstr ""
-"Deze cookies zijn alleen voor je eigen gemak, we gebruiken deze cookies "
-"niet om je te volgen."
+msgstr "Deze cookies zijn alleen voor je eigen gemak, we gebruiken deze cookies niet om je te volgen."
-#: searx/templates/preferences.html:67
+#: searx/templates/courgette/preferences.html:97
+#: searx/templates/default/preferences.html:97
msgid "save"
msgstr "bewaren"
-#: searx/templates/preferences.html:68
+#: searx/templates/courgette/preferences.html:98
+#: searx/templates/default/preferences.html:98
msgid "back"
msgstr "terug"
-#: searx/templates/results.html:11
-msgid "Suggestions"
-msgstr "Suggesties"
-
-#: searx/templates/results.html:22
+#: searx/templates/courgette/results.html:12
+#: searx/templates/default/results.html:12
msgid "Search URL"
msgstr "Zoek URL"
-#: searx/templates/results.html:26
+#: searx/templates/courgette/results.html:16
+#: searx/templates/default/results.html:16
msgid "Download results"
msgstr "Downloaden zoekresultaten"
-#: searx/templates/results.html:62
+#: searx/templates/courgette/results.html:34
+#: searx/templates/default/results.html:34
+msgid "Suggestions"
+msgstr "Suggesties"
+
+#: searx/templates/courgette/results.html:62
+#: searx/templates/default/results.html:62
msgid "previous page"
msgstr "vorige pagina"
-#: searx/templates/results.html:73
+#: searx/templates/courgette/results.html:73
+#: searx/templates/default/results.html:73
msgid "next page"
msgstr "volgende pagina"
-#: searx/templates/search.html:3
+#: searx/templates/courgette/search.html:3
+#: searx/templates/default/search.html:3
msgid "Search for..."
msgstr "Zoeken naar..."
-#: searx/templates/stats.html:4
+#: searx/templates/courgette/stats.html:4 searx/templates/default/stats.html:4
msgid "Engine stats"
msgstr "Zoekmachinestatistieken"
@@ -173,3 +208,5 @@ msgstr "it"
msgid "news"
msgstr "nieuws"
+msgid "map"
+msgstr "kaart"
diff --git a/searx/webapp.py b/searx/webapp.py
index 25c99d94c..830cf440a 100644
--- a/searx/webapp.py
+++ b/searx/webapp.py
@@ -47,6 +47,7 @@ from searx.utils import (
from searx.https_rewrite import https_rules
from searx.languages import language_codes
from searx.search import Search
+from searx.query import Query
from searx.autocomplete import backends as autocomplete_backends
from urlparse import urlparse
@@ -57,8 +58,8 @@ static_path, templates_path, themes =\
get_themes(settings['themes_path']
if settings.get('themes_path')
else searx_dir)
-default_theme = settings['default_theme'] if \
- settings.get('default_theme', None) else 'default'
+
+default_theme = settings['server'].get('default_theme', 'default')
app = Flask(
__name__,
@@ -116,7 +117,9 @@ def get_current_theme_name(override=None):
if override and override in themes:
return override
- theme_name = request.cookies.get('theme', default_theme)
+ theme_name = request.args.get('theme',
+ request.cookies.get('theme',
+ default_theme))
if theme_name not in themes:
theme_name = default_theme
return theme_name
@@ -152,12 +155,18 @@ def render(template_name, override_theme=None, **kwargs):
if not 'selected_categories' in kwargs:
kwargs['selected_categories'] = []
+ for arg in request.args:
+ if arg.startswith('category_'):
+ c = arg.split('_', 1)[1]
+ if c in categories:
+ kwargs['selected_categories'].append(c)
+ if not kwargs['selected_categories']:
cookie_categories = request.cookies.get('categories', '').split(',')
for ccateg in cookie_categories:
if ccateg in categories:
kwargs['selected_categories'].append(ccateg)
- if not kwargs['selected_categories']:
- kwargs['selected_categories'] = ['general']
+ if not kwargs['selected_categories']:
+ kwargs['selected_categories'] = ['general']
if not 'autocomplete' in kwargs:
kwargs['autocomplete'] = autocomplete
@@ -193,7 +202,7 @@ def index():
'index.html',
)
- search.results, search.suggestions = search.search(request)
+ search.results, search.suggestions, search.answers, search.infoboxes = search.search(request)
for result in search.results:
@@ -330,6 +339,8 @@ def index():
pageno=search.pageno,
base_url=get_base_url(),
suggestions=search.suggestions,
+ answers=search.answers,
+ infoboxes=search.infoboxes,
theme=get_current_theme_name()
)
@@ -347,24 +358,46 @@ def autocompleter():
"""Return autocompleter results"""
request_data = {}
+ # select request method
if request.method == 'POST':
request_data = request.form
else:
request_data = request.args
- # TODO fix XSS-vulnerability
- query = request_data.get('q', '').encode('utf-8')
+ # set blocked engines
+ if request.cookies.get('blocked_engines'):
+ blocked_engines = request.cookies['blocked_engines'].split(',') # noqa
+ else:
+ blocked_engines = []
+
+ # parse query
+ query = Query(request_data.get('q', '').encode('utf-8'), blocked_engines)
+ query.parse_query()
- if not query:
+ # check if search query is set
+ if not query.getSearchQuery():
return
+ # run autocompleter
completer = autocomplete_backends.get(request.cookies.get('autocomplete'))
+ # check if valid autocompleter is selected
if not completer:
return
- results = completer(query)
+ # run autocompletion
+ raw_results = completer(query.getSearchQuery())
+
+ # parse results (write :language and !engine back to result string)
+ results = []
+ for result in raw_results:
+ result_query = query
+ result_query.changeSearchQuery(result)
+
+ # add parsed result
+ results.append(result_query.getFullQuery())
+ # return autocompleter results
if request_data.get('format') == 'x-suggestions':
return Response(json.dumps([query, results]),
mimetype='application/json')
diff --git a/setup.py b/setup.py
index 92a5223d1..7048fa22b 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,7 @@ setup(
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
'License :: OSI Approved :: GNU Affero General Public License v3'
],
- keywords='meta search engine',
+ keywords='metasearch searchengine search web http',
author='Adam Tauber',
author_email='asciimoo@gmail.com',
url='https://github.com/asciimoo/searx',