diff options
30 files changed, 482 insertions, 139 deletions
diff --git a/docs/admin/installation-searx.rst b/docs/admin/installation-searx.rst index f1d486021..a368bfe8c 100644 --- a/docs/admin/installation-searx.rst +++ b/docs/admin/installation-searx.rst @@ -52,7 +52,7 @@ In the same shell create *virtualenv*: :end-before: END create virtualenv To install searx's dependencies, exit the searx *bash* session you opened above -and restart a new. Before install, first check if your *virualenv* was sourced +and restart a new. Before install, first check if your *virtualenv* was sourced from the login (*~/.profile*): .. kernel-include:: $DOCS_BUILD/includes/searx.rst diff --git a/docs/conf.py b/docs/conf.py index 66c20594d..4b348ae0e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -87,6 +87,7 @@ issues_github_path = "searx/searx" # HTML ----------------------------------------------------------------- sys.path.append(os.path.abspath('_themes')) +sys.path.insert(0, os.path.abspath("../utils/")) html_theme_path = ['_themes'] html_theme = "searx" diff --git a/docs/dev/makefile.rst b/docs/dev/makefile.rst index 62cd0a984..699729a28 100644 --- a/docs/dev/makefile.rst +++ b/docs/dev/makefile.rst @@ -68,7 +68,7 @@ Python environment ``source ./local/py3/bin/activate`` -With Makefile we do no longer need to build up the virualenv manually (as +With Makefile we do no longer need to build up the virtualenv manually (as described in the :ref:`devquickstart` guide). Jump into your git working tree and release a ``make pyenv``: diff --git a/docs/utils/index.rst b/docs/utils/index.rst index 13914af28..3c7387875 100644 --- a/docs/utils/index.rst +++ b/docs/utils/index.rst @@ -16,6 +16,7 @@ developers. filtron.sh morty.sh lxc.sh + standalone_searx.py .. _toolboxing common: diff --git a/docs/utils/standalone_searx.py.rst b/docs/utils/standalone_searx.py.rst new file mode 100644 index 000000000..557c4b75b --- /dev/null +++ b/docs/utils/standalone_searx.py.rst @@ -0,0 +1,11 @@ + +.. _standalone_searx.py: + +============================= +``utils/standalone_searx.py`` +============================= + +.. automodule:: standalone_searx + :members: + + diff --git a/searx/engines/command.py b/searx/engines/command.py index b9e672ffa..1b73861f7 100644 --- a/searx/engines/command.py +++ b/searx/engines/command.py @@ -14,8 +14,8 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. ''' +import re from os.path import expanduser, isabs, realpath, commonprefix -from re import MULTILINE, search as re_search from shlex import split as shlex_split from subprocess import Popen, PIPE from time import time @@ -59,7 +59,7 @@ def init(engine_settings): if 'parse_regex' in engine_settings: parse_regex = engine_settings['parse_regex'] for result_key, regex in parse_regex.items(): - _compiled_parse_regex[result_key] = re.compile(regex, flags=MULTILINE) + _compiled_parse_regex[result_key] = re.compile(regex, flags=re.MULTILINE) if 'delimiter' in engine_settings: delimiter = engine_settings['delimiter'] diff --git a/searx/engines/scanr_structures.py b/searx/engines/scanr_structures.py index 6dbbf4fd9..72fd2b3c9 100644 --- a/searx/engines/scanr_structures.py +++ b/searx/engines/scanr_structures.py @@ -11,7 +11,7 @@ """ from json import loads, dumps -from urllib.parse import html_to_text +from searx.utils import html_to_text # engine dependent config categories = ['science'] diff --git a/searx/search.py b/searx/search.py index cd195825a..04c6b2885 100644 --- a/searx/search.py +++ b/searx/search.py @@ -57,8 +57,11 @@ class EngineRef: self.category = category self.from_bang = from_bang - def __str__(self): - return "(" + self.name + "," + self.category + "," + str(self.from_bang) + ")" + def __repr__(self): + return "EngineRef({!r}, {!r}, {!r})".format(self.name, self.category, self.from_bang) + + def __eq__(self, other): + return self.name == other.name and self.category == other.category and self.from_bang == other.from_bang class SearchQuery: @@ -87,8 +90,21 @@ class SearchQuery: self.timeout_limit = timeout_limit self.external_bang = external_bang - def __str__(self): - return self.query + ";" + str(self.engineref_list) + def __repr__(self): + return "SearchQuery({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})".\ + format(self.query, self.engineref_list, self.categories, self.lang, self.safesearch, + self.pageno, self.time_range, self.timeout_limit, self.external_bang) + + def __eq__(self, other): + return self.query == other.query\ + and self.engineref_list == other.engineref_list\ + and self.categories == self.categories\ + and self.lang == other.lang\ + and self.safesearch == other.safesearch\ + and self.pageno == other.pageno\ + and self.time_range == other.time_range\ + and self.timeout_limit == other.timeout_limit\ + and self.external_bang == other.external_bang def send_http_request(engine, request_params): diff --git a/searx/settings.yml b/searx/settings.yml index 54352bbfc..5cab0a102 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -17,6 +17,12 @@ server: image_proxy : False # Proxying image results through searx http_protocol_version : "1.0" # 1.0 and 1.1 are supported method: "POST" # POST queries are more secure as they don't show up in history but may cause problems when using Firefox containers + default_http_headers: + X-Content-Type-Options : nosniff + X-XSS-Protection : 1; mode=block + X-Download-Options : noopen + X-Robots-Tag : noindex, nofollow + Referrer-Policy : no-referrer ui: static_path : "" # Custom static path - leave it blank if you didn't change diff --git a/searx/static/plugins/js/infinite_scroll.js b/searx/static/plugins/js/infinite_scroll.js index 9930880e3..cd8096571 100644 --- a/searx/static/plugins/js/infinite_scroll.js +++ b/searx/static/plugins/js/infinite_scroll.js @@ -9,7 +9,7 @@ function loadNextPage() { $('#pagination').html('<div class="loading-spinner"></div>'); $.ajax({ type: "POST", - url: './', + url: $('#search_form').prop('action'), data: formData, dataType: 'html', success: function(data) { diff --git a/searx/templates/__common__/opensearch.xml b/searx/templates/__common__/opensearch.xml index e76a14aff..2476258c0 100644 --- a/searx/templates/__common__/opensearch.xml +++ b/searx/templates/__common__/opensearch.xml @@ -6,9 +6,9 @@ <Image>{{ urljoin(host, url_for('static', filename='img/favicon.png')) }}</Image> <LongName>searx metasearch</LongName> {% if opensearch_method == 'get' %} - <Url rel="results" type="text/html" method="get" template="{{ host }}?q={searchTerms}"/> + <Url rel="results" type="text/html" method="get" template="{{ url_for('search', _external=True) }}?q={searchTerms}"/> {% else %} - <Url rel="results" type="text/html" method="post" template="{{ host }}"> + <Url rel="results" type="text/html" method="post" template="{{ url_for('search', _external=True) }}"> <Param name="q" value="{searchTerms}" /> </Url> {% endif %} diff --git a/searx/templates/__common__/opensearch_response_rss.xml b/searx/templates/__common__/opensearch_response_rss.xml index 686443c49..82d3f7c4e 100644 --- a/searx/templates/__common__/opensearch_response_rss.xml +++ b/searx/templates/__common__/opensearch_response_rss.xml @@ -4,7 +4,7 @@ xmlns:atom="http://www.w3.org/2005/Atom"> <channel> <title>Searx search: {{ q|e }}</title> - <link>{{ base_url }}?q={{ q|e }}</link> + <link>{{ url_for('search', _external=True) }}?q={{ q|e }}</link> <description>Search results for "{{ q|e }}" - searx</description> <opensearch:totalResults>{{ number_of_results }}</opensearch:totalResults> <opensearch:startIndex>1</opensearch:startIndex> diff --git a/searx/templates/courgette/results.html b/searx/templates/courgette/results.html index aa983e666..716ea4d95 100644 --- a/searx/templates/courgette/results.html +++ b/searx/templates/courgette/results.html @@ -1,6 +1,6 @@ {% extends "courgette/base.html" %} {% block title %}{{ q|e }} - {% endblock %} -{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ url_for('index') }}?q={{ q|urlencode }}&format=rss&{% for category in selected_categories %}category_{{ category }}=1&{% endfor %}pageno={{ pageno }}">{% endblock %} +{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}&format=rss&{% for category in selected_categories %}category_{{ category }}=1&{% endfor %}pageno={{ pageno }}">{% endblock %} {% block content %} <div class="right"><a href="{{ url_for('preferences') }}" id="preferences"><span>{{ _('preferences') }}</span></a></div> <div class="small search center"> @@ -10,12 +10,12 @@ <div id="sidebar"> <div id="search_url"> {{ _('Search URL') }}: - <input type="text" value="{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}" readonly /> + <input type="text" value="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}" readonly /> </div> <div id="apis"> {{ _('Download results') }}<br /> {% for output_type in ('csv', 'json', 'rss') %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="left"> <input type="hidden" name="q" value="{{ q|e }}" /> <input type="hidden" name="format" value="{{ output_type }}" /> @@ -41,7 +41,7 @@ {% if suggestions %} <div id="suggestions"><span>{{ _('Suggestions') }}</span> {% for suggestion in suggestions %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <input type="hidden" name="q" value="{{ suggestion.url }}"> <input type="submit" value="{{ suggestion.title }}" /> </form> @@ -60,7 +60,7 @@ {% if paging %} <div id="pagination"> {% if pageno > 1 %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="left"> <input type="hidden" name="q" value="{{ q|e }}" /> {% for category in selected_categories %} @@ -71,7 +71,7 @@ </div> </form> {% endif %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="left"> {% for category in selected_categories %} <input type="hidden" name="category_{{ category }}" value="1"/> diff --git a/searx/templates/courgette/search.html b/searx/templates/courgette/search.html index fe70fde05..89daead89 100644 --- a/searx/templates/courgette/search.html +++ b/searx/templates/courgette/search.html @@ -1,7 +1,7 @@ -<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form"> +<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" id="search_form"> <div id="search_wrapper"> <input type="text" autofocus placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" autocomplete="off" {% if q %}value="{{ q }}"{% endif %}/> <input type="submit" value="search" id="search_submit" /> </div> {% include 'courgette/categories.html' %} -</form>
\ No newline at end of file +</form> diff --git a/searx/templates/legacy/infobox.html b/searx/templates/legacy/infobox.html index 4dd25fabd..70f3b12d3 100644 --- a/searx/templates/legacy/infobox.html +++ b/searx/templates/legacy/infobox.html @@ -36,7 +36,7 @@ <div> <h3><bdi>{{ topic.name }}</bdi></h3> {% for suggestion in topic.suggestions %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <input type="hidden" name="q" value="{{ suggestion }}"> <input type="submit" value="{{ suggestion }}" /> </form> diff --git a/searx/templates/legacy/results.html b/searx/templates/legacy/results.html index fd95657a4..efff0667a 100644 --- a/searx/templates/legacy/results.html +++ b/searx/templates/legacy/results.html @@ -1,6 +1,6 @@ {% extends "legacy/base.html" %} {% block title %}{{ q|e }} - {% endblock %} -{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ url_for('index') }}?q={{ q|urlencode }}&format=rss&{% for category in selected_categories %}category_{{ category }}=1&{% endfor %}pageno={{ pageno }}">{% endblock %} +{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}&format=rss&{% for category in selected_categories %}category_{{ category }}=1&{% endfor %}pageno={{ pageno }}">{% endblock %} {% block content %} <div class="preferences_container right"><a href="{{ url_for('preferences') }}" id="preferences"><span>preferences</span></a></div> <div class="small search center"> @@ -11,12 +11,12 @@ <div id="search_url"> {{ _('Search URL') }}: - <input type="text" value="{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}" readonly /> + <input type="text" value="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}" readonly /> </div> <div id="apis"> {{ _('Download results') }} {% for output_type in ('csv', 'json', 'rss') %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="left"> <input type="hidden" name="q" value="{{ q|e }}" /> <input type="hidden" name="format" value="{{ output_type }}" /> @@ -47,7 +47,7 @@ <div id="suggestions"><span id="suggestions-title">{{ _('Suggestions') }} : </span> {% set first = true %} {% for suggestion in suggestions %} - {% if not first %} • {% endif %}<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + {% if not first %} • {% endif %}<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <input type="hidden" name="q" value="{{ suggestion.url }}"> <input type="submit" class="suggestion" value="{{ suggestion.title }}" /> </form> @@ -75,7 +75,7 @@ {% if paging %} <div id="pagination"> {% if pageno > 1 %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="{% if rtl %}right{% else %}left{% endif %}"> <input type="hidden" name="q" value="{{ q|e }}" /> {% for category in selected_categories %} @@ -86,7 +86,7 @@ </div> </form> {% endif %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="{% if rtl %}left{% else %}right{% endif %}"> {% for category in selected_categories %} <input type="hidden" name="category_{{ category }}" value="1"/> diff --git a/searx/templates/legacy/search.html b/searx/templates/legacy/search.html index fcd08d6d2..88cf3d386 100644 --- a/searx/templates/legacy/search.html +++ b/searx/templates/legacy/search.html @@ -1,4 +1,4 @@ -<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form"> +<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" id="search_form"> <div id="search_wrapper"> <input type="text" autofocus placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" autocomplete="off" size="100" {% if q %}value="{{ q }}"{% endif %}/> <input type="submit" value="search" id="search_submit" /> diff --git a/searx/templates/oscar/results.html b/searx/templates/oscar/results.html index a7f96d8d2..7f6071374 100644 --- a/searx/templates/oscar/results.html +++ b/searx/templates/oscar/results.html @@ -7,7 +7,7 @@ <input type="hidden" name="language" value="{{ current_language }}" />{{- "" -}} {% if timeout_limit %}<input type="hidden" name="timeout_limit" value="{{ timeout_limit|e }}" />{% endif -%} {%- endmacro %} -{%- macro search_url() %}{{ base_url }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}{% if time_range %}&time_range={{ time_range }}{% endif %}{% if current_language != 'all' %}&language={{ current_language }}{% endif %}{% endmacro -%} +{%- macro search_url() %}{{ url_for('search', _external=True) }}?q={{ q|urlencode }}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}{% if time_range %}&time_range={{ time_range }}{% endif %}{% if current_language != 'all' %}&language={{ current_language }}{% endif %}{% endmacro -%} {% block title %}{{ q|e }} - {% endblock %} {% block meta %}{{" "}}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ search_url() }}&format=rss">{% endblock %} @@ -42,7 +42,7 @@ </div> <div class="panel-body"> {% for suggestion in suggestions %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} suggestion_item"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" role="navigation" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} suggestion_item"> {% if current_language != 'all' %} <input type="hidden" name="language" value="{{ current_language }}"> {% endif %} @@ -71,7 +71,7 @@ <label>{{ _('Download results') }}</label> <div class="clearfix"></div> {% for output_type in ('csv', 'json', 'rss') %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} result_download"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="form-inline pull-{% if rtl %}right{% else %}left{% endif %} result_download"> {{- search_form_attrs(pageno) -}} <input type="hidden" name="format" value="{{ output_type }}">{{- "" -}} <button type="submit" class="btn btn-default">{{ output_type }}</button>{{- "" -}} @@ -92,7 +92,7 @@ <div class="clearfix"> <span class="result_header text-muted form-inline pull-left suggestion_item">{{ _('Try searching for:') }}</span> {% for correction in corrections -%} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation" class="form-inline pull-left suggestion_item">{{- "" -}} + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" role="navigation" class="form-inline pull-left suggestion_item">{{- "" -}} {% if current_language != 'all' %} <input type="hidden" name="language" value="{{ current_language }}"> {% endif %} @@ -140,13 +140,13 @@ {% if rtl %} <div id="pagination"> <div class="pull-left">{{- "" -}} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="pull-left"> {{- search_form_attrs(pageno+1) -}} <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-backward"></span> {{ _('next page') }}</button>{{- "" -}} </form>{{- "" -}} </div> <div class="pull-right">{{- "" -}} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="pull-left"> {{- search_form_attrs(pageno-1) -}} <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-forward"></span> {{ _('previous page') }}</button>{{- "" -}} </form>{{- "" -}} @@ -156,13 +156,13 @@ {% else %} <div id="pagination"> <div class="pull-left">{{- "" -}} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="pull-left"> {{- search_form_attrs(pageno-1) -}} <button type="submit" class="btn btn-default" {% if pageno == 1 %}disabled{% endif %}><span class="glyphicon glyphicon-backward"></span> {{ _('previous page') }}</button>{{- "" -}} </form>{{- "" -}} </div> <div class="pull-right">{{- "" -}} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" class="pull-left"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="pull-left"> {{- search_form_attrs(pageno+1) -}} <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-forward"></span> {{ _('next page') }}</button>{{- "" -}} </form>{{- "" -}} diff --git a/searx/templates/oscar/search.html b/searx/templates/oscar/search.html index 666a4df38..c82aab7ea 100644 --- a/searx/templates/oscar/search.html +++ b/searx/templates/oscar/search.html @@ -1,5 +1,5 @@ {% from 'oscar/macros.html' import icon %} -<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search"> +<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" id="search_form" role="search"> <div class="row"> <div class="col-xs-12 col-md-8"> <div class="input-group search-margin"> diff --git a/searx/templates/oscar/search_full.html b/searx/templates/oscar/search_full.html index 1f1c50e50..d3982304d 100644 --- a/searx/templates/oscar/search_full.html +++ b/searx/templates/oscar/search_full.html @@ -1,6 +1,6 @@ {% from 'oscar/macros.html' import icon %} -<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form" role="search"> +<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" id="search_form" role="search"> {% if rtl %} <div class="input-group"> {% else %} diff --git a/searx/templates/pix-art/search.html b/searx/templates/pix-art/search.html index bb40559db..210913e34 100644 --- a/searx/templates/pix-art/search.html +++ b/searx/templates/pix-art/search.html @@ -1,4 +1,4 @@ -<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" id="search_form"> +<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" id="search_form"> <div id="search_wrapper"> <input type="text" autofocus placeholder="{{ _('Search for...') }}" id="q" class="q" name="q" tabindex="1" size="100" {% if q %}value="{{ q }}"{% endif %}/> <input type="submit" value="" id="search_submit" /> diff --git a/searx/templates/simple/infobox.html b/searx/templates/simple/infobox.html index 08daa5038..56c51af77 100644 --- a/searx/templates/simple/infobox.html +++ b/searx/templates/simple/infobox.html @@ -33,7 +33,7 @@ <div> <h3><bdi>{{ topic.name }}</bdi></h3> {% for suggestion in topic.suggestions %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <input type="hidden" name="q" value="{{ suggestion }}"> <input type="hidden" name="time_range" value="{{ time_range }}"> <input type="hidden" name="language" value="{{ current_language }}"> diff --git a/searx/templates/simple/results.html b/searx/templates/simple/results.html index 2e393193f..936de8831 100644 --- a/searx/templates/simple/results.html +++ b/searx/templates/simple/results.html @@ -1,7 +1,7 @@ {% extends "simple/base.html" %} {% from 'simple/macros.html' import icon, icon_small %} {% block title %}{% if method == 'GET' %}{{- q|e -}} -{% endif %}{% endblock %} -{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ url_for('index') }}?q={{ q|urlencode }}&categories={{ selected_categories|join(",") | replace(' ','+') }}&pageno={{ pageno }}&time_range={{ time_range }}&language={{ current_language }}&safesearch={{ safesearch }}&format=rss">{% endblock %} +{% block meta %}<link rel="alternate" type="application/rss+xml" title="Searx search: {{ q|e }}" href="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}&categories={{ selected_categories|join(",") | replace(' ','+') }}&pageno={{ pageno }}&time_range={{ time_range }}&language={{ current_language }}&safesearch={{ safesearch }}&format=rss">{% endblock %} {% block content %} <nav id="linkto_preferences"><a href="{{ url_for('preferences') }}">{{ icon('navicon-round') }}</a></nav> {% include 'simple/search.html' %} @@ -55,7 +55,7 @@ <h4 class="title">{{ _('Suggestions') }} : </h4> <div class="wrapper"> {% for suggestion in suggestions %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <input type="hidden" name="q" value="{{ suggestion.url }}"> <input type="hidden" name="time_range" value="{{ time_range }}"> <input type="hidden" name="language" value="{{ current_language }}"> @@ -71,13 +71,13 @@ <div id="search_url"> <h4 class="title">{{ _('Search URL') }} :</h4> - <div class="selectable_url"><pre>{{ base_url }}?q={{ q|urlencode }}&language={{ current_language }}&time_range={{ time_range }}&safesearch={{ safesearch }}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if timeout_limit %}&timeout_limit={{ timeout_limit|urlencode }}{% endif %}</pre></div> + <div class="selectable_url"><pre>{{ url_for('search', _external=True) }}?q={{ q|urlencode }}&language={{ current_language }}&time_range={{ time_range }}&safesearch={{ safesearch }}{% if pageno > 1 %}&pageno={{ pageno }}{% endif %}{% if selected_categories %}&categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if timeout_limit %}&timeout_limit={{ timeout_limit|urlencode }}{% endif %}</pre></div> </div> <div id="apis"> <h4 class="title">{{ _('Download results') }}</h4> {% for output_type in ('csv', 'json', 'rss') %} <div class="left"> - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <input type="hidden" name="q" value="{{ q|e }}"> {% for category in selected_categories %} <input type="hidden" name="category_{{ category }}" value="1"> @@ -100,7 +100,7 @@ <h4>{{ _('Try searching for:') }}</h4> {% for correction in corrections %} <div class="left"> - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}" role="navigation"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" role="navigation"> <input type="hidden" name="q" value="{{ correction.url }}"> <input type="hidden" name="time_range" value="{{ time_range }}"> <input type="hidden" name="language" value="{{ current_language }}"> @@ -133,7 +133,7 @@ {% if paging %} <nav id="pagination"> {% if pageno > 1 %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="{% if rtl %}right{% else %}left{% endif %}"> <input type="hidden" name="q" value="{{ q|e }}" > {% for category in selected_categories %} @@ -149,7 +149,7 @@ </div> </form> {% endif %} - <form method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> + <form method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div class="{% if rtl %}left{% else %}right{% endif %}"> <input type="hidden" name="q" value="{{ q|e }}" > {% for category in selected_categories %} diff --git a/searx/templates/simple/search.html b/searx/templates/simple/search.html index 61d52dbc7..176e7909e 100644 --- a/searx/templates/simple/search.html +++ b/searx/templates/simple/search.html @@ -1,4 +1,4 @@ -<form id="search" method="{{ method or 'POST' }}" action="{{ url_for('index') }}"> +<form id="search" method="{{ method or 'POST' }}" action="{{ url_for('search') }}"> <div id="search_wrapper"> <div class="search_box"> <input id="q" autofocus name="q" type="text" placeholder="{{ _('Search for...') }}" tabindex="1" autocomplete="off" spellcheck="false" dir="auto" {% if q %}value="{{ q }}"{% endif %} > diff --git a/searx/webadapter.py b/searx/webadapter.py index e2867d99d..7c71b7262 100644 --- a/searx/webadapter.py +++ b/searx/webadapter.py @@ -122,9 +122,6 @@ def parse_specific(raw_text_query: RawTextQuery) -> Tuple[List[EngineRef], List[ def parse_category_form(query_categories: List[str], name: str, value: str) -> None: - if is_locked('categories'): - return preferences.get_value('categories') - if name == 'categories': query_categories.extend(categ for categ in map(str.strip, value.split(',')) if categ in categories) elif name.startswith('category_'): @@ -145,7 +142,7 @@ def parse_category_form(query_categories: List[str], name: str, value: str) -> N def get_selected_categories(preferences: Preferences, form: Optional[Dict[str, str]]) -> List[str]: selected_categories = [] - if form is not None: + if not is_locked('categories') and form is not None: for name, value in form.items(): parse_category_form(selected_categories, name, value) @@ -181,15 +178,17 @@ def parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engin # set categories/engines explicit_engine_list = False - for pd_name, pd in form.items(): - if pd_name == 'engines': - pd_engines = [EngineRef(engine_name, engines[engine_name].categories[0]) - for engine_name in map(str.strip, pd.split(',')) if engine_name in engines] - if pd_engines: - query_engineref_list.extend(pd_engines) - explicit_engine_list = True - else: - parse_category_form(query_categories, pd_name, pd) + if not is_locked('categories'): + # parse the form only if the categories are not locked + for pd_name, pd in form.items(): + if pd_name == 'engines': + pd_engines = [EngineRef(engine_name, engines[engine_name].categories[0]) + for engine_name in map(str.strip, pd.split(',')) if engine_name in engines] + if pd_engines: + query_engineref_list.extend(pd_engines) + explicit_engine_list = True + else: + parse_category_form(query_categories, pd_name, pd) if explicit_engine_list: # explicit list of engines with the "engines" parameter in the form @@ -234,7 +233,7 @@ def get_search_query_from_webapp(preferences: Preferences, form: Dict[str, str]) query_timeout = parse_timeout(form, raw_text_query) external_bang = raw_text_query.external_bang - if raw_text_query.enginerefs and raw_text_query.specific: + if not is_locked('categories') and raw_text_query.enginerefs and raw_text_query.specific: # if engines are calculated from query, # set categories by using that informations query_engineref_list, query_categories = parse_specific(raw_text_query) diff --git a/searx/webapp.py b/searx/webapp.py index 168829db4..326200cec 100755 --- a/searx/webapp.py +++ b/searx/webapp.py @@ -488,6 +488,16 @@ def pre_request(): @app.after_request +def add_default_headers(response): + # set default http headers + for header, value in settings['server'].get('default_http_headers', {}).items(): + if header in response.headers: + continue + response.headers[header] = value + return response + + +@app.after_request def post_request(response): total_time = time() - request.start_time timings_all = ['total;dur=' + str(round(total_time * 1000, 3))] @@ -531,10 +541,24 @@ def index_error(output_format, error_message): ) -@app.route('/search', methods=['GET', 'POST']) @app.route('/', methods=['GET', 'POST']) def index(): - """Render index page. + """Render index page.""" + + # redirect to search if there's a query in the request + if request.form.get('q'): + query = ('?' + request.query_string.decode()) if request.query_string else '' + return redirect(url_for('search') + query, 308) + + return render( + 'index.html', + selected_categories=get_selected_categories(request.preferences, request.form), + ) + + +@app.route('/search', methods=['GET', 'POST']) +def search(): + """Search query in q and return results. Supported outputs: html, json, csv, rss. """ @@ -544,8 +568,8 @@ def index(): if output_format not in ['html', 'csv', 'json', 'rss']: output_format = 'html' - # check if there is query - if request.form.get('q') is None: + # check if there is query (not None and not an empty string) + if not request.form.get('q'): if output_format == 'html': return render( 'index.html', diff --git a/tests/unit/test_search.py b/tests/unit/test_search.py index 36135913c..464a9b37d 100644 --- a/tests/unit/test_search.py +++ b/tests/unit/test_search.py @@ -21,6 +21,20 @@ TEST_ENGINES = [ ] +class SearchQueryTestCase(SearxTestCase): + + def test_repr(self): + s = SearchQuery('test', [EngineRef('bing', 'general', False)], ['general'], 'all', 0, 1, '1', 5.0, 'g') + self.assertEqual(repr(s), + "SearchQuery('test', [EngineRef('bing', 'general', False)], ['general'], 'all', 0, 1, '1', 5.0, 'g')") # noqa + + def test_eq(self): + s = SearchQuery('test', [EngineRef('bing', 'general', False)], ['general'], 'all', 0, 1, None, None, None) + t = SearchQuery('test', [EngineRef('google', 'general', False)], ['general'], 'all', 0, 1, None, None, None) + self.assertEqual(s, s) + self.assertNotEqual(s, t) + + class SearchTestCase(SearxTestCase): @classmethod diff --git a/tests/unit/test_standalone_searx.py b/tests/unit/test_standalone_searx.py new file mode 100644 index 000000000..c00f033b6 --- /dev/null +++ b/tests/unit/test_standalone_searx.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +"""Test utils/standalone_searx.py""" +import datetime +import importlib.util +import sys + +from mock import Mock, patch +from nose2.tools import params + +from searx.search import SearchQuery +from searx.testing import SearxTestCase + + +def get_standalone_searx_module(): + """Get standalone_searx module.""" + module_name = 'utils.standalone_searx' + filename = 'utils/standalone_searx.py' + spec = importlib.util.spec_from_file_location(module_name, filename) + sas = importlib.util.module_from_spec(spec) + spec.loader.exec_module(sas) + return sas + + +class StandaloneSearx(SearxTestCase): + """Unit test for standalone_searx.""" + + def test_parse_argument_no_args(self): + """Test parse argument without args.""" + sas = get_standalone_searx_module() + with patch.object(sys, 'argv', ['standalone_searx']), \ + self.assertRaises(SystemExit): + sas.parse_argument() + + def test_parse_argument_basic_args(self): + """Test parse argument with basic args.""" + sas = get_standalone_searx_module() + query = 'red box' + exp_dict = { + 'query': query, 'category': 'general', 'lang': 'all', 'pageno': 1, + 'safesearch': '0', 'timerange': None} + args = ['standalone_searx', query] + with patch.object(sys, 'argv', args): + res = sas.parse_argument() + self.assertEqual(exp_dict, vars(res)) + res2 = sas.parse_argument(args[1:]) + self.assertEqual(exp_dict, vars(res2)) + + def test_to_dict(self): + """test to_dict.""" + sas = get_standalone_searx_module() + self.assertEqual( + sas.to_dict( + sas.get_search_query(sas.parse_argument(['red box']))), + { + 'search': { + 'q': 'red box', 'pageno': 1, 'lang': 'all', + 'safesearch': 0, 'timerange': None + }, + 'results': [], 'infoboxes': [], 'suggestions': [], + 'answers': [], 'paging': False, 'results_number': 0 + } + ) + + def test_to_dict_with_mock(self): + """test to dict.""" + sas = get_standalone_searx_module() + with patch.object(sas.searx.search, 'Search') as mock_s: + m_search = mock_s().search() + m_sq = Mock() + self.assertEqual( + sas.to_dict(m_sq), + { + 'answers': [], + 'infoboxes': m_search.infoboxes, + 'paging': m_search.paging, + 'results': m_search.get_ordered_results(), + 'results_number': m_search.results_number(), + 'search': { + 'lang': m_sq.lang, + 'pageno': m_sq.pageno, + 'q': m_sq.query, + 'safesearch': m_sq.safesearch, + 'timerange': m_sq.time_range, + }, + 'suggestions': [] + } + ) + + def test_get_search_query(self): + """test get_search_query.""" + sas = get_standalone_searx_module() + args = sas.parse_argument(['rain', ]) + search_q = sas.get_search_query(args) + self.assertTrue(search_q) + self.assertEqual(search_q, SearchQuery('rain', [], ['general'], 'all', 0, 1, None, None, None)) + + def test_no_parsed_url(self): + """test no_parsed_url func""" + sas = get_standalone_searx_module() + self.assertEqual( + sas.no_parsed_url([{'parsed_url': 'http://example.com'}]), + [{}] + ) + + @params( + (datetime.datetime(2020, 1, 1), '2020-01-01T00:00:00'), + ('a'.encode('utf8'), 'a'), + (set([1]), [1]) + ) + def test_json_serial(self, arg, exp_res): + """test json_serial func""" + sas = get_standalone_searx_module() + self.assertEqual(sas.json_serial(arg), exp_res) + + def test_json_serial_error(self): + """test error on json_serial.""" + sas = get_standalone_searx_module() + with self.assertRaises(TypeError): + sas.json_serial('a') diff --git a/tests/unit/test_webapp.py b/tests/unit/test_webapp.py index 7dd465898..75a968ad8 100644 --- a/tests/unit/test_webapp.py +++ b/tests/unit/test_webapp.py @@ -75,8 +75,36 @@ class ViewsTestCase(SearxTestCase): self.assertEqual(result.status_code, 200) self.assertIn(b'<div class="title"><h1>searx</h1></div>', result.data) - def test_index_html(self): + def test_index_html_post(self): result = self.app.post('/', data={'q': 'test'}) + self.assertEqual(result.status_code, 308) + self.assertEqual(result.location, 'http://localhost/search') + + def test_index_html_get(self): + result = self.app.post('/?q=test') + self.assertEqual(result.status_code, 308) + self.assertEqual(result.location, 'http://localhost/search?q=test') + + def test_search_empty_html(self): + result = self.app.post('/search', data={'q': ''}) + self.assertEqual(result.status_code, 200) + self.assertIn(b'<div class="title"><h1>searx</h1></div>', result.data) + + def test_search_empty_json(self): + result = self.app.post('/search', data={'q': '', 'format': 'json'}) + self.assertEqual(result.status_code, 400) + + def test_search_empty_csv(self): + result = self.app.post('/search', data={'q': '', 'format': 'csv'}) + self.assertEqual(result.status_code, 400) + + def test_search_empty_rss(self): + result = self.app.post('/search', data={'q': '', 'format': 'rss'}) + self.assertEqual(result.status_code, 400) + + def test_search_html(self): + result = self.app.post('/search', data={'q': 'test'}) + self.assertIn( b'<h3 class="result_title"><img width="14" height="14" class="favicon" src="/static/themes/legacy/img/icons/icon_youtube.ico" alt="youtube" /><a href="http://second.test.xyz" rel="noreferrer">Second <span class="highlight">Test</span></a></h3>', # noqa result.data @@ -88,7 +116,10 @@ class ViewsTestCase(SearxTestCase): def test_index_json(self): result = self.app.post('/', data={'q': 'test', 'format': 'json'}) + self.assertEqual(result.status_code, 308) + def test_search_json(self): + result = self.app.post('/search', data={'q': 'test', 'format': 'json'}) result_dict = json.loads(result.data.decode()) self.assertEqual('test', result_dict['query']) @@ -98,6 +129,10 @@ class ViewsTestCase(SearxTestCase): def test_index_csv(self): result = self.app.post('/', data={'q': 'test', 'format': 'csv'}) + self.assertEqual(result.status_code, 308) + + def test_search_csv(self): + result = self.app.post('/search', data={'q': 'test', 'format': 'csv'}) self.assertEqual( b'title,url,content,host,engine,score,type\r\n' @@ -108,6 +143,10 @@ class ViewsTestCase(SearxTestCase): def test_index_rss(self): result = self.app.post('/', data={'q': 'test', 'format': 'rss'}) + self.assertEqual(result.status_code, 308) + + def test_index_rss(self): + result = self.app.post('/search', data={'q': 'test', 'format': 'rss'}) self.assertIn( b'<description>Search results for "test" - searx</description>', diff --git a/utils/standalone_searx.py b/utils/standalone_searx.py index 3aab7a6cc..0a35cc4a2 100755 --- a/utils/standalone_searx.py +++ b/utils/standalone_searx.py @@ -1,5 +1,63 @@ #!/usr/bin/env python +"""Script to run searx from terminal. +Getting categories without initiate the engine will only return `['general']` + +>>> import searx.engines +... list(searx.engines.categories.keys()) +['general'] +>>> import searx +... searx.engines.initialize_engines(searx.settings['engines']) +... list(searx.engines.categories.keys()) +['general', 'it', 'science', 'images', 'news', 'videos', 'music', 'files', 'social media', 'map'] + +Example to use this script: + +.. code:: bash + + $ SEARX_DEBUG=1 python3 utils/standalone_searx.py rain + +Example to run it from python: + +>>> import importlib +... import json +... import sys +... import searx +... import searx.engines +... search_query = 'rain' +... # initialize engines +... searx.engines.initialize_engines(searx.settings['engines']) +... # load engines categories once instead of each time the function called +... engine_cs = list(searx.engines.categories.keys()) +... # load module +... spec = importlib.util.spec_from_file_location( +... 'utils.standalone_searx', 'utils/standalone_searx.py') +... sas = importlib.util.module_from_spec(spec) +... spec.loader.exec_module(sas) +... # use function from module +... prog_args = sas.parse_argument([search_query], category_choices=engine_cs) +... search_q = sas.get_search_query(prog_args, engine_categories=engine_cs) +... res_dict = sas.to_dict(search_q) +... sys.stdout.write(json.dumps( +... res_dict, sort_keys=True, indent=4, ensure_ascii=False, +... default=sas.json_serial)) +{ + "answers": [], + "infoboxes": [ {...} ], + "paging": true, + "results": [... ], + "results_number": 820000000.0, + "search": { + "lang": "all", + "pageno": 1, + "q": "rain", + "safesearch": 0, + "timerange": null + }, + "suggestions": [...] +} +""" # noqa: E501 +# pylint: disable=pointless-string-statement ''' 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 @@ -16,90 +74,145 @@ along with searx. If not, see < http://www.gnu.org/licenses/ >. (C) 2016- by Alexandre Flament, <alex@al-f.net> ''' - -# set path -from sys import path -from os.path import realpath, dirname -path.append(realpath(dirname(realpath(__file__)) + '/../')) - -# initialization -from json import dumps -from searx import settings +# pylint: disable=wrong-import-position +import argparse import sys -import codecs -import searx.query -import searx.search +from datetime import datetime +from json import dumps +from typing import Any, Dict, List, Optional + +import searx import searx.engines -import searx.webapdater import searx.preferences +import searx.query +import searx.search import searx.webadapter -import argparse -searx.engines.initialize_engines(settings['engines']) - -# command line parsing -parser = argparse.ArgumentParser(description='Standalone searx.') -parser.add_argument('query', type=str, - help='Text query') -parser.add_argument('--category', type=str, nargs='?', - choices=searx.engines.categories.keys(), - default='general', - help='Search category') -parser.add_argument('--lang', type=str, nargs='?',default='all', - help='Search language') -parser.add_argument('--pageno', type=int, nargs='?', default=1, - help='Page number starting from 1') -parser.add_argument('--safesearch', type=str, nargs='?', choices=['0', '1', '2'], default='0', - help='Safe content filter from none to strict') -parser.add_argument('--timerange', type=str, nargs='?', choices=['day', 'week', 'month', 'year'], - help='Filter by time range') -args = parser.parse_args() - -# search results for the query -form = { - "q":args.query, - "categories":args.category.decode(), - "pageno":str(args.pageno), - "language":args.lang, - "time_range":args.timerange -} -preferences = searx.preferences.Preferences(['oscar'], searx.engines.categories.keys(), searx.engines.engines, []) -preferences.key_value_settings['safesearch'].parse(args.safesearch) +EngineCategoriesVar = Optional[List[str]] -search_query, raw_text_query, _, _ = searx.webadapter.get_search_query_from_webapp(preferences, form) -search = searx.search.Search(search_query) -result_container = search.search() -# output -from datetime import datetime +def get_search_query( + args: argparse.Namespace, engine_categories: EngineCategoriesVar = None +) -> searx.search.SearchQuery: + """Get search results for the query""" + if engine_categories is None: + engine_categories = list(searx.engines.categories.keys()) + try: + category = args.category.decode('utf-8') + except AttributeError: + category = args.category + form = { + "q": args.query, + "categories": category, + "pageno": str(args.pageno), + "language": args.lang, + "time_range": args.timerange + } + preferences = searx.preferences.Preferences( + ['oscar'], engine_categories, searx.engines.engines, []) + preferences.key_value_settings['safesearch'].parse(args.safesearch) + + search_query = searx.webadapter.get_search_query_from_webapp( + preferences, form)[0] + return search_query -def no_parsed_url(results): + +def no_parsed_url(results: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Remove parsed url from dict.""" for result in results: del result['parsed_url'] return results -def json_serial(obj): - """JSON serializer for objects not serializable by default json code""" + +def json_serial(obj: Any) -> Any: + """JSON serializer for objects not serializable by default json code. + + :raise TypeError: raised when **obj** is not serializable + """ if isinstance(obj, datetime): serial = obj.isoformat() return serial - raise TypeError ("Type not serializable") + if isinstance(obj, bytes): + return obj.decode('utf8') + if isinstance(obj, set): + return list(obj) + raise TypeError("Type ({}) not serializable".format(type(obj))) -result_container_json = { - "search": { - "q": search_query.query, - "pageno": search_query.pageno, - "lang": search_query.lang, - "safesearch": search_query.safesearch, - "timerange": search_query.time_range, - "engines": search_query.engines - }, - "results": no_parsed_url(result_container.get_ordered_results()), - "infoboxes": result_container.infoboxes, - "suggestions": list(result_container.suggestions), - "answers": list(result_container.answers), - "paging": result_container.paging, - "results_number": result_container.results_number() -} -sys.stdout = codecs.getwriter("UTF-8")(sys.stdout) -sys.stdout.write(dumps(result_container_json, sort_keys=True, indent=4, ensure_ascii=False, encoding="utf-8", default=json_serial)) + +def to_dict(search_query: searx.search.SearchQuery) -> Dict[str, Any]: + """Get result from parsed arguments.""" + result_container = searx.search.Search(search_query).search() + result_container_json = { + "search": { + "q": search_query.query, + "pageno": search_query.pageno, + "lang": search_query.lang, + "safesearch": search_query.safesearch, + "timerange": search_query.time_range, + }, + "results": no_parsed_url(result_container.get_ordered_results()), + "infoboxes": result_container.infoboxes, + "suggestions": list(result_container.suggestions), + "answers": list(result_container.answers), + "paging": result_container.paging, + "results_number": result_container.results_number() + } + return result_container_json + + +def parse_argument( + args: Optional[List[str]]=None, + category_choices: EngineCategoriesVar=None +) -> argparse.Namespace: + """Parse command line. + + :raise SystemExit: Query argument required on `args` + + Examples: + + >>> import importlib + ... # load module + ... spec = importlib.util.spec_from_file_location( + ... 'utils.standalone_searx', 'utils/standalone_searx.py') + ... sas = importlib.util.module_from_spec(spec) + ... spec.loader.exec_module(sas) + ... sas.parse_argument() + usage: ptipython [-h] [--category [{general}]] [--lang [LANG]] [--pageno [PAGENO]] [--safesearch [{0,1,2}]] [--timerange [{day,week,month,year}]] + query + SystemExit: 2 + >>> sas.parse_argument(['rain']) + Namespace(category='general', lang='all', pageno=1, query='rain', safesearch='0', timerange=None) + """ # noqa: E501 + if not category_choices: + category_choices = list(searx.engines.categories.keys()) + parser = argparse.ArgumentParser(description='Standalone searx.') + parser.add_argument('query', type=str, + help='Text query') + parser.add_argument('--category', type=str, nargs='?', + choices=category_choices, + default='general', + help='Search category') + parser.add_argument('--lang', type=str, nargs='?', default='all', + help='Search language') + parser.add_argument('--pageno', type=int, nargs='?', default=1, + help='Page number starting from 1') + parser.add_argument( + '--safesearch', type=str, nargs='?', + choices=['0', '1', '2'], default='0', + help='Safe content filter from none to strict') + parser.add_argument( + '--timerange', type=str, + nargs='?', choices=['day', 'week', 'month', 'year'], + help='Filter by time range') + return parser.parse_args(args) + + +if __name__ == '__main__': + searx.engines.initialize_engines(searx.settings['engines']) + engine_cs = list(searx.engines.categories.keys()) + prog_args = parse_argument(category_choices=engine_cs) + search_q = get_search_query(prog_args, engine_categories=engine_cs) + res_dict = to_dict(search_q) + sys.stdout.write(dumps( + res_dict, sort_keys=True, indent=4, ensure_ascii=False, + default=json_serial)) |