From 8e824017dc88cebe5a42ee6ca04315ca9545f717 Mon Sep 17 00:00:00 2001 From: Carsten Csiky Date: Mon, 16 Feb 2026 09:21:22 +0100 Subject: [PATCH] [feat] engines: add artstation engine (#5728) - use proper Brave API with api_key for search Co-authored-by: Bnyro --- docs/dev/engines/online/brave.rst | 25 +++++- searx/engines/braveapi.py | 126 ++++++++++++++++++++++++++++++ searx/settings.yml | 6 ++ 3 files changed, 154 insertions(+), 3 deletions(-) create mode 100644 searx/engines/braveapi.py diff --git a/docs/dev/engines/online/brave.rst b/docs/dev/engines/online/brave.rst index a1c589b9d..96606f3bf 100644 --- a/docs/dev/engines/online/brave.rst +++ b/docs/dev/engines/online/brave.rst @@ -1,5 +1,3 @@ -.. _brave engine: - ============= Brave Engines ============= @@ -9,5 +7,26 @@ Brave Engines :local: :backlinks: entry +Brave offers two different engines for SearXNG: + +1. The standard engine (``brave``) uses the web interface. +2. The API engine (``braveapi``) uses the official REST API. + +.. _brave engine: + +Brave Standard Engine +--------------------- + .. automodule:: searx.engines.brave - :members: + :members: + +.. _braveapi engine: + +Brave API Engine +---------------- + +.. automodule:: searx.engines.braveapi + :members: + +The API engine requires an API key from Brave. This can be obtained from the +`API Dashboard `_. diff --git a/searx/engines/braveapi.py b/searx/engines/braveapi.py new file mode 100644 index 000000000..a25258548 --- /dev/null +++ b/searx/engines/braveapi.py @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Engine to search using the Brave (WEB) Search API. + +.. _Brave Search API: https://api-dashboard.search.brave.com/documentation + +Configuration +============= + +The engine has the following mandatory setting: + +- :py:obj:`api_key` + +Optional settings are: + +- :py:obj:`results_per_page` + +.. code:: yaml + + - name: braveapi + engine: braveapi + api_key: 'YOUR-API-KEY' # required + results_per_page: 20 # optional + +The API supports paging and time filters. +""" + +import typing as t + +from urllib.parse import urlencode +from dateutil import parser + +from searx.exceptions import SearxEngineAPIException +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://api.search.brave.com/", + "wikidata_id": None, + "official_api_documentation": "https://api-dashboard.search.brave.com/documentation", + "use_official_api": True, + "require_api_key": True, + "results": "JSON", +} + +api_key: str = "" +"""API key for Brave Search API (required).""" + +categories = ["general", "web"] +paging = True +safesearch = True +time_range_support = True + +results_per_page: int = 20 +"""Maximum number of results per page (default 20).""" + +base_url = "https://api.search.brave.com/res/v1/web/search" +"""Base URL for the Brave Search API.""" + +time_range_map = {"day": "past_day", "week": "past_week", "month": "past_month", "year": "past_year"} +"""Mapping of SearXNG time ranges to Brave API time ranges.""" + + +def init(_): + """Initialize the engine.""" + if not api_key: + raise SearxEngineAPIException("No API key provided") + + +def request(query: str, params: "OnlineParams") -> None: + """Create the API request.""" + search_args: dict[str, str | int | None] = { + "q": query, + "count": results_per_page, + "offset": (params["pageno"] - 1) * results_per_page, + } + + # Apply time filter if specified + if params["time_range"]: + search_args["time_range"] = time_range_map.get(params["time_range"]) + + # Apply SafeSearch if enabled + if params["safesearch"]: + search_args["safesearch"] = "strict" + + params["url"] = f"{base_url}?{urlencode(search_args)}" + params["headers"]["X-Subscription-Token"] = api_key + + +def _extract_published_date(published_date_raw: str): + """Extract and parse the published date from the API response. + + Args: + published_date_raw: Raw date string from the API + + Returns: + Parsed datetime object or None if parsing fails + """ + if not published_date_raw: + return None + + try: + return parser.parse(published_date_raw) + except parser.ParserError: + return None + + +def response(resp: "SXNG_Response") -> EngineResults: + """Process the API response and return results.""" + res = EngineResults() + data = resp.json() + + for result in data.get("web", {}).get("results", []): + res.add( + res.types.MainResult( + url=result["url"], + title=result["title"], + content=result.get("description", ""), + publishedDate=_extract_published_date(result.get("age")), + thumbnail=result.get("thumbnail", {}).get("src"), + ), + ) + + return res diff --git a/searx/settings.yml b/searx/settings.yml index 413c6ce85..d9f7cf8c1 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -2663,6 +2663,12 @@ engines: shortcut: wttr timeout: 9.0 + - name: braveapi + engine: braveapi + # read https://docs.searxng.org/dev/engines/online/brave.html + api_key: "" + inactive: true + - name: brave engine: brave shortcut: br