[feat] implement feeling lucky feature

This commit is contained in:
Bnyro 2023-09-10 18:44:16 +02:00 committed by Markus Heiser
parent 71508abcbf
commit dcee823345
6 changed files with 35 additions and 20 deletions

View file

@ -150,7 +150,7 @@ class LanguageParser(QueryPartParser):
class ExternalBangParser(QueryPartParser): class ExternalBangParser(QueryPartParser):
@staticmethod @staticmethod
def check(raw_value): def check(raw_value):
return raw_value.startswith('!!') return raw_value.startswith('!!') and len(raw_value) > 2
def __call__(self, raw_value): def __call__(self, raw_value):
value = raw_value[2:] value = raw_value[2:]
@ -177,7 +177,8 @@ class ExternalBangParser(QueryPartParser):
class BangParser(QueryPartParser): class BangParser(QueryPartParser):
@staticmethod @staticmethod
def check(raw_value): def check(raw_value):
return raw_value[0] == '!' # make sure it's not any bang with double '!!'
return raw_value[0] == '!' and (len(raw_value) < 2 or raw_value[1] != '!')
def __call__(self, raw_value): def __call__(self, raw_value):
value = raw_value[1:].replace('-', ' ').replace('_', ' ') value = raw_value[1:].replace('-', ' ').replace('_', ' ')
@ -235,14 +236,25 @@ class BangParser(QueryPartParser):
self._add_autocomplete(first_char + engine_shortcut) self._add_autocomplete(first_char + engine_shortcut)
class FeelingLuckyParser(QueryPartParser):
@staticmethod
def check(raw_value):
return raw_value == '!!'
def __call__(self, raw_value):
self.raw_text_query.redirect_to_first_result = True
return True
class RawTextQuery: class RawTextQuery:
"""parse raw text query (the value from the html input)""" """parse raw text query (the value from the html input)"""
PARSER_CLASSES = [ PARSER_CLASSES = [
TimeoutParser, # this force the timeout TimeoutParser, # force the timeout
LanguageParser, # this force a language LanguageParser, # force a language
ExternalBangParser, # external bang (must be before BangParser) ExternalBangParser, # external bang (must be before BangParser)
BangParser, # this force a engine or category BangParser, # force an engine or category
FeelingLuckyParser, # redirect to the first link in the results list
] ]
def __init__(self, query, disabled_engines): def __init__(self, query, disabled_engines):
@ -261,6 +273,7 @@ class RawTextQuery:
self.query_parts = [] # use self.getFullQuery() self.query_parts = [] # use self.getFullQuery()
self.user_query_parts = [] # use self.getQuery() self.user_query_parts = [] # use self.getQuery()
self.autocomplete_location = None self.autocomplete_location = None
self.redirect_to_first_result = False
self._parse_query() self._parse_query()
def _parse_query(self): def _parse_query(self):
@ -330,5 +343,6 @@ class RawTextQuery:
+ f"enginerefs={self.enginerefs!r}\n " + f"enginerefs={self.enginerefs!r}\n "
+ f"autocomplete_list={self.autocomplete_list!r}\n " + f"autocomplete_list={self.autocomplete_list!r}\n "
+ f"query_parts={self.query_parts!r}\n " + f"query_parts={self.query_parts!r}\n "
+ f"user_query_parts={self.user_query_parts!r} >" + f"user_query_parts={self.user_query_parts!r} >\n"
+ f"redirect_to_first_result={self.redirect_to_first_result!r}"
) )

View file

@ -37,6 +37,7 @@ class SearchQuery:
'timeout_limit', 'timeout_limit',
'external_bang', 'external_bang',
'engine_data', 'engine_data',
'redirect_to_first_result',
) )
def __init__( def __init__(
@ -50,6 +51,7 @@ class SearchQuery:
timeout_limit: typing.Optional[float] = None, timeout_limit: typing.Optional[float] = None,
external_bang: typing.Optional[str] = None, external_bang: typing.Optional[str] = None,
engine_data: typing.Optional[typing.Dict[str, str]] = None, engine_data: typing.Optional[typing.Dict[str, str]] = None,
redirect_to_first_result: typing.Optional[bool] = None,
): ):
self.query = query self.query = query
self.engineref_list = engineref_list self.engineref_list = engineref_list
@ -60,6 +62,7 @@ class SearchQuery:
self.timeout_limit = timeout_limit self.timeout_limit = timeout_limit
self.external_bang = external_bang self.external_bang = external_bang
self.engine_data = engine_data or {} self.engine_data = engine_data or {}
self.redirect_to_first_result = redirect_to_first_result
self.locale = None self.locale = None
if self.lang: if self.lang:
@ -73,7 +76,7 @@ class SearchQuery:
return list(set(map(lambda engineref: engineref.category, self.engineref_list))) return list(set(map(lambda engineref: engineref.category, self.engineref_list)))
def __repr__(self): def __repr__(self):
return "SearchQuery({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format( return "SearchQuery({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format(
self.query, self.query,
self.engineref_list, self.engineref_list,
self.lang, self.lang,
@ -82,6 +85,7 @@ class SearchQuery:
self.time_range, self.time_range,
self.timeout_limit, self.timeout_limit,
self.external_bang, self.external_bang,
self.redirect_to_first_result,
) )
def __eq__(self, other): def __eq__(self, other):
@ -94,6 +98,7 @@ class SearchQuery:
and self.time_range == other.time_range and self.time_range == other.time_range
and self.timeout_limit == other.timeout_limit and self.timeout_limit == other.timeout_limit
and self.external_bang == other.external_bang and self.external_bang == other.external_bang
and self.redirect_to_first_result == other.redirect_to_first_result
) )
def __hash__(self): def __hash__(self):
@ -107,6 +112,7 @@ class SearchQuery:
self.time_range, self.time_range,
self.timeout_limit, self.timeout_limit,
self.external_bang, self.external_bang,
self.redirect_to_first_result,
) )
) )
@ -121,4 +127,5 @@ class SearchQuery:
self.timeout_limit, self.timeout_limit,
self.external_bang, self.external_bang,
self.engine_data, self.engine_data,
self.redirect_to_first_result,
) )

View file

@ -254,6 +254,7 @@ def get_search_query_from_webapp(
query_time_range = parse_time_range(form) query_time_range = parse_time_range(form)
query_timeout = parse_timeout(form, raw_text_query) query_timeout = parse_timeout(form, raw_text_query)
external_bang = raw_text_query.external_bang external_bang = raw_text_query.external_bang
redirect_to_first_result = raw_text_query.redirect_to_first_result
engine_data = parse_engine_data(form) engine_data = parse_engine_data(form)
query_lang = parse_lang(preferences, form, raw_text_query) query_lang = parse_lang(preferences, form, raw_text_query)
@ -288,6 +289,7 @@ def get_search_query_from_webapp(
query_timeout, query_timeout,
external_bang=external_bang, external_bang=external_bang,
engine_data=engine_data, engine_data=engine_data,
redirect_to_first_result=redirect_to_first_result,
), ),
raw_text_query, raw_text_query,
query_engineref_list_unknown, query_engineref_list_unknown,

View file

@ -697,6 +697,10 @@ def search():
previous_result = None previous_result = None
results = result_container.get_ordered_results() results = result_container.get_ordered_results()
if search_query.redirect_to_first_result and results:
return redirect(results[0]['url'], 302)
for result in results: for result in results:
if output_format == 'html': if output_format == 'html':
if 'content' in result and result['content']: if 'content' in result and result['content']:

View file

@ -225,18 +225,6 @@ class TestExternalBangParser(SearxTestCase):
a = query.autocomplete_list[0] a = query.autocomplete_list[0]
self.assertEqual(query.get_autocomplete_full_query(a), a + ' the query') self.assertEqual(query.get_autocomplete_full_query(a), a + ' the query')
def test_external_bang_autocomplete_empty(self):
query_text = 'the query !!'
query = RawTextQuery(query_text, [])
self.assertEqual(query.getFullQuery(), 'the query !!')
self.assertEqual(len(query.query_parts), 0)
self.assertFalse(query.specific)
self.assertGreater(len(query.autocomplete_list), 2)
a = query.autocomplete_list[0]
self.assertEqual(query.get_autocomplete_full_query(a), 'the query ' + a)
class TestBang(SearxTestCase): class TestBang(SearxTestCase):

View file

@ -27,7 +27,7 @@ class SearchQueryTestCase(SearxTestCase):
def test_repr(self): def test_repr(self):
s = SearchQuery('test', [EngineRef('bing', 'general')], 'all', 0, 1, '1', 5.0, 'g') s = SearchQuery('test', [EngineRef('bing', 'general')], 'all', 0, 1, '1', 5.0, 'g')
self.assertEqual( self.assertEqual(
repr(s), "SearchQuery('test', [EngineRef('bing', 'general')], 'all', 0, 1, '1', 5.0, 'g')" repr(s), "SearchQuery('test', [EngineRef('bing', 'general')], 'all', 0, 1, '1', 5.0, 'g', None)"
) # noqa ) # noqa
def test_eq(self): def test_eq(self):