diff options
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 Binary files differindex dc2922786..553d3d9d0 100644 --- a/searx/translations/de/LC_MESSAGES/messages.mo +++ b/searx/translations/de/LC_MESSAGES/messages.mo 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 Binary files differindex 4f81aa4ba..13936f4fb 100644 --- a/searx/translations/en/LC_MESSAGES/messages.mo +++ b/searx/translations/en/LC_MESSAGES/messages.mo 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 Binary files differindex 69c0fdfd8..2d0b16527 100644 --- a/searx/translations/es/LC_MESSAGES/messages.mo +++ b/searx/translations/es/LC_MESSAGES/messages.mo 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 Binary files differindex 09022d0c9..7171b9af0 100644 --- a/searx/translations/fr/LC_MESSAGES/messages.mo +++ b/searx/translations/fr/LC_MESSAGES/messages.mo 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 Binary files differindex 64beee1af..a60b3ee22 100644 --- a/searx/translations/hu/LC_MESSAGES/messages.mo +++ b/searx/translations/hu/LC_MESSAGES/messages.mo 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 Binary files differindex ffd0dc9e5..77b5503d0 100644 --- a/searx/translations/it/LC_MESSAGES/messages.mo +++ b/searx/translations/it/LC_MESSAGES/messages.mo 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 Binary files differnew file mode 100644 index 000000000..d91b5b1da --- /dev/null +++ b/searx/translations/ja/LC_MESSAGES/messages.mo 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 Binary files differindex 6f456e165..2d55fb352 100644 --- a/searx/translations/nl/LC_MESSAGES/messages.mo +++ b/searx/translations/nl/LC_MESSAGES/messages.mo 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') @@ -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', |