We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Disabling all engines in a category makes the bang search in general
**How To Reproduce**
1. Disable all engines in one category in the user preferences (e.g. in the files category).
2. Search in the category using the bang syntax (e.g. `!files test`).

**Expected behavior**
No results (and maybe a message that all engines in this category are disabled).

**Observed behavior**
The search is performed in the general category.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `searx/webadapter.py`
Content:
```
1 from collections import defaultdict
2 from typing import Dict, List, Optional, Tuple
3 from searx.exceptions import SearxParameterException
4 from searx.webutils import VALID_LANGUAGE_CODE
5 from searx.query import RawTextQuery
6 from searx.engines import categories, engines
7 from searx.search import SearchQuery, EngineRef
8 from searx.preferences import Preferences, is_locked
9 
10 
11 # remove duplicate queries.
12 # FIXME: does not fix "!music !soundcloud", because the categories are 'none' and 'music'
13 def deduplicate_engineref_list(engineref_list: List[EngineRef]) -> List[EngineRef]:
14     engineref_dict = {q.category + '|' + q.name: q for q in engineref_list}
15     return list(engineref_dict.values())
16 
17 
18 def validate_engineref_list(
19     engineref_list: List[EngineRef], preferences: Preferences
20 ) -> Tuple[List[EngineRef], List[EngineRef], List[EngineRef]]:
21     """Validate query_engines according to the preferences
22 
23     Returns:
24         List[EngineRef]: list of existing engines with a validated token
25         List[EngineRef]: list of unknown engine
26         List[EngineRef]: list of engine with invalid token according to the preferences
27     """
28     valid = []
29     unknown = []
30     no_token = []
31     for engineref in engineref_list:
32         if engineref.name not in engines:
33             unknown.append(engineref)
34             continue
35 
36         engine = engines[engineref.name]
37         if not preferences.validate_token(engine):
38             no_token.append(engineref)
39             continue
40 
41         valid.append(engineref)
42     return valid, unknown, no_token
43 
44 
45 def parse_pageno(form: Dict[str, str]) -> int:
46     pageno_param = form.get('pageno', '1')
47     if not pageno_param.isdigit() or int(pageno_param) < 1:
48         raise SearxParameterException('pageno', pageno_param)
49     return int(pageno_param)
50 
51 
52 def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str:
53     if is_locked('language'):
54         return preferences.get_value('language')
55     # get language
56     # set specific language if set on request, query or preferences
57     # TODO support search with multible languages
58     if len(raw_text_query.languages):
59         query_lang = raw_text_query.languages[-1]
60     elif 'language' in form:
61         query_lang = form.get('language')
62     else:
63         query_lang = preferences.get_value('language')
64 
65     # check language
66     if not VALID_LANGUAGE_CODE.match(query_lang):
67         raise SearxParameterException('language', query_lang)
68 
69     return query_lang
70 
71 
72 def parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int:
73     if is_locked('safesearch'):
74         return preferences.get_value('safesearch')
75 
76     if 'safesearch' in form:
77         query_safesearch = form.get('safesearch')
78         # first check safesearch
79         if not query_safesearch.isdigit():
80             raise SearxParameterException('safesearch', query_safesearch)
81         query_safesearch = int(query_safesearch)
82     else:
83         query_safesearch = preferences.get_value('safesearch')
84 
85     # safesearch : second check
86     if query_safesearch < 0 or query_safesearch > 2:
87         raise SearxParameterException('safesearch', query_safesearch)
88 
89     return query_safesearch
90 
91 
92 def parse_time_range(form: Dict[str, str]) -> Optional[str]:
93     query_time_range = form.get('time_range')
94     # check time_range
95     query_time_range = None if query_time_range in ('', 'None') else query_time_range
96     if query_time_range not in (None, 'day', 'week', 'month', 'year'):
97         raise SearxParameterException('time_range', query_time_range)
98     return query_time_range
99 
100 
101 def parse_timeout(form: Dict[str, str], raw_text_query: RawTextQuery) -> Optional[float]:
102     timeout_limit = raw_text_query.timeout_limit
103     if timeout_limit is None:
104         timeout_limit = form.get('timeout_limit')
105 
106     if timeout_limit is None or timeout_limit in ['None', '']:
107         return None
108     try:
109         return float(timeout_limit)
110     except ValueError as e:
111         raise SearxParameterException('timeout_limit', timeout_limit) from e
112 
113 
114 def parse_category_form(query_categories: List[str], name: str, value: str) -> None:
115     if name == 'categories':
116         query_categories.extend(categ for categ in map(str.strip, value.split(',')) if categ in categories)
117     elif name.startswith('category_'):
118         category = name[9:]
119 
120         # if category is not found in list, skip
121         if category not in categories:
122             return
123 
124         if value != 'off':
125             # add category to list
126             query_categories.append(category)
127         elif category in query_categories:
128             # remove category from list if property is set to 'off'
129             query_categories.remove(category)
130 
131 
132 def get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]:
133     selected_categories = []
134 
135     if not is_locked('categories') and form is not None:
136         for name, value in form.items():
137             parse_category_form(selected_categories, name, value)
138 
139     # if no category is specified for this search,
140     # using user-defined default-configuration which
141     # (is stored in cookie)
142     if not selected_categories:
143         cookie_categories = preferences.get_value('categories')
144         for ccateg in cookie_categories:
145             selected_categories.append(ccateg)
146 
147     # if still no category is specified, using general
148     # as default-category
149     if not selected_categories:
150         selected_categories = ['general']
151 
152     return selected_categories
153 
154 
155 def get_engineref_from_category_list(category_list: List[str], disabled_engines: List[str]) -> List[EngineRef]:
156     result = []
157     for categ in category_list:
158         result.extend(
159             EngineRef(engine.name, categ)
160             for engine in categories[categ]
161             if (engine.name, categ) not in disabled_engines
162         )
163     return result
164 
165 
166 def parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engines: List[str]) -> List[EngineRef]:
167     query_engineref_list = []
168     query_categories = []
169 
170     # set categories/engines
171     explicit_engine_list = False
172     if not is_locked('categories'):
173         # parse the form only if the categories are not locked
174         for pd_name, pd in form.items():
175             if pd_name == 'engines':
176                 pd_engines = [
177                     EngineRef(engine_name, engines[engine_name].categories[0])
178                     for engine_name in map(str.strip, pd.split(','))
179                     if engine_name in engines
180                 ]
181                 if pd_engines:
182                     query_engineref_list.extend(pd_engines)
183                     explicit_engine_list = True
184             else:
185                 parse_category_form(query_categories, pd_name, pd)
186 
187     if explicit_engine_list:
188         # explicit list of engines with the "engines" parameter in the form
189         if query_categories:
190             # add engines from referenced by the "categories" parameter and the "category_*"" parameters
191             query_engineref_list.extend(get_engineref_from_category_list(query_categories, disabled_engines))
192     else:
193         # no "engines" parameters in the form
194         if not query_categories:
195             # and neither "categories" parameter nor "category_*"" parameters in the form
196             # -> get the categories from the preferences (the cookies or the settings)
197             query_categories = get_selected_categories(preferences, None)
198 
199         # using all engines for that search, which are
200         # declared under the specific categories
201         query_engineref_list.extend(get_engineref_from_category_list(query_categories, disabled_engines))
202 
203     return query_engineref_list
204 
205 
206 def parse_engine_data(form):
207     engine_data = defaultdict(dict)
208     for k, v in form.items():
209         if k.startswith("engine_data"):
210             _, engine, key = k.split('-')
211             engine_data[engine][key] = v
212     return engine_data
213 
214 
215 def get_search_query_from_webapp(
216     preferences: Preferences, form: Dict[str, str]
217 ) -> Tuple[SearchQuery, RawTextQuery, List[EngineRef], List[EngineRef]]:
218     # no text for the query ?
219     if not form.get('q'):
220         raise SearxParameterException('q', '')
221 
222     # set blocked engines
223     disabled_engines = preferences.engines.get_disabled()
224 
225     # parse query, if tags are set, which change
226     # the serch engine or search-language
227     raw_text_query = RawTextQuery(form['q'], disabled_engines)
228 
229     # set query
230     query = raw_text_query.getQuery()
231     query_pageno = parse_pageno(form)
232     query_lang = parse_lang(preferences, form, raw_text_query)
233     query_safesearch = parse_safesearch(preferences, form)
234     query_time_range = parse_time_range(form)
235     query_timeout = parse_timeout(form, raw_text_query)
236     external_bang = raw_text_query.external_bang
237     engine_data = parse_engine_data(form)
238 
239     if not is_locked('categories') and raw_text_query.enginerefs and raw_text_query.specific:
240         # if engines are calculated from query,
241         # set categories by using that informations
242         query_engineref_list = raw_text_query.enginerefs
243     else:
244         # otherwise, using defined categories to
245         # calculate which engines should be used
246         query_engineref_list = parse_generic(preferences, form, disabled_engines)
247 
248     query_engineref_list = deduplicate_engineref_list(query_engineref_list)
249     query_engineref_list, query_engineref_list_unknown, query_engineref_list_notoken = validate_engineref_list(
250         query_engineref_list, preferences
251     )
252 
253     return (
254         SearchQuery(
255             query,
256             query_engineref_list,
257             query_lang,
258             query_safesearch,
259             query_pageno,
260             query_time_range,
261             query_timeout,
262             external_bang=external_bang,
263             engine_data=engine_data,
264         ),
265         raw_text_query,
266         query_engineref_list_unknown,
267         query_engineref_list_notoken,
268     )
269
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/searx/webadapter.py b/searx/webadapter.py
--- a/searx/webadapter.py
+++ b/searx/webadapter.py
@@ -236,7 +236,7 @@
     external_bang = raw_text_query.external_bang
     engine_data = parse_engine_data(form)
 
-    if not is_locked('categories') and raw_text_query.enginerefs and raw_text_query.specific:
+    if not is_locked('categories') and raw_text_query.specific:
         # if engines are calculated from query,
         # set categories by using that informations
         query_engineref_list = raw_text_query.enginerefs

{"golden_diff": "diff --git a/searx/webadapter.py b/searx/webadapter.py\n--- a/searx/webadapter.py\n+++ b/searx/webadapter.py\n@@ -236,7 +236,7 @@\n     external_bang = raw_text_query.external_bang\n     engine_data = parse_engine_data(form)\n \n-    if not is_locked('categories') and raw_text_query.enginerefs and raw_text_query.specific:\n+    if not is_locked('categories') and raw_text_query.specific:\n         # if engines are calculated from query,\n         # set categories by using that informations\n         query_engineref_list = raw_text_query.enginerefs\n", "issue": "Disabling all engines in a category makes the bang search in general\n**How To Reproduce**\r\n1. Disable all engines in one category in the user preferences (e.g. in the files category).\r\n2. Search in the category using the bang syntax (e.g. `!files test`).\r\n\r\n**Expected behavior**\r\nNo results (and maybe a message that all engines in this category are disabled).\r\n\r\n**Observed behavior**\r\nThe search is performed in the general category.\n", "before_files": [{"content": "from collections import defaultdict\nfrom typing import Dict, List, Optional, Tuple\nfrom searx.exceptions import SearxParameterException\nfrom searx.webutils import VALID_LANGUAGE_CODE\nfrom searx.query import RawTextQuery\nfrom searx.engines import categories, engines\nfrom searx.search import SearchQuery, EngineRef\nfrom searx.preferences import Preferences, is_locked\n\n\n# remove duplicate queries.\n# FIXME: does not fix \"!music !soundcloud\", because the categories are 'none' and 'music'\ndef deduplicate_engineref_list(engineref_list: List[EngineRef]) -> List[EngineRef]:\n    engineref_dict = {q.category + '|' + q.name: q for q in engineref_list}\n    return list(engineref_dict.values())\n\n\ndef validate_engineref_list(\n    engineref_list: List[EngineRef], preferences: Preferences\n) -> Tuple[List[EngineRef], List[EngineRef], List[EngineRef]]:\n    \"\"\"Validate query_engines according to the preferences\n\n    Returns:\n        List[EngineRef]: list of existing engines with a validated token\n        List[EngineRef]: list of unknown engine\n        List[EngineRef]: list of engine with invalid token according to the preferences\n    \"\"\"\n    valid = []\n    unknown = []\n    no_token = []\n    for engineref in engineref_list:\n        if engineref.name not in engines:\n            unknown.append(engineref)\n            continue\n\n        engine = engines[engineref.name]\n        if not preferences.validate_token(engine):\n            no_token.append(engineref)\n            continue\n\n        valid.append(engineref)\n    return valid, unknown, no_token\n\n\ndef parse_pageno(form: Dict[str, str]) -> int:\n    pageno_param = form.get('pageno', '1')\n    if not pageno_param.isdigit() or int(pageno_param) < 1:\n        raise SearxParameterException('pageno', pageno_param)\n    return int(pageno_param)\n\n\ndef parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str:\n    if is_locked('language'):\n        return preferences.get_value('language')\n    # get language\n    # set specific language if set on request, query or preferences\n    # TODO support search with multible languages\n    if len(raw_text_query.languages):\n        query_lang = raw_text_query.languages[-1]\n    elif 'language' in form:\n        query_lang = form.get('language')\n    else:\n        query_lang = preferences.get_value('language')\n\n    # check language\n    if not VALID_LANGUAGE_CODE.match(query_lang):\n        raise SearxParameterException('language', query_lang)\n\n    return query_lang\n\n\ndef parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int:\n    if is_locked('safesearch'):\n        return preferences.get_value('safesearch')\n\n    if 'safesearch' in form:\n        query_safesearch = form.get('safesearch')\n        # first check safesearch\n        if not query_safesearch.isdigit():\n            raise SearxParameterException('safesearch', query_safesearch)\n        query_safesearch = int(query_safesearch)\n    else:\n        query_safesearch = preferences.get_value('safesearch')\n\n    # safesearch : second check\n    if query_safesearch < 0 or query_safesearch > 2:\n        raise SearxParameterException('safesearch', query_safesearch)\n\n    return query_safesearch\n\n\ndef parse_time_range(form: Dict[str, str]) -> Optional[str]:\n    query_time_range = form.get('time_range')\n    # check time_range\n    query_time_range = None if query_time_range in ('', 'None') else query_time_range\n    if query_time_range not in (None, 'day', 'week', 'month', 'year'):\n        raise SearxParameterException('time_range', query_time_range)\n    return query_time_range\n\n\ndef parse_timeout(form: Dict[str, str], raw_text_query: RawTextQuery) -> Optional[float]:\n    timeout_limit = raw_text_query.timeout_limit\n    if timeout_limit is None:\n        timeout_limit = form.get('timeout_limit')\n\n    if timeout_limit is None or timeout_limit in ['None', '']:\n        return None\n    try:\n        return float(timeout_limit)\n    except ValueError as e:\n        raise SearxParameterException('timeout_limit', timeout_limit) from e\n\n\ndef parse_category_form(query_categories: List[str], name: str, value: str) -> None:\n    if name == 'categories':\n        query_categories.extend(categ for categ in map(str.strip, value.split(',')) if categ in categories)\n    elif name.startswith('category_'):\n        category = name[9:]\n\n        # if category is not found in list, skip\n        if category not in categories:\n            return\n\n        if value != 'off':\n            # add category to list\n            query_categories.append(category)\n        elif category in query_categories:\n            # remove category from list if property is set to 'off'\n            query_categories.remove(category)\n\n\ndef get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]:\n    selected_categories = []\n\n    if not is_locked('categories') and form is not None:\n        for name, value in form.items():\n            parse_category_form(selected_categories, name, value)\n\n    # if no category is specified for this search,\n    # using user-defined default-configuration which\n    # (is stored in cookie)\n    if not selected_categories:\n        cookie_categories = preferences.get_value('categories')\n        for ccateg in cookie_categories:\n            selected_categories.append(ccateg)\n\n    # if still no category is specified, using general\n    # as default-category\n    if not selected_categories:\n        selected_categories = ['general']\n\n    return selected_categories\n\n\ndef get_engineref_from_category_list(category_list: List[str], disabled_engines: List[str]) -> List[EngineRef]:\n    result = []\n    for categ in category_list:\n        result.extend(\n            EngineRef(engine.name, categ)\n            for engine in categories[categ]\n            if (engine.name, categ) not in disabled_engines\n        )\n    return result\n\n\ndef parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engines: List[str]) -> List[EngineRef]:\n    query_engineref_list = []\n    query_categories = []\n\n    # set categories/engines\n    explicit_engine_list = False\n    if not is_locked('categories'):\n        # parse the form only if the categories are not locked\n        for pd_name, pd in form.items():\n            if pd_name == 'engines':\n                pd_engines = [\n                    EngineRef(engine_name, engines[engine_name].categories[0])\n                    for engine_name in map(str.strip, pd.split(','))\n                    if engine_name in engines\n                ]\n                if pd_engines:\n                    query_engineref_list.extend(pd_engines)\n                    explicit_engine_list = True\n            else:\n                parse_category_form(query_categories, pd_name, pd)\n\n    if explicit_engine_list:\n        # explicit list of engines with the \"engines\" parameter in the form\n        if query_categories:\n            # add engines from referenced by the \"categories\" parameter and the \"category_*\"\" parameters\n            query_engineref_list.extend(get_engineref_from_category_list(query_categories, disabled_engines))\n    else:\n        # no \"engines\" parameters in the form\n        if not query_categories:\n            # and neither \"categories\" parameter nor \"category_*\"\" parameters in the form\n            # -> get the categories from the preferences (the cookies or the settings)\n            query_categories = get_selected_categories(preferences, None)\n\n        # using all engines for that search, which are\n        # declared under the specific categories\n        query_engineref_list.extend(get_engineref_from_category_list(query_categories, disabled_engines))\n\n    return query_engineref_list\n\n\ndef parse_engine_data(form):\n    engine_data = defaultdict(dict)\n    for k, v in form.items():\n        if k.startswith(\"engine_data\"):\n            _, engine, key = k.split('-')\n            engine_data[engine][key] = v\n    return engine_data\n\n\ndef get_search_query_from_webapp(\n    preferences: Preferences, form: Dict[str, str]\n) -> Tuple[SearchQuery, RawTextQuery, List[EngineRef], List[EngineRef]]:\n    # no text for the query ?\n    if not form.get('q'):\n        raise SearxParameterException('q', '')\n\n    # set blocked engines\n    disabled_engines = preferences.engines.get_disabled()\n\n    # parse query, if tags are set, which change\n    # the serch engine or search-language\n    raw_text_query = RawTextQuery(form['q'], disabled_engines)\n\n    # set query\n    query = raw_text_query.getQuery()\n    query_pageno = parse_pageno(form)\n    query_lang = parse_lang(preferences, form, raw_text_query)\n    query_safesearch = parse_safesearch(preferences, form)\n    query_time_range = parse_time_range(form)\n    query_timeout = parse_timeout(form, raw_text_query)\n    external_bang = raw_text_query.external_bang\n    engine_data = parse_engine_data(form)\n\n    if not is_locked('categories') and raw_text_query.enginerefs and raw_text_query.specific:\n        # if engines are calculated from query,\n        # set categories by using that informations\n        query_engineref_list = raw_text_query.enginerefs\n    else:\n        # otherwise, using defined categories to\n        # calculate which engines should be used\n        query_engineref_list = parse_generic(preferences, form, disabled_engines)\n\n    query_engineref_list = deduplicate_engineref_list(query_engineref_list)\n    query_engineref_list, query_engineref_list_unknown, query_engineref_list_notoken = validate_engineref_list(\n        query_engineref_list, preferences\n    )\n\n    return (\n        SearchQuery(\n            query,\n            query_engineref_list,\n            query_lang,\n            query_safesearch,\n            query_pageno,\n            query_time_range,\n            query_timeout,\n            external_bang=external_bang,\n            engine_data=engine_data,\n        ),\n        raw_text_query,\n        query_engineref_list_unknown,\n        query_engineref_list_notoken,\n    )\n", "path": "searx/webadapter.py"}], "after_files": [{"content": "from collections import defaultdict\nfrom typing import Dict, List, Optional, Tuple\nfrom searx.exceptions import SearxParameterException\nfrom searx.webutils import VALID_LANGUAGE_CODE\nfrom searx.query import RawTextQuery\nfrom searx.engines import categories, engines\nfrom searx.search import SearchQuery, EngineRef\nfrom searx.preferences import Preferences, is_locked\n\n\n# remove duplicate queries.\n# FIXME: does not fix \"!music !soundcloud\", because the categories are 'none' and 'music'\ndef deduplicate_engineref_list(engineref_list: List[EngineRef]) -> List[EngineRef]:\n    engineref_dict = {q.category + '|' + q.name: q for q in engineref_list}\n    return list(engineref_dict.values())\n\n\ndef validate_engineref_list(\n    engineref_list: List[EngineRef], preferences: Preferences\n) -> Tuple[List[EngineRef], List[EngineRef], List[EngineRef]]:\n    \"\"\"Validate query_engines according to the preferences\n\n    Returns:\n        List[EngineRef]: list of existing engines with a validated token\n        List[EngineRef]: list of unknown engine\n        List[EngineRef]: list of engine with invalid token according to the preferences\n    \"\"\"\n    valid = []\n    unknown = []\n    no_token = []\n    for engineref in engineref_list:\n        if engineref.name not in engines:\n            unknown.append(engineref)\n            continue\n\n        engine = engines[engineref.name]\n        if not preferences.validate_token(engine):\n            no_token.append(engineref)\n            continue\n\n        valid.append(engineref)\n    return valid, unknown, no_token\n\n\ndef parse_pageno(form: Dict[str, str]) -> int:\n    pageno_param = form.get('pageno', '1')\n    if not pageno_param.isdigit() or int(pageno_param) < 1:\n        raise SearxParameterException('pageno', pageno_param)\n    return int(pageno_param)\n\n\ndef parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str:\n    if is_locked('language'):\n        return preferences.get_value('language')\n    # get language\n    # set specific language if set on request, query or preferences\n    # TODO support search with multible languages\n    if len(raw_text_query.languages):\n        query_lang = raw_text_query.languages[-1]\n    elif 'language' in form:\n        query_lang = form.get('language')\n    else:\n        query_lang = preferences.get_value('language')\n\n    # check language\n    if not VALID_LANGUAGE_CODE.match(query_lang):\n        raise SearxParameterException('language', query_lang)\n\n    return query_lang\n\n\ndef parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int:\n    if is_locked('safesearch'):\n        return preferences.get_value('safesearch')\n\n    if 'safesearch' in form:\n        query_safesearch = form.get('safesearch')\n        # first check safesearch\n        if not query_safesearch.isdigit():\n            raise SearxParameterException('safesearch', query_safesearch)\n        query_safesearch = int(query_safesearch)\n    else:\n        query_safesearch = preferences.get_value('safesearch')\n\n    # safesearch : second check\n    if query_safesearch < 0 or query_safesearch > 2:\n        raise SearxParameterException('safesearch', query_safesearch)\n\n    return query_safesearch\n\n\ndef parse_time_range(form: Dict[str, str]) -> Optional[str]:\n    query_time_range = form.get('time_range')\n    # check time_range\n    query_time_range = None if query_time_range in ('', 'None') else query_time_range\n    if query_time_range not in (None, 'day', 'week', 'month', 'year'):\n        raise SearxParameterException('time_range', query_time_range)\n    return query_time_range\n\n\ndef parse_timeout(form: Dict[str, str], raw_text_query: RawTextQuery) -> Optional[float]:\n    timeout_limit = raw_text_query.timeout_limit\n    if timeout_limit is None:\n        timeout_limit = form.get('timeout_limit')\n\n    if timeout_limit is None or timeout_limit in ['None', '']:\n        return None\n    try:\n        return float(timeout_limit)\n    except ValueError as e:\n        raise SearxParameterException('timeout_limit', timeout_limit) from e\n\n\ndef parse_category_form(query_categories: List[str], name: str, value: str) -> None:\n    if name == 'categories':\n        query_categories.extend(categ for categ in map(str.strip, value.split(',')) if categ in categories)\n    elif name.startswith('category_'):\n        category = name[9:]\n\n        # if category is not found in list, skip\n        if category not in categories:\n            return\n\n        if value != 'off':\n            # add category to list\n            query_categories.append(category)\n        elif category in query_categories:\n            # remove category from list if property is set to 'off'\n            query_categories.remove(category)\n\n\ndef get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]:\n    selected_categories = []\n\n    if not is_locked('categories') and form is not None:\n        for name, value in form.items():\n            parse_category_form(selected_categories, name, value)\n\n    # if no category is specified for this search,\n    # using user-defined default-configuration which\n    # (is stored in cookie)\n    if not selected_categories:\n        cookie_categories = preferences.get_value('categories')\n        for ccateg in cookie_categories:\n            selected_categories.append(ccateg)\n\n    # if still no category is specified, using general\n    # as default-category\n    if not selected_categories:\n        selected_categories = ['general']\n\n    return selected_categories\n\n\ndef get_engineref_from_category_list(category_list: List[str], disabled_engines: List[str]) -> List[EngineRef]:\n    result = []\n    for categ in category_list:\n        result.extend(\n            EngineRef(engine.name, categ)\n            for engine in categories[categ]\n            if (engine.name, categ) not in disabled_engines\n        )\n    return result\n\n\ndef parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engines: List[str]) -> List[EngineRef]:\n    query_engineref_list = []\n    query_categories = []\n\n    # set categories/engines\n    explicit_engine_list = False\n    if not is_locked('categories'):\n        # parse the form only if the categories are not locked\n        for pd_name, pd in form.items():\n            if pd_name == 'engines':\n                pd_engines = [\n                    EngineRef(engine_name, engines[engine_name].categories[0])\n                    for engine_name in map(str.strip, pd.split(','))\n                    if engine_name in engines\n                ]\n                if pd_engines:\n                    query_engineref_list.extend(pd_engines)\n                    explicit_engine_list = True\n            else:\n                parse_category_form(query_categories, pd_name, pd)\n\n    if explicit_engine_list:\n        # explicit list of engines with the \"engines\" parameter in the form\n        if query_categories:\n            # add engines from referenced by the \"categories\" parameter and the \"category_*\"\" parameters\n            query_engineref_list.extend(get_engineref_from_category_list(query_categories, disabled_engines))\n    else:\n        # no \"engines\" parameters in the form\n        if not query_categories:\n            # and neither \"categories\" parameter nor \"category_*\"\" parameters in the form\n            # -> get the categories from the preferences (the cookies or the settings)\n            query_categories = get_selected_categories(preferences, None)\n\n        # using all engines for that search, which are\n        # declared under the specific categories\n        query_engineref_list.extend(get_engineref_from_category_list(query_categories, disabled_engines))\n\n    return query_engineref_list\n\n\ndef parse_engine_data(form):\n    engine_data = defaultdict(dict)\n    for k, v in form.items():\n        if k.startswith(\"engine_data\"):\n            _, engine, key = k.split('-')\n            engine_data[engine][key] = v\n    return engine_data\n\n\ndef get_search_query_from_webapp(\n    preferences: Preferences, form: Dict[str, str]\n) -> Tuple[SearchQuery, RawTextQuery, List[EngineRef], List[EngineRef]]:\n    # no text for the query ?\n    if not form.get('q'):\n        raise SearxParameterException('q', '')\n\n    # set blocked engines\n    disabled_engines = preferences.engines.get_disabled()\n\n    # parse query, if tags are set, which change\n    # the serch engine or search-language\n    raw_text_query = RawTextQuery(form['q'], disabled_engines)\n\n    # set query\n    query = raw_text_query.getQuery()\n    query_pageno = parse_pageno(form)\n    query_lang = parse_lang(preferences, form, raw_text_query)\n    query_safesearch = parse_safesearch(preferences, form)\n    query_time_range = parse_time_range(form)\n    query_timeout = parse_timeout(form, raw_text_query)\n    external_bang = raw_text_query.external_bang\n    engine_data = parse_engine_data(form)\n\n    if not is_locked('categories') and raw_text_query.specific:\n        # if engines are calculated from query,\n        # set categories by using that informations\n        query_engineref_list = raw_text_query.enginerefs\n    else:\n        # otherwise, using defined categories to\n        # calculate which engines should be used\n        query_engineref_list = parse_generic(preferences, form, disabled_engines)\n\n    query_engineref_list = deduplicate_engineref_list(query_engineref_list)\n    query_engineref_list, query_engineref_list_unknown, query_engineref_list_notoken = validate_engineref_list(\n        query_engineref_list, preferences\n    )\n\n    return (\n        SearchQuery(\n            query,\n            query_engineref_list,\n            query_lang,\n            query_safesearch,\n            query_pageno,\n            query_time_range,\n            query_timeout,\n            external_bang=external_bang,\n            engine_data=engine_data,\n        ),\n        raw_text_query,\n        query_engineref_list_unknown,\n        query_engineref_list_notoken,\n    )\n", "path": "searx/webadapter.py"}]}