[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 <markus.heiser@darmarit.de>
Format: reST
This commit is contained in:
Bnyro 2025-09-01 16:33:31 +02:00 committed by GitHub
parent 501327ddf1
commit b93cc2f9f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 109 additions and 0 deletions

View file

@ -0,0 +1,8 @@
.. _repology engine:
========
Repology
========
.. automodule:: searx.engines.repology
:members:

95
searx/engines/repology.py Normal file
View file

@ -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

View file

@ -2525,6 +2525,12 @@ engines:
categories: videos categories: videos
disabled: true disabled: true
- name: repology
engine: repology
shortcut: rep
disabled: true
inactive: true
- name: livespace - name: livespace
engine: livespace engine: livespace
shortcut: ls shortcut: ls