summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/admin/installation-searx.rst2
-rw-r--r--docs/conf.py1
-rw-r--r--docs/dev/makefile.rst2
-rw-r--r--docs/utils/index.rst1
-rw-r--r--docs/utils/standalone_searx.py.rst11
-rw-r--r--searx/engines/command.py4
-rw-r--r--searx/engines/scanr_structures.py2
-rw-r--r--searx/search.py24
-rw-r--r--searx/settings.yml6
-rw-r--r--searx/static/plugins/js/infinite_scroll.js2
-rw-r--r--searx/templates/__common__/opensearch.xml4
-rw-r--r--searx/templates/__common__/opensearch_response_rss.xml2
-rw-r--r--searx/templates/courgette/results.html12
-rw-r--r--searx/templates/courgette/search.html4
-rw-r--r--searx/templates/legacy/infobox.html2
-rw-r--r--searx/templates/legacy/results.html12
-rw-r--r--searx/templates/legacy/search.html2
-rw-r--r--searx/templates/oscar/results.html16
-rw-r--r--searx/templates/oscar/search.html2
-rw-r--r--searx/templates/oscar/search_full.html2
-rw-r--r--searx/templates/pix-art/search.html2
-rw-r--r--searx/templates/simple/infobox.html2
-rw-r--r--searx/templates/simple/results.html14
-rw-r--r--searx/templates/simple/search.html2
-rw-r--r--searx/webadapter.py27
-rwxr-xr-xsearx/webapp.py32
-rw-r--r--tests/unit/test_search.py14
-rw-r--r--tests/unit/test_standalone_searx.py119
-rw-r--r--tests/unit/test_webapp.py41
-rwxr-xr-xutils/standalone_searx.py255
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 }}&amp;format=rss&amp;{% for category in selected_categories %}category_{{ category }}=1&amp;{% 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 }}&amp;format=rss&amp;{% for category in selected_categories %}category_{{ category }}=1&amp;{% 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 %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}" readonly />
+ <input type="text" value="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;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 }}&amp;format=rss&amp;{% for category in selected_categories %}category_{{ category }}=1&amp;{% 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 }}&amp;format=rss&amp;{% for category in selected_categories %}category_{{ category }}=1&amp;{% 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 %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}" readonly />
+ <input type="text" value="{{ url_for('search', _external=True) }}?q={{ q|urlencode }}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;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 %} &bull; {% endif %}<form method="{{ method or 'POST' }}" action="{{ url_for('index') }}">
+ {% if not first %} &bull; {% 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 %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}{% if time_range %}&amp;time_range={{ time_range }}{% endif %}{% if current_language != 'all' %}&amp;language={{ current_language }}{% endif %}{% endmacro -%}
+{%- macro search_url() %}{{ url_for('search', _external=True) }}?q={{ q|urlencode }}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}{% if time_range %}&amp;time_range={{ time_range }}{% endif %}{% if current_language != 'all' %}&amp;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() }}&amp;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 }}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}&amp;pageno={{ pageno }}&amp;time_range={{ time_range }}&amp;language={{ current_language }}&amp;safesearch={{ safesearch }}&amp;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 }}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}&amp;pageno={{ pageno }}&amp;time_range={{ time_range }}&amp;language={{ current_language }}&amp;safesearch={{ safesearch }}&amp;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 }}&amp;language={{ current_language }}&amp;time_range={{ time_range }}&amp;safesearch={{ safesearch }}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if timeout_limit %}&amp;timeout_limit={{ timeout_limit|urlencode }}{% endif %}</pre></div>
+ <div class="selectable_url"><pre>{{ url_for('search', _external=True) }}?q={{ q|urlencode }}&amp;language={{ current_language }}&amp;time_range={{ time_range }}&amp;safesearch={{ safesearch }}{% if pageno > 1 %}&amp;pageno={{ pageno }}{% endif %}{% if selected_categories %}&amp;categories={{ selected_categories|join(",") | replace(' ','+') }}{% endif %}{% if timeout_limit %}&amp;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))