19 Commits

Author SHA1 Message Date
Bnyro e6559c9ad6 [fix] gabanza: result URLs are invalid 2026-06-04 08:55:19 +02:00
Bnyro 5bae05514b [feat] engines: add zapmeta general search engine 2026-06-03 22:38:59 +02:00
Bnyro 00ca5776f2 [feat] engines: add gabanza general engine 2026-06-03 22:38:23 +02:00
Bnyro 577f5f2f30 [fix] online engines: send_accept_language_header is sent even if disabled 2026-06-03 22:37:13 +02:00
Bnyro 253dc86c10 [fix] duckduckgo: image requests get blocked 2026-06-03 22:37:13 +02:00
Bnyro 3066bc19eb [fix] public domain image archive: fails to extract API url 2026-06-03 22:35:21 +02:00
Austin-Olacsi e964708c00 [fix] bilibili engine: fix Referer and add Accept HTTP header (#6189) 2026-06-02 06:06:31 +02:00
Bnyro 7159b8aed3 [feat] marginalia: add support for pagination 2026-05-31 12:54:53 +02:00
Bnyro 246f5a5499 [mod] svgrepo: remove engine
- SVGRepo uses Cloudflare for every session, no matter
if you're opening it in a browser or not
2026-05-31 12:54:32 +02:00
vojkovic 300695de5c [fix] crash when lock is omitted 2026-05-31 01:37:37 +08:00
Markus Heiser bd863f16b1 [build] /static 2026-05-30 22:43:50 +08:00
Markus Heiser 4ac822fd7f [mod] typification of the preference settings
no functional change / except the missing online doc which is now available::

    $ make docs.live
    $ xdg-open "http://127.0.0.1:8000/admin/settings/settings_preferences.html"

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
2026-05-30 22:43:50 +08:00
vojkovic e1d25c5078 [mod] enable image proxy for public instances 2026-05-30 22:43:50 +08:00
dependabot[bot] 01159b82fe [upd] pypi: Bump the minor group with 3 updates (#6164)
Bumps the minor group with 3 updates: [granian](https://github.com/emmett-framework/granian), [basedpyright](https://github.com/detachhead/basedpyright) and [typer](https://github.com/fastapi/typer).


Updates `granian` from 2.7.4 to 2.7.5
- [Release notes](https://github.com/emmett-framework/granian/releases)
- [Commits](https://github.com/emmett-framework/granian/compare/v2.7.4...v2.7.5)

Updates `basedpyright` from 1.39.5 to 1.39.6
- [Release notes](https://github.com/detachhead/basedpyright/releases)
- [Commits](https://github.com/detachhead/basedpyright/compare/v1.39.5...v1.39.6)

Updates `typer` from 0.25.1 to 0.26.3
- [Release notes](https://github.com/fastapi/typer/releases)
- [Changelog](https://github.com/fastapi/typer/blob/master/docs/release-notes.md)
- [Commits](https://github.com/fastapi/typer/compare/0.25.1...0.26.3)
2026-05-30 11:33:10 +02:00
Bnyro 780ee32564 [fix] pexels: fix engine crashes with SearxEngineAccessDeniedException 2026-05-29 22:03:22 +02:00
github-actions[bot] 217c9a1597 [l10n] update translations from Weblate (#6170)
207f98ecc - 2026-05-26 - mustafa-phd <mustafa-phd@noreply.codeberg.org>
3b51fbca7 - 2026-05-25 - Amirkhandrend-Nicest-XII <amirkhandrend-nicest-xii@noreply.codeberg.org>

Co-authored-by: searxng-bot <searxng-bot@users.noreply.github.com>
2026-05-29 14:47:43 +02:00
dependabot[bot] 70e810bd7b [upd] github-actions: Bump docker/setup-qemu-action from 4.0.0 to 4.1.0 (#6166)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/ce360397dd3f832beb865e1373c09c0e9f86d70a...06116385d9baf250c9f4dcb4858b16962ea869c3)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 14:42:48 +02:00
dependabot[bot] baab1c160a [upd] github-actions: Bump github/codeql-action from 4.35.5 to 4.36.0 (#6167)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.5 to 4.36.0.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/9e0d7b8d25671d64c341c19c0152d693099fb5ba...7211b7c8077ea37d8641b6271f6a365a22a5fbfa)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 14:42:28 +02:00
dependabot[bot] dd4664e03a [upd] github-actions: Bump docker/login-action from 4.1.0 to 4.2.0 (#6168)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/4907a6ddec9925e35a0a9e82d7399ccc52663121...650006c6eb7dba73a995cc03b0b2d7f5ca915bee)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 14:41:49 +02:00
26 changed files with 206 additions and 158 deletions
+6 -6
View File
@@ -106,10 +106,10 @@ jobs:
- if: ${{ matrix.emulation }}
name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
@@ -147,10 +147,10 @@ jobs:
- if: ${{ matrix.emulation }}
name: Setup QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
@@ -180,14 +180,14 @@ jobs:
persist-credentials: "false"
- name: Login to GHCR
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "ghcr.io"
username: "${{ github.repository_owner }}"
password: "${{ secrets.GITHUB_TOKEN }}"
- name: Login to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: "docker.io"
username: "${{ secrets.DOCKER_USER }}"
+1 -1
View File
@@ -41,6 +41,6 @@ jobs:
write-comment: "false"
- name: Upload SARIFs
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
sarif_file: "./scout.sarif"
-1
View File
@@ -5,7 +5,6 @@ import type { KeyBindingLayout } from "./main/keyboard.ts";
// synced with searx/webapp.py get_client_settings
type Settings = {
plugins?: string[];
advanced_search?: boolean;
autocomplete?: string;
autocomplete_min?: number;
doi_resolver?: string;
+1
View File
@@ -19,6 +19,7 @@ Settings
settings_search
settings_server
settings_ui
settings_preferences
settings_redis
settings_valkey
settings_outgoing
@@ -0,0 +1,8 @@
.. _settings preferences:
================
``preferences:``
================
.. autoclass:: searx._settings.SettingsPref
:members:
+1
View File
@@ -47,6 +47,7 @@
activated:
- :py:obj:`searx.botdetection.link_token` in the :ref:`limiter`
- :ref:`image_proxy`
.. _image_proxy:
+2 -2
View File
@@ -23,6 +23,6 @@ coloredlogs==15.0.1
docutils>=0.21.2;python_version <= "3.11"
docutils>=0.22.4; python_version > "3.11"
parameterized==0.9.0
granian[reload]==2.7.4
basedpyright==1.39.5
granian[reload]==2.7.5
basedpyright==1.39.6
types-lxml==2026.2.16
+2 -2
View File
@@ -1,2 +1,2 @@
granian==2.7.4
granian[pname]==2.7.4
granian==2.7.5
granian[pname]==2.7.5
+1 -1
View File
@@ -13,7 +13,7 @@ sniffio==1.3.1
valkey==6.1.1
markdown-it-py==4.2.0
msgspec==0.21.1
typer==0.25.1
typer==0.26.3
isodate==0.7.2
whitenoise==6.12.0
typing-extensions==4.15.0
+8 -1
View File
@@ -10,6 +10,7 @@ from os.path import dirname, abspath
import logging
import msgspec
from ._settings import SettingsPref
# Debug
LOG_FORMAT_DEBUG: str = '%(levelname)-7s %(name)-30.30s: %(message)s'
@@ -47,6 +48,12 @@ def init_settings():
settings.clear()
settings.update(cfg)
if get_setting("server.public_instance"):
# enable image proxy for public instances #6125
settings["server"]["image_proxy"] = True
pref: SettingsPref = get_setting("preferences")
pref.lock.add("image_proxy")
sxng_debug = get_setting("general.debug")
if sxng_debug:
_logging_config_debug()
@@ -66,7 +73,7 @@ def init_settings():
if settings['server']['public_instance']:
logger.warning(
"Be aware you have activated features intended only for public instances. "
"This force the usage of the limiter and link_token / "
"This force the usage of the limiter, link_token and image proxy / "
"see https://docs.searxng.org/admin/searx.limiter.html"
)
+42
View File
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Implementation of the :py:obj:`preference <searx.preference>` settings."""
# pylint: disable = too-few-public-methods
import typing as t
import msgspec
class SettingsPref(msgspec.Struct, kw_only=True, forbid_unknown_fields=True):
"""Options for configuring the preferences
.. code:: yaml
preferences:
lock:
- favicon_resolver
- image_proxy
- method
# ...
"""
lock: set[
t.Literal[
"categories",
"language",
"locale",
"autocomplete",
"favicon_resolver",
"image_proxy",
"method",
"safesearch",
"theme",
"results_on_new_tab",
"doi_resolver",
"simple_style",
"center_alignment",
"query_in_title",
"search_on_category_select",
]
] = set()
"""Lock arbitrary settings on the preferences page."""
+2 -3
View File
@@ -51,11 +51,10 @@ def request(query, params):
}
params["url"] = f"{base_url}?{urlencode(query_params)}"
params["headers"]["Referer"] = "https://www.bilibili.com"
params["headers"]["Referer"] = "https://www.bilibili.com/"
params["headers"]["Accept"] = "application/json, text/javascript, */*; q=0.01"
params["cookies"] = cookie
return params
def response(resp):
search_res = resp.json()
+2
View File
@@ -41,7 +41,9 @@ safesearch_cookies = {0: "-2", 1: None, 2: "1"}
safesearch_args = {0: "1", 1: None, 2: "1"}
search_path_map = {"images": "i", "videos": "v", "news": "news"}
_HTTP_User_Agent: str = gen_useragent()
send_accept_language_header = False
def init(engine_settings: dict[str, t.Any]):
+7 -2
View File
@@ -45,7 +45,7 @@ about = {
base_url = "https://api2.marginalia-search.com"
safesearch = True
categories = ["general"]
paging = False
paging = True
results_per_page = 20
api_key = None
"""To get an API key, please follow the instructions from `Key and license`_
@@ -85,7 +85,12 @@ class ApiSearchResults(t.TypedDict):
def request(query: str, params: dict[str, t.Any]):
query_params = {"count": results_per_page, "nsfw": min(params["safesearch"], 1), "query": query}
query_params = {
"page": params["pageno"],
"count": results_per_page,
"nsfw": min(params["safesearch"], 1),
"query": query,
}
params["url"] = f"{base_url}/search?{urlencode(query_params)}"
params["headers"]["User-Agent"] = searxng_useragent()
+4 -2
View File
@@ -9,7 +9,7 @@ from lxml import html
from searx.result_types import EngineResults
from searx.utils import eval_xpath_list, gen_useragent
from searx.enginelib import EngineCache
from searx.exceptions import SearxEngineAPIException
from searx.exceptions import SearxEngineAPIException, SearxEngineAccessDeniedException
from searx.network import get
@@ -58,6 +58,8 @@ def _get_secret_key():
# circumvents Cloudflare bot protections
"User-Agent": gen_useragent(),
"Referer": base_url,
"Sec-GPC": "1",
"Connection": "keep-alive",
},
)
@@ -95,7 +97,7 @@ def request(query, params):
try:
secret_key = _get_secret_key()
CACHE.set(SECRET_KEY_DB_KEY, secret_key)
except SearxEngineAPIException as e:
except (SearxEngineAPIException, SearxEngineAccessDeniedException) as e:
logger.debug("failed to extract API key %s" % e)
secret_key = api_key
+8 -3
View File
@@ -1,6 +1,8 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Public domain image archive"""
import re
from urllib.parse import urlencode, urlparse, urlunparse, parse_qsl
from json import dumps
@@ -49,6 +51,8 @@ paging = True
__CACHED_API_URL = None
_API_URL_RE = re.compile(r"\"(https://.*?/search-proxy)\"")
def _clean_url(url):
parsed = urlparse(url)
@@ -74,11 +78,12 @@ def _get_algolia_api_url():
if resp.status_code != 200:
raise LookupError("Failed to obtain AWS api url for PDImageArchive")
api_url = extr(resp.text, 'const r="', '"', default=None)
if api_url is None:
api_url_match = _API_URL_RE.search(resp.text)
if api_url_match is None:
raise LookupError("Couldn't obtain AWS api url for PDImageArchive")
api_url = api_url_match.group(1)
__CACHED_API_URL = api_url
return api_url
-44
View File
@@ -1,44 +0,0 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Svgrepo (icons)"""
from lxml import html
from searx.utils import extract_text, eval_xpath, eval_xpath_list
about = {
"website": 'https://www.svgrepo.com',
"official_api_documentation": 'https://svgapi.com',
"use_official_api": False,
"require_api_key": False,
"results": 'HTML',
}
paging = True
categories = ['images', 'icons']
base_url = "https://www.svgrepo.com"
results_xpath = "//div[@class='style_nodeListing__7Nmro']/div"
url_xpath = ".//a/@href"
title_xpath = ".//a/@title"
img_src_xpath = ".//img/@src"
def request(query, params):
params['url'] = f"{base_url}/vectors/{query}/{params['pageno']}/"
return params
def response(resp):
results = []
dom = html.fromstring(resp.text)
for result in eval_xpath_list(dom, results_xpath):
results.append(
{
'template': 'images.html',
'url': base_url + extract_text(eval_xpath(result, url_xpath)),
'title': extract_text(eval_xpath(result, title_xpath)).replace(" SVG File", "").replace("Show ", ""),
'img_src': extract_text(eval_xpath(result, img_src_xpath)),
}
)
return results
+9 -1
View File
@@ -174,6 +174,10 @@ number, but an offset.'''
first_page_num = 1
'''Number of the first page (usually 0 or 1).'''
send_page_num_on_first_page = True
'''Whether to include the page number in the request for the first page.
This can help if an engine blocks request that send a page number for the first page.'''
time_range_support = False
'''Engine supports search time range.'''
@@ -238,10 +242,14 @@ def request(query, params):
if safe_search_val is not None:
safe_search = safe_search_map[safe_search_val]
pageno = ""
if send_page_num_on_first_page or params["pageno"] != 1:
pageno = (params['pageno'] - 1) * page_size + first_page_num
fargs = {
'query': urlencode({'q': query})[2:],
'lang': lang,
'pageno': (params['pageno'] - 1) * page_size + first_page_num,
'pageno': pageno,
'time_range': time_range,
'safe_search': safe_search,
}
+52 -63
View File
@@ -17,13 +17,14 @@ import babel.core
import searx.plugins
from searx import settings, autocomplete, favicons
from searx import get_setting, settings, autocomplete, favicons
from searx.enginelib import Engine
from searx.engines import DEFAULT_CATEGORY
from searx.extended_types import SXNG_Request
from searx.locales import LOCALE_NAMES
from searx.webutils import VALID_LANGUAGE_CODE
from ._settings import SettingsPref
COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 5 # 5 years
DOI_RESOLVERS = list(settings['doi_resolvers'])
@@ -386,6 +387,7 @@ class ClientPref:
return cls(locale=locale)
@t.final
class Preferences:
"""Validates and saves preferences to cookies"""
@@ -400,95 +402,91 @@ class Preferences:
super().__init__()
self.cfg: SettingsPref = get_setting("preferences")
self.key_value_settings: dict[str, Setting] = {
# fmt: off
'categories': MultipleChoiceSetting(
['general'],
locked=is_locked('categories'),
choices=categories + ['none']
["general"],
locked="categories" in self.cfg.lock,
choices=categories + ["none"],
),
'language': SearchLanguageSetting(
settings['search']['default_lang'],
locked=is_locked('language'),
choices=settings['search']['languages'] + ['']
get_setting("search.default_lang"),
locked="language" in self.cfg.lock,
choices=get_setting("search.languages") + [""],
),
'locale': EnumStringSetting(
settings['ui']['default_locale'],
locked=is_locked('locale'),
choices=list(LOCALE_NAMES.keys()) + ['']
get_setting("ui.default_locale"),
locked="locale" in self.cfg.lock,
choices=list(LOCALE_NAMES.keys()) + [""],
),
'autocomplete': EnumStringSetting(
settings['search']['autocomplete'],
locked=is_locked('autocomplete'),
choices=list(autocomplete.backends.keys()) + ['']
get_setting("search.autocomplete"),
locked="autocomplete" in self.cfg.lock,
choices=list(autocomplete.backends.keys()) + [""],
),
'favicon_resolver': EnumStringSetting(
settings['search']['favicon_resolver'],
locked=is_locked('favicon_resolver'),
choices=list(favicons.proxy.CFG.resolver_map.keys()) + ['']
get_setting("search.favicon_resolver"),
locked="favicon_resolver" in self.cfg.lock,
choices=list(favicons.proxy.CFG.resolver_map.keys()) + [''],
),
'image_proxy': BooleanSetting(
settings['server']['image_proxy'],
locked=is_locked('image_proxy')
get_setting("server.image_proxy"),
locked="image_proxy" in self.cfg.lock,
),
'method': EnumStringSetting(
settings['server']['method'],
locked=is_locked('method'),
choices=('GET', 'POST')
get_setting("server.method"),
locked="method" in self.cfg.lock,
choices=("GET", "POST"),
),
'safesearch': MapSetting(
settings['search']['safe_search'],
locked=is_locked('safesearch'),
get_setting("search.safe_search"),
locked="safesearch" in self.cfg.lock,
map={
'0': 0,
'1': 1,
'2': 2
}
"0": 0,
"1": 1,
"2": 2,
},
),
'theme': EnumStringSetting(
settings['ui']['default_theme'],
locked=is_locked('theme'),
choices=themes
get_setting("ui.default_theme"),
locked="theme" in self.cfg.lock,
choices=themes,
),
'results_on_new_tab': BooleanSetting(
settings['ui']['results_on_new_tab'],
locked=is_locked('results_on_new_tab')
get_setting("ui.results_on_new_tab"),
locked="results_on_new_tab" in self.cfg.lock,
),
'doi_resolver': MultipleChoiceSetting(
[settings['default_doi_resolver'], ],
locked=is_locked('doi_resolver'),
choices=DOI_RESOLVERS
[get_setting("default_doi_resolver")],
locked="doi_resolver" in self.cfg.lock,
choices=DOI_RESOLVERS,
),
'simple_style': EnumStringSetting(
settings['ui']['theme_args']['simple_style'],
locked=is_locked('simple_style'),
choices=['', 'auto', 'light', 'dark', 'black']
get_setting("ui.theme_args.simple_style"),
locked="simple_style" in self.cfg.lock,
choices=["", "auto", "light", "dark", "black"],
),
'center_alignment': BooleanSetting(
settings['ui']['center_alignment'],
locked=is_locked('center_alignment')
),
'advanced_search': BooleanSetting(
settings['ui']['advanced_search'],
locked=is_locked('advanced_search')
get_setting("ui.center_alignment"),
locked="center_alignment" in self.cfg.lock,
),
'query_in_title': BooleanSetting(
settings['ui']['query_in_title'],
locked=is_locked('query_in_title')
get_setting("ui.query_in_title"),
locked="query_in_title" in self.cfg.lock,
),
'search_on_category_select': BooleanSetting(
settings['ui']['search_on_category_select'],
locked=is_locked('search_on_category_select')
get_setting("ui.search_on_category_select"),
locked="search_on_category_select" in self.cfg.lock,
),
'hotkeys': EnumStringSetting(
settings['ui']['hotkeys'],
choices=['default', 'vim']
get_setting("ui.hotkeys"),
choices=["default", "vim"],
),
'url_formatting': EnumStringSetting(
settings['ui']['url_formatting'],
choices=['pretty', 'full', 'host']
get_setting("ui.url_formatting"),
choices=["pretty", "full", "host"],
),
# fmt: on
}
self.engines = EnginesSetting('engines', engines=engines.values())
@@ -597,12 +595,3 @@ class Preferences:
break
return valid
def is_locked(setting_name: str):
"""Checks if a given setting name is locked by settings.yml"""
if 'preferences' not in settings:
return False
if 'lock' not in settings['preferences']:
return False
return setting_name in settings['preferences']['lock']
-1
View File
@@ -152,7 +152,6 @@ class OnlineProcessor(EngineProcessor):
# add Accept-Language header
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language
headers["Accept-Language"] = "en,en-US;q=0.7,en;q=0.3"
if self.engine.send_accept_language_header and search_query.locale:
_l = search_query.locale.language
_t = search_query.locale.territory or _l
+36 -10
View File
@@ -154,14 +154,13 @@ ui:
# URL formatting: pretty, full or host
url_formatting: pretty
preferences:
# Lock arbitrary settings on the preferences page.
#
# preferences:
# lock:
lock: []
# - categories
# - language
# - autocomplete
# - favicon
# - favicon_resolver
# - safesearch
# - method
# - doi_resolver
@@ -965,6 +964,22 @@ engines:
timeout: 8.0
disabled: true
- name: gabanza
engine: xpath
search_url: https://www.gabanza.com/search?query={query}
shortcut: gab
timeout: 4
disabled: true
results_xpath: //div[contains(@class, "border-t")]/div/div
url_xpath: (.//a/@href)[1]
title_xpath: ./a
content_xpath: .//p
about:
website: https://www.gabanza.com
use_official_api: false
require_api_key: false
results: HTML
- name: geizhals
engine: geizhals
shortcut: geiz
@@ -2549,12 +2564,6 @@ engines:
results: HTML
language: de
- name: svgrepo
engine: svgrepo
shortcut: svg
timeout: 10.0
disabled: true
- name: tootfinder
engine: tootfinder
shortcut: toot
@@ -2600,6 +2609,23 @@ engines:
shortcut: wttr
timeout: 9.0
- name: zapmeta
engine: xpath
shortcut: zpm
search_url: https://www.zapmeta.com/search?q={query}&pg={pageno}
results_xpath: //article[contains(@class, "organic-results-item")]
url_xpath: ./h2/a/@href
title_xpath: ./h2
content_xpath: ./p
paging: true
send_page_num_on_first_page: false # otherwise blocks requests
disabled: true
about:
website: https://www.zapmeta.com/
use_official_api: false
require_api_key: false
results: HTML
- name: braveapi
engine: braveapi
# read https://docs.searxng.org/dev/engines/online/brave.html
+4 -4
View File
@@ -15,6 +15,7 @@ import msgspec
from typing_extensions import override
from .brand import SettingsBrand
from .sxng_locales import sxng_locales
from ._settings import SettingsPref
searx_dir = abspath(dirname(__file__))
@@ -146,6 +147,8 @@ def apply_schema(settings: dict[str, t.Any], schema: dict[str, t.Any], path_list
# Type Validation at runtime:
# https://jcristharif.com/msgspec/structs.html#type-validation
cfg_dict = settings.get(key)
if cfg_dict is None:
cfg_dict = {}
cfg_json = msgspec.json.encode(cfg_dict)
settings[key] = msgspec.json.decode(cfg_json, type=value)
except msgspec.ValidationError as e:
@@ -236,16 +239,13 @@ SCHEMA: dict[str, t.Any] = {
},
'center_alignment': SettingsValue(bool, False),
'results_on_new_tab': SettingsValue(bool, False),
'advanced_search': SettingsValue(bool, False),
'query_in_title': SettingsValue(bool, False),
'cache_url': SettingsValue(str, 'https://web.archive.org/web/'),
'search_on_category_select': SettingsValue(bool, True),
'hotkeys': SettingsValue(('default', 'vim'), 'default'),
'url_formatting': SettingsValue(('pretty', 'full', 'host'), 'pretty'),
},
'preferences': {
'lock': SettingsValue(list, []),
},
"preferences": SettingsPref,
'outgoing': {
'useragent_suffix': SettingsValue(str, ''),
'request_timeout': SettingsValue(numbers.Real, 3.0),
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -180,7 +180,7 @@
{%- if 'autocomplete' not in locked_preferences -%}
{%- include 'simple/preferences/autocomplete.html' -%}
{%- endif -%}
{%- if 'favicon' not in locked_preferences -%}
{%- if 'favicon_resolver' not in locked_preferences -%}
{%- include 'simple/preferences/favicon.html' -%}
{%- endif -%}
{% if 'safesearch' not in locked_preferences %}
+6 -6
View File
@@ -8,7 +8,7 @@ from searx.webutils import VALID_LANGUAGE_CODE
from searx.query import RawTextQuery
from searx.engines import categories, engines
from searx.search.models import SearchQuery, EngineRef
from searx.preferences import Preferences, is_locked
from searx.preferences import Preferences
# remove duplicate queries.
@@ -53,7 +53,7 @@ def parse_pageno(form: Dict[str, str]) -> int:
def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: RawTextQuery) -> str:
if is_locked('language'):
if "language" in preferences.cfg.lock:
return preferences.get_value('language')
# get language
# set specific language if set on request, query or preferences
@@ -73,7 +73,7 @@ def parse_lang(preferences: Preferences, form: Dict[str, str], raw_text_query: R
def parse_safesearch(preferences: Preferences, form: Dict[str, str]) -> int:
if is_locked('safesearch'):
if "safesearch" in preferences.cfg.lock:
return preferences.get_value('safesearch')
if 'safesearch' in form:
@@ -135,7 +135,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 not is_locked('categories') and form is not None:
if not "categories" in preferences.cfg.lock and form is not None:
for name, value in form.items():
parse_category_form(selected_categories, name, value)
@@ -175,7 +175,7 @@ def parse_generic(preferences: Preferences, form: Dict[str, str], disabled_engin
# set categories/engines
explicit_engine_list = False
if not is_locked('categories'):
if not "categories" in preferences.cfg.lock:
# parse the form only if the categories are not locked
for pd_name, pd in form.items(): # pylint: disable=invalid-name
if pd_name == 'engines':
@@ -266,7 +266,7 @@ def get_search_query_from_webapp(
if query_lang == 'auto':
query_lang = preferences.client.locale_tag or 'all'
if not is_locked('categories') and raw_text_query.specific:
if not "categories" in preferences.cfg.lock and raw_text_query.specific:
# if engines are calculated from query,
# set categories by using that information
query_engineref_list = raw_text_query.enginerefs
+1 -2
View File
@@ -377,7 +377,6 @@ def get_client_settings():
'theme_static_path': custom_url_for('static', filename='themes/simple'),
'results_on_new_tab': req_pref.get_value('results_on_new_tab'),
'favicon_resolver': req_pref.get_value('favicon_resolver'),
'advanced_search': req_pref.get_value('advanced_search'),
'query_in_title': req_pref.get_value('query_in_title'),
'safesearch': req_pref.get_value('safesearch'),
'theme': req_pref.get_value('theme'),
@@ -977,7 +976,7 @@ def preferences():
current_doi_resolver = get_doi_resolver(),
allowed_plugins = allowed_plugins,
preferences_url_params = sxng_request.preferences.get_as_url_params(),
locked_preferences = get_setting("preferences.lock", []),
locked_preferences = get_setting("preferences").lock,
doi_resolvers = get_setting("doi_resolvers", {}),
# fmt: on
)