1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
# 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
from searx.utils import html_to_text
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/api-reference/web/search/get",
"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."""
max_page = 10
def setup(_: dict[str, t.Any]) -> bool | None:
"""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,
"text_decorations": False,
}
# 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
params["headers"]["Accept"] = "application/json"
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") or {}).get("results", []):
thumbnail_obj = result.get("thumbnail")
thumbnail = ""
if thumbnail_obj and not thumbnail_obj.get("logo", False):
thumbnail = thumbnail_obj.get("src") or ""
res.add(
res.types.MainResult(
url=result["url"],
title=html_to_text(result["title"]),
content=html_to_text(result.get("description", "")),
publishedDate=_extract_published_date(result.get("age")),
thumbnail=thumbnail,
),
)
return res
|