diff options
| author | Patrick Evans <holysoles97@gmail.com> | 2026-06-12 16:25:13 -0500 |
|---|---|---|
| committer | Bnyro <bnyro@tutanota.com> | 2026-07-07 10:49:54 +0200 |
| commit | f9304437267b1113086e3cbec47dfb37e15f1112 (patch) | |
| tree | bc6078968a0193f6c2d8682c33a35aed672b41ab /searx | |
| parent | d58ced8f7126ff4a349c4f454af0c47a4e649f06 (diff) | |
[fix] kagi: safely access unpromised result properties
I was testing the new Kagi engine and found for some queries I was getting a
`KeyError` exception from the result parsing.
This PR ensures we only assumes the `url` key exists, and we use `.get()` to
retrieve values for keys that may not be present.
Their API documentation [1] clarifies that only the `url` and `title` properties
are required/guaranteed in the search result object, but this is not correct:
File "/home/patrick/code/searxng/searx/engines/kagi.py", line 161, in response
title=html.unescape(result["title"]),
~~~~~~^^^^^^^^^
KeyError: 'title
Heard back from Kagi support:
> The image results "title' should be marked as optional, as many images likely
> don't have titles - as you've noticed. The only required field there should be
> the "url" field.
[1] https://kagi.redocly.app/api/docs/openapi/search/search#search/search/t=response&c=200&path=data/search
[2] https://github.com/searxng/searxng/issues/2247#issuecomment-4692976877
Diffstat (limited to 'searx')
| -rw-r--r-- | searx/engines/kagi.py | 25 |
1 files changed, 15 insertions, 10 deletions
diff --git a/searx/engines/kagi.py b/searx/engines/kagi.py index b0c394ea6..63ec64c17 100644 --- a/searx/engines/kagi.py +++ b/searx/engines/kagi.py @@ -42,11 +42,11 @@ To enable Kagi, add the following to the ``engines`` seciton of .. _Api Portal: https://help.kagi.com/kagi/api/overview.html """ - from datetime import datetime, timedelta import typing as t + from searx.extended_types import SXNG_Response from searx.result_types import EngineResults from searx.utils import html_to_text, parse_duration_string @@ -76,7 +76,12 @@ kagi_categ: t.Literal["search", "images", "news", "videos"] = "search" base_url = "https://kagi.com" safe_search_map = {0: False, 1: True, 2: True} -time_range_to_days_map: dict[TimeRangeType, int] = {"day": 1, "week": 7, "month": 30, "year": 365} +time_range_to_days_map: dict[TimeRangeType, int] = { + "day": 1, + "week": 7, + "month": 30, + "year": 365, +} api_key = "" """Kagi API key. Required for using this engine.""" @@ -147,8 +152,8 @@ def response(resp: "SXNG_Response") -> EngineResults: res.add( res.types.MainResult( url=result["url"], - title=html_to_text(result["title"]), - content=html_to_text(result["snippet"]), + title=html_to_text(result.get("title", "no title available")), + content=html_to_text(result.get("snippet", "")), thumbnail=result.get("image", {}).get("url") or "", publishedDate=published_date, ) @@ -157,15 +162,15 @@ def response(resp: "SXNG_Response") -> EngineResults: res.add( res.types.Image( url=result["url"], - title=html_to_text(result.get("title")), + title=html_to_text(result.get("title", "no title available")), img_src=result.get("image", {}).get("url"), - resolution=f"{result['image']['width']}x{result['image']['height']}", + resolution=f"{result.get('image', {}).get('width')}x{result.get('image', {}).get('height')}", thumbnail_src=result.get("props", {}).get("thumbnail", {}).get("url"), ) ) elif kagi_categ == "videos": length: timedelta | None = None - if result["props"].get("duration"): + if result.get("props", {}).get("duration"): length = parse_duration_string(result["props"]["duration"]) res.add( @@ -173,11 +178,11 @@ def response(resp: "SXNG_Response") -> EngineResults: { "template": "videos.html", "url": result["url"], - "title": html_to_text(result["title"]), - "content": html_to_text(result["snippet"]), + "title": html_to_text(result.get("title", "no title available")), + "content": html_to_text(result.get("snippet", "")), "thumbnail": result.get("image", {}).get("url"), "publishedDate": published_date, - "author": result["props"].get("creator_name"), + "author": result.get("props", {}).get("creator_name"), "length": length, } ) |
