From 16ff8d06c73d3e0ec784e8c9a1fb38b36a95d8fb Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 7 Nov 2016 22:30:20 +0100 Subject: [PATCH 01/28] [fix] bing paging and language support see https://msdn.microsoft.com/en-us/library/ff795620.aspx for bing specific search operators closes #755 --- searx/engines/bing.py | 11 +++-------- tests/unit/engines/test_bing.py | 6 ++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index 6bdfd378b..b9c5f73fc 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -32,18 +32,13 @@ search_string = 'search?{query}&first={offset}' def request(query, params): offset = (params['pageno'] - 1) * 10 + 1 - if params['language'] == 'all': - language = 'en-US' - else: - language = params['language'].replace('_', '-') + if params['language'] != 'all': + query = 'language:{} {}'.format(params['language'].split('_')[0].upper(), query) search_path = search_string.format( - query=urlencode({'q': query, 'setmkt': language}), + 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 diff --git a/tests/unit/engines/test_bing.py b/tests/unit/engines/test_bing.py index bce221440..886584229 100644 --- a/tests/unit/engines/test_bing.py +++ b/tests/unit/engines/test_bing.py @@ -14,14 +14,12 @@ class TestBingEngine(SearxTestCase): params = bing.request(query, dicto) self.assertTrue('url' in params) self.assertTrue(query in params['url']) + self.assertTrue('language%3AFR' in params['url']) self.assertTrue('bing.com' in params['url']) - self.assertTrue('SRCHHPGUSR' in params['cookies']) - self.assertTrue('fr' in params['cookies']['SRCHHPGUSR']) dicto['language'] = 'all' params = bing.request(query, dicto) - self.assertTrue('SRCHHPGUSR' in params['cookies']) - self.assertTrue('en' in params['cookies']['SRCHHPGUSR']) + self.assertTrue('language' not in params['url']) def test_response(self): self.assertRaises(AttributeError, bing.response, None) From 17b08d096c5d7823799cdd6eab7fe67ef9941f9f Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 7 Nov 2016 22:33:17 +0100 Subject: [PATCH 02/28] [fix] unicode search expression for bing --- searx/engines/bing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index b9c5f73fc..75814a595 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -33,7 +33,7 @@ def request(query, params): offset = (params['pageno'] - 1) * 10 + 1 if params['language'] != 'all': - query = 'language:{} {}'.format(params['language'].split('_')[0].upper(), query) + query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), query) search_path = search_string.format( query=urlencode({'q': query}), From 1176505fa4d58677ca05e7a1c27ee459d86275aa Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 15:47:42 +0100 Subject: [PATCH 03/28] [fix] bing character encoding - closes #760 --- searx/engines/bing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index 75814a595..768cbdeb5 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -33,10 +33,10 @@ def request(query, params): offset = (params['pageno'] - 1) * 10 + 1 if params['language'] != 'all': - query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), query) + query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), query.decode('utf-8')) search_path = search_string.format( - query=urlencode({'q': query}), + query=urlencode({'q': query.encode('utf-8')}), offset=offset) params['url'] = base_url + search_path From 94196c4b6c92da621b45db6332f594d59af52471 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 15:49:06 +0100 Subject: [PATCH 04/28] [enh] show traceback of search errors --- searx/webapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/searx/webapp.py b/searx/webapp.py index d3d5bb51e..7d5adaf07 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -410,7 +410,8 @@ def index(): # search = Search(search_query) # without plugins search = SearchWithPlugins(search_query, request) result_container = search.search() - except: + except Exception: + logger.exception('search error') return render( 'index.html', ) From 16f2e346b3b5507034f6207e5d829cceaa99c046 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 15:52:29 +0100 Subject: [PATCH 05/28] [fix] bing unicode issue part III. --- searx/engines/bing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index 768cbdeb5..24cefaffa 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -33,10 +33,10 @@ def request(query, params): offset = (params['pageno'] - 1) * 10 + 1 if params['language'] != 'all': - query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), query.decode('utf-8')) + query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), query.decode('utf-8')).encode('utf-8') search_path = search_string.format( - query=urlencode({'q': query.encode('utf-8')}), + query=urlencode({'q': query}), offset=offset) params['url'] = base_url + search_path From 43ddbc60da114e3c764e6f7fa344e686964b66c2 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 16:09:16 +0100 Subject: [PATCH 06/28] [fix] pep8 --- searx/engines/bing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/searx/engines/bing.py b/searx/engines/bing.py index 24cefaffa..540597162 100644 --- a/searx/engines/bing.py +++ b/searx/engines/bing.py @@ -33,7 +33,8 @@ def request(query, params): offset = (params['pageno'] - 1) * 10 + 1 if params['language'] != 'all': - query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), query.decode('utf-8')).encode('utf-8') + query = u'language:{} {}'.format(params['language'].split('_')[0].upper(), + query.decode('utf-8')).encode('utf-8') search_path = search_string.format( query=urlencode({'q': query}), From 96f182d75d76a77457137abd89b3cc374c26aa9f Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 16:17:11 +0100 Subject: [PATCH 07/28] [fix] allow empty autocomplete setting closes #756 closes #761 --- searx/preferences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/preferences.py b/searx/preferences.py index ed7b6f6cd..8b787fc59 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -218,7 +218,7 @@ class Preferences(object): 'locale': EnumStringSetting(settings['ui']['default_locale'], choices=settings['locales'].keys()), 'autocomplete': EnumStringSetting(settings['search']['autocomplete'], - choices=autocomplete.backends.keys()), + choices=autocomplete.backends.keys() + ['']), 'image_proxy': MapSetting(settings['server']['image_proxy'], map={'': settings['server']['image_proxy'], '0': False, From 88dfee858e93e54ad6e54801f88b93bfdc2bb149 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 17:25:21 +0100 Subject: [PATCH 08/28] [fix] rewrite missing variable --- searx/plugins/doai_rewrite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/plugins/doai_rewrite.py b/searx/plugins/doai_rewrite.py index 0142af672..a6e15ae5a 100644 --- a/searx/plugins/doai_rewrite.py +++ b/searx/plugins/doai_rewrite.py @@ -27,5 +27,5 @@ def on_result(request, search, result): if doi.endswith(suffix): doi = doi[:-len(suffix)] result['url'] = 'http://doai.io/' + doi - result['parsed_url'] = urlparse(ctx['result']['url']) + result['parsed_url'] = urlparse(result['url']) return True From 832cf37a97b97061a26e2fdf49c293d26e917ef5 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 22:07:23 +0100 Subject: [PATCH 09/28] [enh] display errors also tried flask's flash feature but flask creates session cookies if it isn't flushed. Avoiding session cookies to preserve privacy --- searx/templates/oscar/base.html | 15 +++++++++++++++ searx/webapp.py | 16 ++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/searx/templates/oscar/base.html b/searx/templates/oscar/base.html index 81d4febcf..220f5f8b1 100644 --- a/searx/templates/oscar/base.html +++ b/searx/templates/oscar/base.html @@ -1,3 +1,4 @@ +{% from 'oscar/macros.html' import icon %} @@ -54,6 +55,20 @@ {% include 'oscar/navbar.html' %}
+ {% if errors %} + + {% endif %} {% block site_alert_error %} {% endblock %} diff --git a/searx/webapp.py b/searx/webapp.py index 7d5adaf07..68902a6cd 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -344,6 +344,8 @@ def render(template_name, override_theme=None, **kwargs): kwargs['cookies'] = request.cookies + kwargs['errors'] = request.errors + kwargs['instance_name'] = settings['general']['instance_name'] kwargs['results_on_new_tab'] = request.preferences.get_value('results_on_new_tab') @@ -364,15 +366,16 @@ def render(template_name, override_theme=None, **kwargs): @app.before_request def pre_request(): - # merge GET, POST vars + request.errors = [] + preferences = Preferences(themes, categories.keys(), engines, plugins) try: preferences.parse_cookies(request.cookies) except: - # TODO throw error message to the user - logger.warning('Invalid config') + request.errors.append(gettext('Invalid settings, please edit your preferences')) request.preferences = preferences + # merge GET, POST vars # request.form request.form = dict(request.form.items()) for k, v in request.args.items(): @@ -397,7 +400,7 @@ def index(): Supported outputs: html, json, csv, rss. """ - if not request.args and not request.form: + if request.form.get('q') is None: return render( 'index.html', ) @@ -410,7 +413,8 @@ def index(): # search = Search(search_query) # without plugins search = SearchWithPlugins(search_query, request) result_container = search.search() - except Exception: + except: + request.errors.append(gettext('search error')) logger.exception('search error') return render( 'index.html', @@ -573,7 +577,7 @@ def preferences(): try: request.preferences.parse_form(request.form) except ValidationException: - # TODO use flash feature of flask + request.errors.append(gettext('Invalid settings, please edit your preferences')) return resp return request.preferences.save(resp) From a757c2f005f38a97d7ebe53ab7271e26c13b7197 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 22:15:03 +0100 Subject: [PATCH 10/28] [fix] remove unused imports --- searx/webapp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/searx/webapp.py b/searx/webapp.py index 68902a6cd..45332ba99 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -62,8 +62,8 @@ from searx.utils import ( ) from searx.version import VERSION_STRING from searx.languages import language_codes -from searx.search import Search, SearchWithPlugins, get_search_query_from_webapp -from searx.query import RawTextQuery, SearchQuery +from searx.search import SearchWithPlugins, get_search_query_from_webapp +from searx.query import RawTextQuery from searx.autocomplete import searx_bang, backends as autocomplete_backends from searx.plugins import plugins from searx.preferences import Preferences, ValidationException From 044809e2980193f6947d26da84ef66d03321c4b1 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Mon, 14 Nov 2016 22:21:19 +0100 Subject: [PATCH 11/28] [fix] search mocking in webapp test --- tests/unit/test_webapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_webapp.py b/tests/unit/test_webapp.py index 912bebc4d..1ef1f56c3 100644 --- a/tests/unit/test_webapp.py +++ b/tests/unit/test_webapp.py @@ -5,6 +5,7 @@ from mock import Mock from urlparse import ParseResult from searx import webapp from searx.testing import SearxTestCase +from searx.search import Search class ViewsTestCase(SearxTestCase): @@ -41,7 +42,7 @@ class ViewsTestCase(SearxTestCase): results_number=lambda: 3, results_length=lambda: len(self.test_results)) - webapp.Search.search = search_mock + Search.search = search_mock def get_current_theme_name_mock(override=None): return 'legacy' From 12c369e858b45b4904ab079c3aefebaa18e7ece4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Mon, 14 Nov 2016 22:24:40 +0100 Subject: [PATCH 12/28] preferences: refactor to check consistently input values --- searx/preferences.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/searx/preferences.py b/searx/preferences.py index 8b787fc59..045f0e8c6 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -49,28 +49,32 @@ class StringSetting(Setting): class EnumStringSetting(Setting): """Setting of a value which can only come from the given choices""" + def _validate_selection(self, selection): + if selection not in self.choices: + raise ValidationException('Invalid value: "{0}"'.format(selection)) + def _post_init(self): if not hasattr(self, 'choices'): raise MissingArgumentException('Missing argument: choices') - - if self.value != '' and self.value not in self.choices: - raise ValidationException('Invalid default value: {0}'.format(self.value)) + self._validate_selection(self.value) def parse(self, data): - if data not in self.choices and data != self.value: - raise ValidationException('Invalid choice: {0}'.format(data)) + self._validate_selection(data) self.value = data class MultipleChoiceSetting(EnumStringSetting): """Setting of values which can only come from the given choices""" + def _validate_selections(self, selections): + for item in selections: + if item not in self.choices: + raise ValidationException('Invalid value: "{0}"'.format(selections)) + def _post_init(self): if not hasattr(self, 'choices'): raise MissingArgumentException('Missing argument: choices') - for item in self.value: - if item not in self.choices: - raise ValidationException('Invalid default value: {0}'.format(self.value)) + self._validate_selections(self.value) def parse(self, data): if data == '': @@ -78,9 +82,7 @@ class MultipleChoiceSetting(EnumStringSetting): return elements = data.split(',') - for item in elements: - if item not in self.choices: - raise ValidationException('Invalid choice: {0}'.format(item)) + self._validate_selections(elements) self.value = elements def parse_form(self, data): @@ -216,7 +218,7 @@ class Preferences(object): self.key_value_settings = {'categories': MultipleChoiceSetting(['general'], choices=categories), 'language': EnumStringSetting('all', choices=LANGUAGE_CODES), 'locale': EnumStringSetting(settings['ui']['default_locale'], - choices=settings['locales'].keys()), + choices=settings['locales'].keys() + ['']), 'autocomplete': EnumStringSetting(settings['search']['autocomplete'], choices=autocomplete.backends.keys() + ['']), 'image_proxy': MapSetting(settings['server']['image_proxy'], From 299c8823045a269c83d19cb6d05b24ef334207af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?No=C3=A9mi=20V=C3=A1nyi?= Date: Mon, 14 Nov 2016 22:32:40 +0100 Subject: [PATCH 13/28] search: make language configurable from settings.yml --- searx/preferences.py | 3 ++- searx/settings.yml | 1 + searx/settings_robot.yml | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/searx/preferences.py b/searx/preferences.py index 045f0e8c6..4436b8fe8 100644 --- a/searx/preferences.py +++ b/searx/preferences.py @@ -216,7 +216,8 @@ class Preferences(object): super(Preferences, self).__init__() self.key_value_settings = {'categories': MultipleChoiceSetting(['general'], choices=categories), - 'language': EnumStringSetting('all', choices=LANGUAGE_CODES), + 'language': EnumStringSetting(settings['search']['language'], + choices=LANGUAGE_CODES), 'locale': EnumStringSetting(settings['ui']['default_locale'], choices=settings['locales'].keys() + ['']), 'autocomplete': EnumStringSetting(settings['search']['autocomplete'], diff --git a/searx/settings.yml b/searx/settings.yml index 573cf5458..733341c31 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -5,6 +5,7 @@ general: search: safe_search : 0 # Filter results. 0: None, 1: Moderate, 2: Strict autocomplete : "" # Existing autocomplete backends: "dbpedia", "duckduckgo", "google", "startpage", "wikipedia" - leave blank to turn it off by default + language : "all" server: port : 8888 diff --git a/searx/settings_robot.yml b/searx/settings_robot.yml index 43dc9b00a..7d2701449 100644 --- a/searx/settings_robot.yml +++ b/searx/settings_robot.yml @@ -5,6 +5,7 @@ general: search: safe_search : 0 autocomplete : "" + language: "all" server: port : 11111 From 827f9e41ca84638d873997001b53e7f4a62a78aa Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Tue, 15 Nov 2016 09:56:18 +0100 Subject: [PATCH 14/28] [fix] gettext requires request.preferences --- searx/webapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searx/webapp.py b/searx/webapp.py index 45332ba99..090df57e2 100644 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -369,11 +369,11 @@ def pre_request(): request.errors = [] preferences = Preferences(themes, categories.keys(), engines, plugins) + request.preferences = preferences try: preferences.parse_cookies(request.cookies) except: request.errors.append(gettext('Invalid settings, please edit your preferences')) - request.preferences = preferences # merge GET, POST vars # request.form From 55dc538398090e437c5e495dddad983a7870d09b Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Sat, 19 Nov 2016 17:51:19 +0100 Subject: [PATCH 15/28] [mod] move load_module function to utils --- searx/engines/__init__.py | 17 ++++------------- searx/utils.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/searx/engines/__init__.py b/searx/engines/__init__.py index 14376c31f..116eadc97 100644 --- a/searx/engines/__init__.py +++ b/searx/engines/__init__.py @@ -16,13 +16,13 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2013- by Adam Tauber, ''' -from os.path import realpath, dirname, splitext, join +from os.path import realpath, dirname import sys -from imp import load_source from flask_babel import gettext from operator import itemgetter from searx import settings from searx import logger +from searx.utils import load_module logger = logger.getChild('engines') @@ -32,6 +32,7 @@ engine_dir = dirname(realpath(__file__)) engines = {} categories = {'general': []} +_initialized = False engine_shortcuts = {} engine_default_args = {'paging': False, @@ -46,16 +47,6 @@ engine_default_args = {'paging': False, 'time_range_support': False} -def load_module(filename): - modname = splitext(filename)[0] - if modname in sys.modules: - del sys.modules[modname] - filepath = join(engine_dir, filename) - module = load_source(modname, filepath) - module.name = modname - return module - - def load_engine(engine_data): if '_' in engine_data['name']: @@ -65,7 +56,7 @@ def load_engine(engine_data): engine_module = engine_data['engine'] try: - engine = load_module(engine_module + '.py') + engine = load_module(engine_module + '.py', engine_dir) except: logger.exception('Cannot load engine "{}"'.format(engine_module)) return None diff --git a/searx/utils.py b/searx/utils.py index 5039fa975..faa634853 100644 --- a/searx/utils.py +++ b/searx/utils.py @@ -6,7 +6,10 @@ import re from babel.dates import format_date from codecs import getincrementalencoder from HTMLParser import HTMLParser +from imp import load_source +from os.path import splitext, join from random import choice +import sys from searx.version import VERSION_STRING from searx.languages import language_codes @@ -285,3 +288,13 @@ def is_valid_lang(lang): if l[1].lower() == lang.lower(): return (True, l[0][:2], l[1].lower()) return False + + +def load_module(filename, module_dir): + modname = splitext(filename)[0] + if modname in sys.modules: + del sys.modules[modname] + filepath = join(module_dir, filename) + module = load_source(modname, filepath) + module.name = modname + return module From 971ed0abd159625bd01d0b3ca52c90b394711d77 Mon Sep 17 00:00:00 2001 From: Adam Tauber Date: Sat, 19 Nov 2016 20:53:51 +0100 Subject: [PATCH 16/28] [enh] add quick answer functionality with an example answerer --- searx/answerers/__init__.py | 46 ++++++++++++++++++++++++ searx/answerers/random/answerer.py | 50 ++++++++++++++++++++++++++ searx/results.py | 9 ++--- searx/search.py | 8 +++++ searx/templates/oscar/preferences.html | 29 +++++++++++++++ searx/webapp.py | 2 ++ tests/unit/test_answerers.py | 16 +++++++++ 7 files changed, 156 insertions(+), 4 deletions(-) create mode 100644 searx/answerers/__init__.py create mode 100644 searx/answerers/random/answerer.py create mode 100644 tests/unit/test_answerers.py diff --git a/searx/answerers/__init__.py b/searx/answerers/__init__.py new file mode 100644 index 000000000..8f5951c75 --- /dev/null +++ b/searx/answerers/__init__.py @@ -0,0 +1,46 @@ +from os import listdir +from os.path import realpath, dirname, join, isdir +from searx.utils import load_module +from collections import defaultdict + + +answerers_dir = dirname(realpath(__file__)) + + +def load_answerers(): + answerers = [] + for filename in listdir(answerers_dir): + if not isdir(join(answerers_dir, filename)): + continue + module = load_module('answerer.py', join(answerers_dir, filename)) + if not hasattr(module, 'keywords') or not isinstance(module.keywords, tuple) or not len(module.keywords): + exit(2) + answerers.append(module) + return answerers + + +def get_answerers_by_keywords(answerers): + by_keyword = defaultdict(list) + for answerer in answerers: + for keyword in answerer.keywords: + for keyword in answerer.keywords: + by_keyword[keyword].append(answerer.answer) + return by_keyword + + +def ask(query): + results = [] + query_parts = filter(None, query.query.split()) + + if query_parts[0] not in answerers_by_keywords: + return results + + for answerer in answerers_by_keywords[query_parts[0]]: + result = answerer(query) + if result: + results.append(result) + return results + + +answerers = load_answerers() +answerers_by_keywords = get_answerers_by_keywords(answerers) diff --git a/searx/answerers/random/answerer.py b/searx/answerers/random/answerer.py new file mode 100644 index 000000000..510d9f5be --- /dev/null +++ b/searx/answerers/random/answerer.py @@ -0,0 +1,50 @@ +import random +import string +from flask_babel import gettext + +# required answerer attribute +# specifies which search query keywords triggers this answerer +keywords = ('random',) + +random_int_max = 2**31 + +random_string_letters = string.lowercase + string.digits + string.uppercase + + +def random_string(): + return u''.join(random.choice(random_string_letters) + for _ in range(random.randint(8, 32))) + + +def random_float(): + return unicode(random.random()) + + +def random_int(): + return unicode(random.randint(-random_int_max, random_int_max)) + + +random_types = {u'string': random_string, + u'int': random_int, + u'float': random_float} + + +# required answerer function +# can return a list of results (any result type) for a given query +def answer(query): + parts = query.query.split() + if len(parts) != 2: + return [] + + if parts[1] not in random_types: + return [] + + return [{'answer': random_types[parts[1]]()}] + + +# required answerer function +# returns information about the answerer +def self_info(): + return {'name': gettext('Random value generator'), + 'description': gettext('Generate different random values'), + 'examples': [u'random {}'.format(x) for x in random_types]} diff --git a/searx/results.py b/searx/results.py index 634f71acd..73a96c081 100644 --- a/searx/results.py +++ b/searx/results.py @@ -146,16 +146,17 @@ class ResultContainer(object): self._number_of_results.append(result['number_of_results']) results.remove(result) - with RLock(): - engines[engine_name].stats['search_count'] += 1 - engines[engine_name].stats['result_count'] += len(results) + if engine_name in engines: + with RLock(): + engines[engine_name].stats['search_count'] += 1 + engines[engine_name].stats['result_count'] += len(results) if not results: return self.results[engine_name].extend(results) - if not self.paging and engines[engine_name].paging: + if not self.paging and engine_name in engines and engines[engine_name].paging: self.paging = True for i, result in enumerate(results): diff --git a/searx/search.py b/searx/search.py index c3f1566a9..0095de821 100644 --- a/searx/search.py +++ b/searx/search.py @@ -24,6 +24,7 @@ import searx.poolrequests as requests_lib from searx.engines import ( categories, engines ) +from searx.answerers import ask from searx.utils import gen_useragent from searx.query import RawTextQuery, SearchQuery from searx.results import ResultContainer @@ -254,6 +255,13 @@ class Search(object): def search(self): global number_of_searches + answerers_results = ask(self.search_query) + + if answerers_results: + for results in answerers_results: + self.result_container.extend('answer', results) + return self.result_container + # init vars requests = [] diff --git a/searx/templates/oscar/preferences.html b/searx/templates/oscar/preferences.html index ed790c56a..6ad795095 100644 --- a/searx/templates/oscar/preferences.html +++ b/searx/templates/oscar/preferences.html @@ -12,6 +12,7 @@
  • {{ _('General') }}
  • {{ _('Engines') }}
  • {{ _('Plugins') }}
  • + {% if answerers %}
  • {{ _('Answerers') }}
  • {% endif %}
  • {{ _('Cookies') }}
  • @@ -224,6 +225,34 @@
    + {% if answerers %} +
    + +

    + {{ _('This is the list of searx\'s instant answering modules.') }} +

    + + + + + + + + + {% for answerer in answerers %} + + + + + + + {% endfor %} +
    {{ _('Name') }}{{ _('Keywords') }}{{ _('Description') }}{{ _('Examples') }}
    {{ answerer.info.name }}{{ answerer.keywords|join(', ') }}{{ answerer.info.description }}{{ answerer.info.examples|join(', ') }}
    +
    + {% endif %} +