Browse Source

[feat] implement feeling lucky feature

Bnyro 1 year ago
parent
commit
dcee823345
6 changed files with 35 additions and 20 deletions
  1. 20 6
      searx/query.py
  2. 8 1
      searx/search/models.py
  3. 2 0
      searx/webadapter.py
  4. 4 0
      searx/webapp.py
  5. 0 12
      tests/unit/test_query.py
  6. 1 1
      tests/unit/test_search.py

+ 20 - 6
searx/query.py

@@ -150,7 +150,7 @@ class LanguageParser(QueryPartParser):
 class ExternalBangParser(QueryPartParser):
     @staticmethod
     def check(raw_value):
-        return raw_value.startswith('!!')
+        return raw_value.startswith('!!') and len(raw_value) > 2
 
     def __call__(self, raw_value):
         value = raw_value[2:]
@@ -177,7 +177,8 @@ class ExternalBangParser(QueryPartParser):
 class BangParser(QueryPartParser):
     @staticmethod
     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):
         value = raw_value[1:].replace('-', ' ').replace('_', ' ')
@@ -235,14 +236,25 @@ class BangParser(QueryPartParser):
                 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:
     """parse raw text query (the value from the html input)"""
 
     PARSER_CLASSES = [
-        TimeoutParser,  # this force the timeout
-        LanguageParser,  # this force a language
+        TimeoutParser,  # force the timeout
+        LanguageParser,  # force a language
         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):
@@ -261,6 +273,7 @@ class RawTextQuery:
         self.query_parts = []  # use self.getFullQuery()
         self.user_query_parts = []  # use self.getQuery()
         self.autocomplete_location = None
+        self.redirect_to_first_result = False
         self._parse_query()
 
     def _parse_query(self):
@@ -330,5 +343,6 @@ class RawTextQuery:
             + f"enginerefs={self.enginerefs!r}\n  "
             + f"autocomplete_list={self.autocomplete_list!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}"
         )

+ 8 - 1
searx/search/models.py

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

+ 2 - 0
searx/webadapter.py

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

+ 4 - 0
searx/webapp.py

@@ -697,6 +697,10 @@ def search():
     previous_result = None
 
     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:
         if output_format == 'html':
             if 'content' in result and result['content']:

+ 0 - 12
tests/unit/test_query.py

@@ -225,18 +225,6 @@ class TestExternalBangParser(SearxTestCase):
         a = query.autocomplete_list[0]
         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):
 

+ 1 - 1
tests/unit/test_search.py

@@ -27,7 +27,7 @@ class SearchQueryTestCase(SearxTestCase):
     def test_repr(self):
         s = SearchQuery('test', [EngineRef('bing', 'general')], 'all', 0, 1, '1', 5.0, 'g')
         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
 
     def test_eq(self):