From 1f0e62772135c76306ae73b85403ea8d92ca697e Mon Sep 17 00:00:00 2001 From: "itdrui.de" Date: Sun, 1 Mar 2026 18:49:18 +0100 Subject: [PATCH] dev: gruppierte Suchergebnisse mit Quellenauswahl (Issue #2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _show_search_results() gruppiert Treffer über alle Plugins nach Titel - Titel in einem Plugin: direkt zur Staffel-Ansicht (kein Plugin-Suffix) - Titel in mehreren Plugins: Zwischenstufe 'Quelle wählen' - Neue Funktion _show_choose_source() und Route 'choose_source' --- addon/default.py | 89 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 14 deletions(-) diff --git a/addon/default.py b/addon/default.py index e5f0100..34432af 100644 --- a/addon/default.py +++ b/addon/default.py @@ -1958,7 +1958,8 @@ def _show_search_results(query: str) -> None: xbmcgui.Dialog().notification("Suche", "Keine Quellen gefunden.", xbmcgui.NOTIFICATION_INFO, 3000) xbmcplugin.endOfDirectory(handle) return - list_items: list[dict[str, object]] = [] + # grouped: casefold-key → Liste der Plugin-Einträge für diesen Titel + grouped: dict[str, list[dict[str, object]]] = {} canceled = False plugin_entries = list(plugins.items()) total_plugins = max(1, len(plugin_entries)) @@ -2029,31 +2030,61 @@ def _show_search_results(query: str) -> None: merged_info = _apply_playstate_to_info(dict(info_labels), playstate) label = _label_with_duration(title, info_labels) label = _label_with_playstate(label, playstate) - label = f"{label} [{plugin_name}]" direct_play = bool( plugin_name.casefold() == "einschalten" and _get_setting_bool("einschalten_enable_playback", default=False) ) extra_params = _series_url_params(plugin, title) - list_items.append( - { - "label": label, - "action": "play_movie" if direct_play else "seasons", - "params": {"plugin": plugin_name, "title": title, **extra_params}, - "is_folder": (not direct_play), - "info_labels": merged_info, - "art": art, - "cast": cast, - } - ) + key = title.casefold() + grouped.setdefault(key, []).append({ + "title": title, + "plugin_name": plugin_name, + "extra_params": extra_params, + "label_base": label, + "direct_play": direct_play, + "info_labels": merged_info, + "art": art, + "cast": cast, + }) if canceled: break if not canceled: progress(100, "Suche fertig") - if canceled and not list_items: + if canceled and not grouped: xbmcgui.Dialog().notification("Suche", "Suche abgebrochen.", xbmcgui.NOTIFICATION_INFO, 2500) xbmcplugin.endOfDirectory(handle) return + # Gruppierte Einträge alphabetisch ausgeben + list_items: list[dict[str, object]] = [] + for key in sorted(grouped): + entries = grouped[key] + first = entries[0] + canonical_title = str(first["title"]) + if len(entries) == 1: + # Nur ein Plugin → direkt zur Staffel-Ansicht + direct_play = bool(first["direct_play"]) + list_items.append({ + "label": first["label_base"], + "action": "play_movie" if direct_play else "seasons", + "params": {"plugin": first["plugin_name"], "title": canonical_title, **dict(first["extra_params"])}, + "is_folder": not direct_play, + "info_labels": first["info_labels"], + "art": first["art"], + "cast": first["cast"], + }) + else: + # Mehrere Plugins → Zwischenstufe "Quelle wählen" + plugin_list = ",".join(str(e["plugin_name"]) for e in entries) + list_items.append({ + "label": first["label_base"], + "action": "choose_source", + "params": {"title": canonical_title, "plugins": plugin_list}, + "is_folder": True, + "info_labels": first["info_labels"], + "art": first["art"], + "cast": first["cast"], + }) + for item in list_items: _add_directory_item( handle, @@ -2068,6 +2099,31 @@ def _show_search_results(query: str) -> None: xbmcplugin.endOfDirectory(handle) +def _show_choose_source(title: str, plugins_str: str) -> None: + """Zeigt Quellenauswahl wenn ein Titel in mehreren Plugins verfügbar ist.""" + handle = _get_handle() + title = (title or "").strip() + plugin_names = [p.strip() for p in (plugins_str or "").split(",") if p.strip()] + all_plugins = _discover_plugins() + + xbmcplugin.setPluginCategory(handle, title) + _set_content(handle, "tvshows") + + for plugin_name in plugin_names: + plugin = all_plugins.get(plugin_name) + if not plugin: + continue + extra_params = _series_url_params(plugin, title) + _add_directory_item( + handle, + plugin_name, + "seasons", + {"plugin": plugin_name, "title": title, **extra_params}, + is_folder=True, + ) + xbmcplugin.endOfDirectory(handle) + + def _movie_seed_for_title(plugin: BasisPlugin, title: str, seasons: list[str]) -> tuple[str, str] | None: """Ermittelt ein Film-Seed (Season/Episode), um direkt Provider anzeigen zu können.""" if not seasons or len(seasons) != 1: @@ -4728,6 +4784,11 @@ def _route_install_resolveurl(params: dict[str, str]) -> None: _ensure_resolveurl_installed(force=True, silent=False) +@_router.route("choose_source") +def _route_choose_source(params: dict[str, str]) -> None: + _show_choose_source(params.get("title", ""), params.get("plugins", "")) + + @_router.route("seasons") def _route_seasons(params: dict[str, str]) -> None: _show_seasons(params.get("plugin", ""), params.get("title", ""), params.get("series_url", ""))