From b93cc2f9f8ac058152bab236b4b54b8197b942f6 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Mon, 1 Sep 2025 16:33:31 +0200 Subject: [feat] engines: add repology.org engine for linux packages (#5103) Repology_ monitors a huge number of package repositories and other sources comparing packages versions across them and gathering other information. Repology_ shows you in which repositories a given project is packaged, which version is the latest and which needs updating, who maintains the package, and other related information. .. _Repology: https://repology.org/docs/about Co-authored-by: Markus Heiser Format: reST --- searx/engines/repology.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 searx/engines/repology.py (limited to 'searx/engines/repology.py') diff --git a/searx/engines/repology.py b/searx/engines/repology.py new file mode 100644 index 000000000..022f23190 --- /dev/null +++ b/searx/engines/repology.py @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: AGPL-3.0-or-later +"""Repology_ monitors a huge number of package repositories and other sources +comparing packages versions across them and gathering other information. + +Repology_ shows you in which repositories a given project is packaged, which +version is the latest and which needs updating, who maintains the package, and +other related information. + +.. _Repology: https://repology.org/docs/about + +Configuration +============= + +The engine is inactive by default, meaning it is not available in the service. +If you want to offer the engine, the ``inactive`` flag must be set to ``false``. + +.. code:: yaml + + - name: repology + inactive: false + +""" + +import typing as t + +from urllib.parse import urlencode +from searx.result_types import EngineResults + +if t.TYPE_CHECKING: + from searx.extended_types import SXNG_Response + +about = { + 'website': 'https://repology.org', + 'wikidata_id': 'Q107409859', + 'use_official_api': True, + 'official_api_documentation': 'https://repology.org/api/v1', + 'require_api_key': False, + 'results': 'JSON', +} +categories: list[str] = ['packages', 'it'] +base_url: str = 'https://repology.org' + + +def request(query: str, params: dict[str, t.Any]) -> None: + args = { + 'search': query, + } + params['url'] = f"{base_url}/api/v1/projects/?{urlencode(args)}" + + +def _get_most_common(items: list[str | None]) -> str | None: + counts: dict[str | None, int] = {} + for item in items: + if item: + counts[item] = counts.get(item, 0) + 1 + + if len(counts) == 0: + return None + return max(counts, key=counts.get) + + +def _flatten(xss): + return [x for xs in xss for x in xs] + + +def response(resp: 'SXNG_Response') -> EngineResults: + res = EngineResults() + + resp_json = resp.json() + for pkgname, repositories in resp_json.items(): + + # either there's a package with status "newest" or we assume that the + # most commonly used version is the latest released (non-alpha) version + latest_version = None + for repo in repositories: + if repo.get("status") == "newest": + latest_version = repo["version"] + break + else: + latest_version = _get_most_common([repo.get("version") for repo in repositories]) + + res.add( + res.types.LegacyResult( + template='packages.html', + url=f"{base_url}/project/{pkgname}/versions", + title=pkgname, + content=_get_most_common([pkg.get("summary") for pkg in repositories]), + package_name=_get_most_common([pkg.get("visiblename") for pkg in repositories]), + version=latest_version, + license_name=_get_most_common(_flatten([pkg.get("licenses", []) for pkg in repositories])), + tags=list({pkg.get("repo") for pkg in repositories}), # ensure that tags are unique + ) + ) + + return res -- cgit v1.2.3