diff --git a/docs/dev/engines/online/marginalia.rst b/docs/dev/engines/online/marginalia.rst new file mode 100644 index 000000000..cbb80400f --- /dev/null +++ b/docs/dev/engines/online/marginalia.rst @@ -0,0 +1,8 @@ +.. _marginalia engine: + +================= +Marginalia Search +================= + +.. automodule:: searx.engines.marginalia + :members: diff --git a/searx/engines/marginalia.py b/searx/engines/marginalia.py new file mode 100644 index 000000000..63f411cf0 --- /dev/null +++ b/searx/engines/marginalia.py @@ -0,0 +1,125 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""`Marginalia Search`_ is an independent open source Internet search engine +operating out of Sweden. It is principally developed and operated by Viktor +Lofgren . + +.. _Marginalia Search: + https://about.marginalia-search.com/ + +Configuration +============= + +The engine has the following required settings: + +- :py:obj:`api_key` + +You can configure a Marginalia engine by: + +.. code:: yaml + + - name: marginalia + engine: marginalia + shortcut: mar + api_key: ... + +Implementations +=============== + +""" +from __future__ import annotations + +import typing as t +from urllib.parse import urlencode, quote_plus +from searx.utils import searxng_useragent +from searx.result_types import EngineResults +from searx.extended_types import SXNG_Response + +about = { + "website": "https://marginalia.nu", + "wikidata_id": None, + "official_api_documentation": "https://about.marginalia-search.com/article/api/", + "use_official_api": True, + "require_api_key": True, + "results": "JSON", +} + +base_url = "https://api.marginalia.nu" +safesearch = True +categories = ["general"] +paging = False +results_per_page = 20 +api_key = None +"""To get an API key, please follow the instructions from `Key and license`_ + +.. _Key and license: + https://about.marginalia-search.com/article/api/ + +""" + + +class ApiSearchResult(t.TypedDict): + """Marginalia's ApiSearchResult_ class definition. + + .. _ApiSearchResult: + https://github.com/MarginaliaSearch/MarginaliaSearch/blob/master/code/services-application/api-service/java/nu/marginalia/api/model/ApiSearchResult.java + """ + + url: str + title: str + description: str + quality: float + format: str + details: str + + +class ApiSearchResults(t.TypedDict): + """Marginalia's ApiSearchResults_ class definition. + + .. _ApiSearchResults: + https://github.com/MarginaliaSearch/MarginaliaSearch/blob/master/code/services-application/api-service/java/nu/marginalia/api/model/ApiSearchResults.java + """ + + license: str + query: str + results: list[ApiSearchResult] + + +def request(query: str, params: dict[str, t.Any]): + + query_params = { + "count": results_per_page, + "nsfw": min(params["safesearch"], 1), + } + + params["url"] = f"{base_url}/{api_key}/search/{quote_plus(query)}?{urlencode(query_params)}" + params["headers"]["User-Agent"] = searxng_useragent() + + +def response(resp: SXNG_Response): + + res = EngineResults() + resp_json: ApiSearchResults = resp.json() # type: ignore + + for item in resp_json.get("results", []): + res.add( + res.types.MainResult( + title=item["title"], + url=item["url"], + content=item.get("description", ""), + ) + ) + + return res + + +def init(engine_settings: dict[str, t.Any]): + + _api_key = engine_settings.get("api_key") + if not _api_key: + logger.error("missing api_key: see https://about.marginalia-search.com/article/api") + return False + + if _api_key == "public": + logger.error("invalid api_key (%s): see https://about.marginalia-search.com/article/api", api_key) + + return True diff --git a/searx/settings.yml b/searx/settings.yml index 0319191dd..d21192651 100644 --- a/searx/settings.yml +++ b/searx/settings.yml @@ -1277,6 +1277,15 @@ engines: require_api_key: false results: HTML + - name: marginalia + engine: marginalia + shortcut: mar + # To get an API key, please follow the instructions at + # - https://about.marginalia-search.com/article/api/ + # api_key: ... + disabled: true + inactive: true + - name: mastodon users engine: mastodon mastodon_type: accounts