mirror of
https://github.com/searxng/searxng.git
synced 2026-08-14 19:21:24 +00:00
Compare commits
1 Commits
dependabot
...
translatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf31fb508b |
8
.github/workflows/container.yml
vendored
8
.github/workflows/container.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: "ghcr.io"
|
registry: "ghcr.io"
|
||||||
username: "${{ github.repository_owner }}"
|
username: "${{ github.repository_owner }}"
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: "ghcr.io"
|
registry: "ghcr.io"
|
||||||
username: "${{ github.repository_owner }}"
|
username: "${{ github.repository_owner }}"
|
||||||
@@ -144,14 +144,14 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: "docker.io"
|
registry: "docker.io"
|
||||||
username: "${{ secrets.DOCKER_USER }}"
|
username: "${{ secrets.DOCKER_USER }}"
|
||||||
password: "${{ secrets.DOCKER_TOKEN }}"
|
password: "${{ secrets.DOCKER_TOKEN }}"
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4.5.2
|
||||||
with:
|
with:
|
||||||
registry: "ghcr.io"
|
registry: "ghcr.io"
|
||||||
username: "${{ github.repository_owner }}"
|
username: "${{ github.repository_owner }}"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
search:
|
search:
|
||||||
safe_search: 0
|
safe_search: 0
|
||||||
autocomplete: "duckduckgo"
|
autocomplete: ""
|
||||||
favicon_resolver: ""
|
favicon_resolver: ""
|
||||||
default_lang: ""
|
default_lang: ""
|
||||||
ban_time_on_fail: 5
|
ban_time_on_fail: 5
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
- ``2``: Strict
|
- ``2``: Strict
|
||||||
|
|
||||||
``autocomplete``:
|
``autocomplete``:
|
||||||
Existing autocomplete backends, set blank to turn it off.
|
Existing autocomplete backends, leave blank to turn it off.
|
||||||
|
|
||||||
- ``360search``
|
- ``360search``
|
||||||
- ``baidu``
|
- ``baidu``
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
limiter: false
|
limiter: false
|
||||||
public_instance: false
|
public_instance: false
|
||||||
image_proxy: false
|
image_proxy: false
|
||||||
method: "GET"
|
method: "POST"
|
||||||
default_http_headers:
|
default_http_headers:
|
||||||
X-Content-Type-Options : nosniff
|
X-Content-Type-Options : nosniff
|
||||||
X-Download-Options : noopen
|
X-Download-Options : noopen
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
|
|
||||||
``method`` : ``GET`` | ``POST``
|
``method`` : ``GET`` | ``POST``
|
||||||
|
|
||||||
HTTP method. By default, ``GET`` is used / The ``POST`` method has the
|
HTTP method. By defaults ``POST`` is used / The ``POST`` method has the
|
||||||
advantage with some browsers that the history is not saved, but
|
advantage with some WEB browsers that the history is not easy to read, but
|
||||||
there are also various disadvantages that sometimes **severely restrict the
|
there are also various disadvantages that sometimes **severely restrict the
|
||||||
ease of use for the end user** (e.g. back button to jump back to the previous
|
ease of use for the end user** (e.g. back button to jump back to the previous
|
||||||
search page and drag & drop of search term to new tabs do not work as
|
search page and drag & drop of search term to new tabs do not work as
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
.. _jina engine:
|
|
||||||
|
|
||||||
===========
|
|
||||||
Jina Engine
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. automodule:: searx.engines.jina
|
|
||||||
:members:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
.. _yandex api engine:
|
|
||||||
|
|
||||||
=================
|
|
||||||
Yandex Search API
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. automodule:: searx.engines.yandex_api
|
|
||||||
:members:
|
|
||||||
@@ -2,9 +2,9 @@ mock==5.2.0
|
|||||||
nose2[coverage_plugin]==0.16.0
|
nose2[coverage_plugin]==0.16.0
|
||||||
cov-core==1.15.0
|
cov-core==1.15.0
|
||||||
black==25.9.0
|
black==25.9.0
|
||||||
pylint==4.0.7
|
pylint==4.0.6
|
||||||
splinter==0.21.0
|
splinter==0.21.0
|
||||||
selenium==4.47.0
|
selenium==4.46.0
|
||||||
Sphinx==8.2.3;python_version <= "3.11"
|
Sphinx==8.2.3;python_version <= "3.11"
|
||||||
Sphinx==9.1.0; python_version > "3.11"
|
Sphinx==9.1.0; python_version > "3.11"
|
||||||
sphinx-issues==6.0.0
|
sphinx-issues==6.0.0
|
||||||
@@ -18,11 +18,11 @@ myst-parser==5.0.0
|
|||||||
linuxdoc==20260504
|
linuxdoc==20260504
|
||||||
aiounittest==1.5.0
|
aiounittest==1.5.0
|
||||||
yamllint==1.38.0
|
yamllint==1.38.0
|
||||||
wlc==2.1.1
|
wlc==2.1.0
|
||||||
coloredlogs==15.0.1
|
coloredlogs==15.0.1
|
||||||
docutils>=0.21.2;python_version <= "3.11"
|
docutils>=0.21.2;python_version <= "3.11"
|
||||||
docutils>=0.22.4; python_version > "3.11"
|
docutils>=0.22.4; python_version > "3.11"
|
||||||
parameterized==0.9.0
|
parameterized==0.9.0
|
||||||
granian[reload]==2.8.1
|
granian[reload]==2.7.9
|
||||||
basedpyright==1.39.9
|
basedpyright==1.39.9
|
||||||
types-lxml==2026.2.16
|
types-lxml==2026.2.16
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
granian==2.8.1
|
granian==2.7.9
|
||||||
granian[pname]==2.8.1
|
granian[pname]==2.7.9
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ sniffio==1.3.1
|
|||||||
valkey==6.1.1
|
valkey==6.1.1
|
||||||
markdown-it-py==4.2.0
|
markdown-it-py==4.2.0
|
||||||
msgspec==0.21.1
|
msgspec==0.21.1
|
||||||
typer==0.27.1
|
typer==0.27.0
|
||||||
isodate==0.7.2
|
isodate==0.7.2
|
||||||
whitenoise==6.12.0
|
whitenoise==6.12.0
|
||||||
typing-extensions==4.16.0
|
typing-extensions==4.16.0
|
||||||
|
|||||||
@@ -43,10 +43,9 @@ def init(_):
|
|||||||
|
|
||||||
def request(query: str, params: "OnlineParams"):
|
def request(query: str, params: "OnlineParams"):
|
||||||
params["url"] = f"{base_url}/api/{dogpile_categ}"
|
params["url"] = f"{base_url}/api/{dogpile_categ}"
|
||||||
params["headers"]["Origin"] = base_url
|
|
||||||
|
|
||||||
params["method"] = "POST"
|
params["method"] = "POST"
|
||||||
params["json"] = {"q": query, "qadf": safe_search_map[params["safesearch"]], "page": params["pageno"]}
|
params["json"] = {"q": query, "qadf": safe_search_map[params["safesearch"]], "page": params["pageno"]}
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
def response(resp: "SXNG_Response"):
|
def response(resp: "SXNG_Response"):
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
"""Jina is a search AI and part of Elastic, the company behind ElasticSearch.
|
|
||||||
|
|
||||||
The engine requires an API key, you can get one from the
|
|
||||||
`API dashboard <https://jina.ai/api-dashboard/>`_ without signup.
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
- name: jina
|
|
||||||
engine: jina
|
|
||||||
shortcut: ji
|
|
||||||
api_key: "jina_..."
|
|
||||||
jina_engine: reader
|
|
||||||
inactive: false
|
|
||||||
|
|
||||||
By default, Jina's own index is used. You can change that by setting a different :py:obj:`jina_engine`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import typing as t
|
|
||||||
from urllib.parse import urlencode
|
|
||||||
|
|
||||||
from dateutil import parser
|
|
||||||
from searx.result_types import EngineResults
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
|
||||||
from searx.extended_types import SXNG_Response
|
|
||||||
from searx.search.processors import OnlineParams
|
|
||||||
|
|
||||||
|
|
||||||
about = {
|
|
||||||
"website": "https://jina.ai",
|
|
||||||
"wikidata_id": None,
|
|
||||||
"official_api_documentation": "https://s.jina.ai/docs",
|
|
||||||
"use_official_api": True,
|
|
||||||
"require_api_key": True,
|
|
||||||
"results": "JSON",
|
|
||||||
}
|
|
||||||
|
|
||||||
categories = ["general"]
|
|
||||||
paging = True
|
|
||||||
|
|
||||||
jina_engine = "reader"
|
|
||||||
"""Search mode. Currently supported values are 'reader', 'google' and 'bing'."""
|
|
||||||
|
|
||||||
base_url = "https://s.jina.ai"
|
|
||||||
api_key: str | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def setup(_):
|
|
||||||
if not api_key:
|
|
||||||
raise ValueError("missing api key")
|
|
||||||
|
|
||||||
|
|
||||||
def request(query: str, params: "OnlineParams"):
|
|
||||||
# setting 'no-content' pushes the response time down to a third
|
|
||||||
args = {"q": query, "page": params["pageno"], "engine": jina_engine, "respondWith": "no-content"}
|
|
||||||
params["url"] = f"{base_url}/?{urlencode(args)}"
|
|
||||||
params["headers"].update(
|
|
||||||
{
|
|
||||||
"Accept": "application/json",
|
|
||||||
"Authorization": f"Bearer {api_key}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def response(resp: "SXNG_Response"):
|
|
||||||
res = EngineResults()
|
|
||||||
|
|
||||||
json_resp: dict[str, t.Any] = resp.json()
|
|
||||||
|
|
||||||
result: dict[str, str]
|
|
||||||
for result in json_resp["data"]:
|
|
||||||
published_date = None
|
|
||||||
if result.get("date"):
|
|
||||||
try:
|
|
||||||
published_date = parser.parse(result["date"])
|
|
||||||
except parser.ParserError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
res.add(
|
|
||||||
res.types.MainResult(
|
|
||||||
url=result["url"],
|
|
||||||
title=result["title"],
|
|
||||||
content=result["description"],
|
|
||||||
publishedDate=published_date,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return res
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
||||||
"""Yandex Search API (the official **paid** `Yandex Search API v2`_).
|
|
||||||
|
|
||||||
Unlike the :origin:`yandex <searx/engines/yandex.py>` engine (which scrapes the
|
|
||||||
public HTML interface and is prone to CAPTCHA blocking), this engine talks to
|
|
||||||
the official, paid Yandex Cloud Search API. It requires a Yandex Cloud account,
|
|
||||||
a *folder id* and an *API key*.
|
|
||||||
|
|
||||||
The API answers with a Base64-encoded XML document, which is decoded and
|
|
||||||
parsed here.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
The engine is inactive by default because it needs credentials. To enable it,
|
|
||||||
set ``inactive: false`` and add your ``api_key`` and ``yandex_folder_id`` to
|
|
||||||
:origin:`searx/settings.yml`:
|
|
||||||
|
|
||||||
.. code:: yaml
|
|
||||||
|
|
||||||
- name: yandex api
|
|
||||||
engine: yandex_api
|
|
||||||
shortcut: yda
|
|
||||||
categories: [general, web]
|
|
||||||
inactive: false
|
|
||||||
api_key: "" # Yandex Cloud API key (``Api-Key``)
|
|
||||||
yandex_folder_id: "" # Yandex Cloud folder id
|
|
||||||
# optional, see below:
|
|
||||||
yandex_default_language: en
|
|
||||||
|
|
||||||
.. _Yandex Search API v2:
|
|
||||||
https://aistudio.yandex.ru/docs/en/search-api/api-ref/WebSearch/search.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
import math
|
|
||||||
import typing as t
|
|
||||||
from base64 import b64decode
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from searx.exceptions import SearxEngineAPIException
|
|
||||||
from searx.result_types import EngineResults
|
|
||||||
from searx.utils import extract_text
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
|
||||||
from searx.extended_types import SXNG_Response
|
|
||||||
from searx.search.processors import OnlineParams
|
|
||||||
|
|
||||||
|
|
||||||
about = {
|
|
||||||
"website": "https://yandex.cloud/en/services/search-api",
|
|
||||||
"wikidata_id": "Q5281",
|
|
||||||
"official_api_documentation": "https://aistudio.yandex.ru/docs/en/search-api/api-ref/WebSearch/search.html",
|
|
||||||
"use_official_api": True,
|
|
||||||
"require_api_key": True,
|
|
||||||
"results": "XML",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Engine configuration
|
|
||||||
categories = ["general", "web"]
|
|
||||||
paging = True
|
|
||||||
safesearch = True
|
|
||||||
|
|
||||||
# Credentials, overwritten via settings.yml
|
|
||||||
api_key: str = ""
|
|
||||||
"""Yandex Cloud API key, passed as ``Authorization: Api-Key <api_key>``."""
|
|
||||||
|
|
||||||
yandex_folder_id: str = ""
|
|
||||||
"""Yandex Cloud folder id the API key belongs to."""
|
|
||||||
|
|
||||||
# Search tuning, overwritten via settings.yml
|
|
||||||
yandex_default_language: str = "en"
|
|
||||||
"""Default query language. It selects the Yandex search domain (e.g. yandex.ru
|
|
||||||
for ``ru``, yandex.com for ``en``) and the language of the search-result
|
|
||||||
notifications, but only as a fallback -- a request whose own locale matches
|
|
||||||
:py:obj:`language_map` overrides it. Must be one of its keys: ``ru``, ``be``,
|
|
||||||
``kk``, ``uk``, ``tr`` or ``en``."""
|
|
||||||
|
|
||||||
region: str = ""
|
|
||||||
"""Optional Yandex `region id`.
|
|
||||||
Only meaningful together with ``SEARCH_TYPE_RU``.
|
|
||||||
|
|
||||||
__ https://aistudio.yandex.ru/docs/en/search-api/reference/regions.html
|
|
||||||
"""
|
|
||||||
|
|
||||||
page_size: int = 10
|
|
||||||
"""Number of results requested per page."""
|
|
||||||
|
|
||||||
base_url = "https://searchapi.api.cloud.yandex.net/v2/web/search"
|
|
||||||
|
|
||||||
# searxng safesearch level -> Yandex familyMode
|
|
||||||
safesearch_map = {
|
|
||||||
0: "FAMILY_MODE_NONE",
|
|
||||||
1: "FAMILY_MODE_MODERATE",
|
|
||||||
2: "FAMILY_MODE_STRICT",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Map a query language to a (search_type, l10n) pair. It drives both the
|
|
||||||
# per-request override (when the query's locale matches) and the
|
|
||||||
# ``yandex_default_language`` default.
|
|
||||||
language_map = {
|
|
||||||
"ru": ("SEARCH_TYPE_RU", "LOCALIZATION_RU"),
|
|
||||||
"be": ("SEARCH_TYPE_BE", "LOCALIZATION_BE"),
|
|
||||||
"kk": ("SEARCH_TYPE_KK", "LOCALIZATION_KK"),
|
|
||||||
"uk": ("SEARCH_TYPE_RU", "LOCALIZATION_UK"),
|
|
||||||
"tr": ("SEARCH_TYPE_TR", "LOCALIZATION_TR"),
|
|
||||||
"en": ("SEARCH_TYPE_COM", "LOCALIZATION_EN"),
|
|
||||||
# Uzbek ('uz') is intentionally omitted: Yandex offers SEARCH_TYPE_UZ but no
|
|
||||||
# matching LOCALIZATION_UZ, so such queries fall back to the defaults above.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def setup(_):
|
|
||||||
"""Validate credentials and paging limits when the engine is loaded."""
|
|
||||||
if not api_key or not yandex_folder_id:
|
|
||||||
raise SearxEngineAPIException("missing 'api_key' and/or 'yandex_folder_id' in engine settings")
|
|
||||||
if not 1 <= page_size <= 100:
|
|
||||||
raise SearxEngineAPIException("'page_size' must be in the range 1..100 (Yandex 'groupsOnPage')")
|
|
||||||
if yandex_default_language not in language_map:
|
|
||||||
raise SearxEngineAPIException(f"'yandex_default_language' must be one of {sorted(language_map)}")
|
|
||||||
|
|
||||||
|
|
||||||
def request(query: str, params: "OnlineParams"):
|
|
||||||
|
|
||||||
# Yandex returns at most 250 results for a query.
|
|
||||||
max_page = math.ceil(250 / page_size)
|
|
||||||
if params["pageno"] > max_page:
|
|
||||||
params["url"] = None
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(query) > 400:
|
|
||||||
# Yandex rejects a 'queryText' longer than 400 characters; decline the
|
|
||||||
# request gracefully instead of provoking an API error.
|
|
||||||
params["url"] = None
|
|
||||||
return
|
|
||||||
|
|
||||||
lang = params["searxng_locale"].split("-")[0].lower()
|
|
||||||
req_search_type, req_l10n = language_map.get(lang, language_map[yandex_default_language])
|
|
||||||
|
|
||||||
body: dict[str, t.Any] = {
|
|
||||||
"query": {
|
|
||||||
"searchType": req_search_type,
|
|
||||||
"queryText": query,
|
|
||||||
"familyMode": safesearch_map[params["safesearch"]],
|
|
||||||
# the API uses a 0-based page index
|
|
||||||
"page": str(params["pageno"] - 1),
|
|
||||||
},
|
|
||||||
"groupSpec": {
|
|
||||||
"groupMode": "GROUP_MODE_FLAT",
|
|
||||||
"groupsOnPage": str(page_size),
|
|
||||||
"docsInGroup": "1",
|
|
||||||
},
|
|
||||||
"l10n": req_l10n,
|
|
||||||
"folderId": yandex_folder_id,
|
|
||||||
"responseFormat": "FORMAT_XML",
|
|
||||||
}
|
|
||||||
# Yandex accepts a 'region' only together with the Russian search type.
|
|
||||||
if region and req_search_type == "SEARCH_TYPE_RU":
|
|
||||||
body["region"] = region
|
|
||||||
|
|
||||||
params["method"] = "POST"
|
|
||||||
params["url"] = base_url
|
|
||||||
params["headers"]["Authorization"] = f"Api-Key {api_key}"
|
|
||||||
params["headers"]["Content-Type"] = "application/json"
|
|
||||||
params["json"] = body
|
|
||||||
|
|
||||||
|
|
||||||
def _raw_xml(resp: "SXNG_Response") -> bytes:
|
|
||||||
"""Extract and Base64-decode the XML payload out of the JSON envelope.
|
|
||||||
|
|
||||||
The synchronous ``/v2/web/search`` endpoint returns ``{"rawData":
|
|
||||||
"<base64>"}`` on success; HTTP errors are raised upstream via
|
|
||||||
``raise_for_httperror``.
|
|
||||||
"""
|
|
||||||
data: dict[str, t.Any] = resp.json()
|
|
||||||
|
|
||||||
raw_data = data.get("rawData")
|
|
||||||
if raw_data is None:
|
|
||||||
raise SearxEngineAPIException("Yandex Search API: no 'rawData' in response")
|
|
||||||
|
|
||||||
return b64decode(raw_data)
|
|
||||||
|
|
||||||
|
|
||||||
def response(resp: "SXNG_Response") -> EngineResults:
|
|
||||||
res = EngineResults()
|
|
||||||
|
|
||||||
dom = etree.fromstring(_raw_xml(resp)) # pylint: disable=c-extension-no-member
|
|
||||||
|
|
||||||
# An <error> inside <response> signals an application error. Code 15 simply
|
|
||||||
# means "nothing was found" and must not raise.
|
|
||||||
error = dom.find(".//response/error")
|
|
||||||
if error is not None:
|
|
||||||
if error.get("code") == "15":
|
|
||||||
return res
|
|
||||||
raise SearxEngineAPIException(f"Yandex Search API error {error.get('code')}: {error.text}")
|
|
||||||
|
|
||||||
for doc in dom.iterfind(".//doc"):
|
|
||||||
url = extract_text(doc.find("url"), allow_none=True)
|
|
||||||
title = extract_text(doc.find("title"), allow_none=True)
|
|
||||||
if not url or not title:
|
|
||||||
continue
|
|
||||||
|
|
||||||
content = extract_text(doc.find("headline"), allow_none=True)
|
|
||||||
if not content:
|
|
||||||
passages = doc.find("passages")
|
|
||||||
if passages is not None:
|
|
||||||
content = " ".join(extract_text(p) or "" for p in passages.iterfind("passage")).strip()
|
|
||||||
|
|
||||||
res.add(
|
|
||||||
res.types.MainResult(
|
|
||||||
url=url,
|
|
||||||
title=title,
|
|
||||||
content=content or "",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return res
|
|
||||||
@@ -41,9 +41,9 @@ search:
|
|||||||
# Filter results. 0: None, 1: Moderate, 2: Strict
|
# Filter results. 0: None, 1: Moderate, 2: Strict
|
||||||
safe_search: 0
|
safe_search: 0
|
||||||
# Existing autocomplete backends: "360search", "baidu", "bing", "brave", "dbpedia", "duckduckgo", "google",
|
# Existing autocomplete backends: "360search", "baidu", "bing", "brave", "dbpedia", "duckduckgo", "google",
|
||||||
# "yandex", "privacywall", "mwmbl", "naver", "seznam", "sogou", "startpage", "swisscows", "quark", "qwant",
|
# "kagi", "yandex", "privacywall", "mwmbl", "naver", "seznam", "sogou", "startpage", "swisscows", "quark",
|
||||||
# "wikipedia" - set blank to turn it off by default.
|
# "qwant", "wikipedia" - leave blank to turn it off by default.
|
||||||
autocomplete: "duckduckgo"
|
autocomplete: ""
|
||||||
# minimun characters to type before autocompleter starts
|
# minimun characters to type before autocompleter starts
|
||||||
autocomplete_min: 4
|
autocomplete_min: 4
|
||||||
# backend for the favicon near URL in search results.
|
# backend for the favicon near URL in search results.
|
||||||
@@ -107,10 +107,11 @@ server:
|
|||||||
image_proxy: false
|
image_proxy: false
|
||||||
# 1.0 and 1.1 are supported
|
# 1.0 and 1.1 are supported
|
||||||
http_protocol_version: "1.0"
|
http_protocol_version: "1.0"
|
||||||
# POST keeps search queries out of the browsers history, but causes UX issues.
|
# POST queries are "more secure!" but are also the source of hard-to-locate
|
||||||
|
# annoyances, which is why GET may be better for end users and their browsers.
|
||||||
# see https://github.com/searxng/searxng/pull/3619
|
# see https://github.com/searxng/searxng/pull/3619
|
||||||
# Is overwritten by ${SEARXNG_METHOD}
|
# Is overwritten by ${SEARXNG_METHOD}
|
||||||
method: "GET"
|
method: "POST"
|
||||||
default_http_headers:
|
default_http_headers:
|
||||||
X-Content-Type-Options: nosniff
|
X-Content-Type-Options: nosniff
|
||||||
X-Download-Options: noopen
|
X-Download-Options: noopen
|
||||||
@@ -852,28 +853,28 @@ engines:
|
|||||||
shortcut: dog
|
shortcut: dog
|
||||||
dogpile_categ: search
|
dogpile_categ: search
|
||||||
categories: general
|
categories: general
|
||||||
inactive: true
|
disabled: true
|
||||||
|
|
||||||
- name: dogpile images
|
- name: dogpile images
|
||||||
engine: dogpile
|
engine: dogpile
|
||||||
shortcut: dogi
|
shortcut: dogi
|
||||||
dogpile_categ: images
|
dogpile_categ: images
|
||||||
categories: images
|
categories: images
|
||||||
inactive: true
|
disabled: true
|
||||||
|
|
||||||
- name: dogpile videos
|
- name: dogpile videos
|
||||||
engine: dogpile
|
engine: dogpile
|
||||||
shortcut: dogv
|
shortcut: dogv
|
||||||
dogpile_categ: videos
|
dogpile_categ: videos
|
||||||
categories: videos
|
categories: videos
|
||||||
inactive: true
|
disabled: true
|
||||||
|
|
||||||
- name: dogpile news
|
- name: dogpile news
|
||||||
engine: dogpile
|
engine: dogpile
|
||||||
shortcut: dogn
|
shortcut: dogn
|
||||||
dogpile_categ: news
|
dogpile_categ: news
|
||||||
categories: news
|
categories: news
|
||||||
inactive: true
|
disabled: true
|
||||||
|
|
||||||
# duckduckgo uses html.duckduckgo.com,
|
# duckduckgo uses html.duckduckgo.com,
|
||||||
# duckduckgo web uses duckduckgo.com
|
# duckduckgo web uses duckduckgo.com
|
||||||
@@ -1410,12 +1411,6 @@ engines:
|
|||||||
shortcut: iq
|
shortcut: iq
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
||||||
- name: jina
|
|
||||||
engine: jina
|
|
||||||
shortcut: ji
|
|
||||||
api_key: ""
|
|
||||||
inactive: true
|
|
||||||
|
|
||||||
# - name: kagi
|
# - name: kagi
|
||||||
# engine: kagi
|
# engine: kagi
|
||||||
# categories: [general, web]
|
# categories: [general, web]
|
||||||
@@ -2584,16 +2579,6 @@ engines:
|
|||||||
shortcut: ydi
|
shortcut: ydi
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
||||||
- name: yandex api
|
|
||||||
engine: yandex_api
|
|
||||||
categories: [general, web]
|
|
||||||
shortcut: yda
|
|
||||||
inactive: true
|
|
||||||
# api_key: ""
|
|
||||||
# folder_id: ""
|
|
||||||
# search_type: SEARCH_TYPE_COM
|
|
||||||
# l10n: LOCALIZATION_EN
|
|
||||||
|
|
||||||
- name: yandex music
|
- name: yandex music
|
||||||
engine: yandex_music
|
engine: yandex_music
|
||||||
network: yandex
|
network: yandex
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ SCHEMA: dict[str, t.Any] = {
|
|||||||
'brand': SettingsBrand,
|
'brand': SettingsBrand,
|
||||||
'search': {
|
'search': {
|
||||||
'safe_search': SettingsValue((0, 1, 2), 0),
|
'safe_search': SettingsValue((0, 1, 2), 0),
|
||||||
'autocomplete': SettingsValue(str, 'duckduckgo'),
|
'autocomplete': SettingsValue(str, ''),
|
||||||
'autocomplete_min': SettingsValue(int, 4),
|
'autocomplete_min': SettingsValue(int, 4),
|
||||||
'favicon_resolver': SettingsValue(str, ''),
|
'favicon_resolver': SettingsValue(str, ''),
|
||||||
'default_lang': SettingsValue(tuple(SXNG_LOCALE_TAGS + ['']), ''),
|
'default_lang': SettingsValue(tuple(SXNG_LOCALE_TAGS + ['']), ''),
|
||||||
@@ -219,7 +219,7 @@ SCHEMA: dict[str, t.Any] = {
|
|||||||
'base_url': SettingsValue((False, str), False, 'SEARXNG_BASE_URL'),
|
'base_url': SettingsValue((False, str), False, 'SEARXNG_BASE_URL'),
|
||||||
'image_proxy': SettingsValue(bool, False, 'SEARXNG_IMAGE_PROXY'),
|
'image_proxy': SettingsValue(bool, False, 'SEARXNG_IMAGE_PROXY'),
|
||||||
'http_protocol_version': SettingsValue(('1.0', '1.1'), '1.0'),
|
'http_protocol_version': SettingsValue(('1.0', '1.1'), '1.0'),
|
||||||
'method': SettingsValue(('POST', 'GET'), 'GET', 'SEARXNG_METHOD'),
|
'method': SettingsValue(('POST', 'GET'), 'POST', 'SEARXNG_METHOD'),
|
||||||
'default_http_headers': SettingsValue(dict, {}),
|
'default_http_headers': SettingsValue(dict, {}),
|
||||||
},
|
},
|
||||||
# redis is deprecated ..
|
# redis is deprecated ..
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{%- for output_type in search_formats -%}
|
{%- for output_type in search_formats -%}
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<form method="{{ method }}" action="{{ url_for('search') }}">
|
<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}">
|
||||||
<input type="hidden" name="q" value="{{ q|e }}">
|
<input type="hidden" name="q" value="{{ q|e }}">
|
||||||
{%- for category in selected_categories -%}
|
{%- for category in selected_categories -%}
|
||||||
<input type="hidden" name="category_{{ category }}" value="1">
|
<input type="hidden" name="category_{{ category }}" value="1">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<h4 id="corrections-title">{{ _('Try searching for:') }}</h4>
|
<h4 id="corrections-title">{{ _('Try searching for:') }}</h4>
|
||||||
{% for correction in corrections %}
|
{% for correction in corrections %}
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<form method="{{ method }}" action="{{ url_for('search') }}" role="navigation">
|
<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" role="navigation">
|
||||||
{% for category in selected_categories %}
|
{% for category in selected_categories %}
|
||||||
<input type="hidden" name="category_{{ category }}" value="1">
|
<input type="hidden" name="category_{{ category }}" value="1">
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h3><bdi>{{ topic.name }}</bdi></h3>
|
<h3><bdi>{{ topic.name }}</bdi></h3>
|
||||||
{%- for suggestion in topic.suggestions -%}
|
{%- for suggestion in topic.suggestions -%}
|
||||||
<form method="{{ method }}" action="{{ url_for('search') }}">
|
<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}">
|
||||||
<input type="hidden" name="q" value="{{ suggestion }}">
|
<input type="hidden" name="q" value="{{ suggestion }}">
|
||||||
<input type="hidden" name="time_range" value="{{ time_range }}">
|
<input type="hidden" name="time_range" value="{{ time_range }}">
|
||||||
<input type="hidden" name="language" value="{{ current_language }}">
|
<input type="hidden" name="language" value="{{ current_language }}">
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<summary class="title" id="suggestions-title">{{ _('Suggestions') }}</summary>
|
<summary class="title" id="suggestions-title">{{ _('Suggestions') }}</summary>
|
||||||
<ul class="wrapper">
|
<ul class="wrapper">
|
||||||
{%- for suggestion in suggestions -%}
|
{%- for suggestion in suggestions -%}
|
||||||
<li><form method="{{ method }}" action="{{ url_for('search') }}">
|
<li><form method="{{ method or 'POST' }}" action="{{ url_for('search') }}">
|
||||||
<input type="hidden" name="q" value="{{ suggestion.url }}">
|
<input type="hidden" name="q" value="{{ suggestion.url }}">
|
||||||
{%- for category in selected_categories -%}
|
{%- for category in selected_categories -%}
|
||||||
<input type="hidden" name="category_{{ category }}" value="1">
|
<input type="hidden" name="category_{{ category }}" value="1">
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
{% if paging %}
|
{% if paging %}
|
||||||
<nav id="pagination" role="navigation">
|
<nav id="pagination" role="navigation">
|
||||||
{% if pageno > 1 %}
|
{% if pageno > 1 %}
|
||||||
<form method="{{ method }}" action="{{ url_for('search') }}" class="previous_page">
|
<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="previous_page">
|
||||||
<div class="{% if rtl %}right{% else %}left{% endif %}">
|
<div class="{% if rtl %}right{% else %}left{% endif %}">
|
||||||
<input type="hidden" name="q" value="{{ q|e }}" >
|
<input type="hidden" name="q" value="{{ q|e }}" >
|
||||||
{% for category in selected_categories %}
|
{% for category in selected_categories %}
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- if results | count > 0 -%}
|
{%- if results | count > 0 -%}
|
||||||
<form method="{{ method }}" action="{{ url_for('search') }}" class="next_page">
|
<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="next_page">
|
||||||
<div class="{% if rtl %}left{% else %}right{% endif %}">
|
<div class="{% if rtl %}left{% else %}right{% endif %}">
|
||||||
<input type="hidden" name="q" value="{{ q|e }}" >
|
<input type="hidden" name="q" value="{{ q|e }}" >
|
||||||
{% for category in selected_categories %}
|
{% for category in selected_categories %}
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
|
|
||||||
<div class="numbered_pagination">
|
<div class="numbered_pagination">
|
||||||
{% for x in range(pstart, pend) %}
|
{% for x in range(pstart, pend) %}
|
||||||
<form method="{{ method }}" action="{{ url_for('search') }}" class="page_number">
|
<form method="{{ method or 'POST' }}" action="{{ url_for('search') }}" class="page_number">
|
||||||
<input type="hidden" name="q" value="{{ q|e }}" >
|
<input type="hidden" name="q" value="{{ q|e }}" >
|
||||||
{% for category in selected_categories %}
|
{% for category in selected_categories %}
|
||||||
<input type="hidden" name="category_{{ category }}" value="1" >
|
<input type="hidden" name="category_{{ category }}" value="1" >
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<form id="search" method="{{ method }}" action="{{ url_for('search') }}" role="search">
|
<form id="search" method="{{ method or 'POST' }}" action="{{ url_for('search') }}" role="search">
|
||||||
<div id="search_header">
|
<div id="search_header">
|
||||||
<a id="search_logo" href="{{ url_for('index') }}" tabindex="0" title="{{ _('Display the front page') }}">
|
<a id="search_logo" href="{{ url_for('index') }}" tabindex="0" title="{{ _('Display the front page') }}">
|
||||||
<span hidden>SearXNG</span>
|
<span hidden>SearXNG</span>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<form id="search" method="{{ method }}" action="{{ url_for('search') }}" role="search">
|
<form id="search" method="{{ method or 'POST' }}" action="{{ url_for('search') }}" role="search">
|
||||||
<div id="search_header">
|
<div id="search_header">
|
||||||
<div id="search_view">
|
<div id="search_view">
|
||||||
<div class="search_box">
|
<div class="search_box">
|
||||||
|
|||||||
@@ -1195,7 +1195,7 @@ def opensearch():
|
|||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
|
||||||
if method not in ('POST', 'GET'):
|
if method not in ('POST', 'GET'):
|
||||||
method = 'GET'
|
method = 'POST'
|
||||||
|
|
||||||
ret = render('opensearch.xml', opensearch_method=method, autocomplete=autocomplete)
|
ret = render('opensearch.xml', opensearch_method=method, autocomplete=autocomplete)
|
||||||
resp = Response(response=ret, status=200, mimetype="application/opensearchdescription+xml")
|
resp = Response(response=ret, status=200, mimetype="application/opensearchdescription+xml")
|
||||||
|
|||||||
Reference in New Issue
Block a user