diff --git a/custom_components/bahmcloud_store/views.py b/custom_components/bahmcloud_store/views.py index fdb1b70..7fc79bf 100644 --- a/custom_components/bahmcloud_store/views.py +++ b/custom_components/bahmcloud_store/views.py @@ -1,6 +1,5 @@ from __future__ import annotations -import base64 import logging from dataclasses import asdict from pathlib import Path @@ -9,6 +8,8 @@ from typing import Any, TYPE_CHECKING from aiohttp import web from homeassistant.components.http import HomeAssistantView +from .providers import fetch_readme_markdown_from_repo + if TYPE_CHECKING: from .core import BCSCore # typing only @@ -23,7 +24,6 @@ def _render_markdown_server_side(md: str) -> str | None: html: str | None = None - # 1) python-markdown try: import markdown as mdlib # type: ignore @@ -39,7 +39,6 @@ def _render_markdown_server_side(md: str) -> str | None: if not html: return None - # 2) Sanitize via bleach try: import bleach # type: ignore @@ -105,94 +104,6 @@ def _render_markdown_server_side(md: str) -> str | None: return html -_TEXT_KEYS = ("readme", "markdown", "text", "content", "data", "body") - - -def _maybe_decode_base64(content: str, encoding: Any) -> str | None: - if not isinstance(content, str): - return None - enc = "" - if isinstance(encoding, str): - enc = encoding.strip().lower() - if "base64" not in enc: - return None - try: - raw = base64.b64decode(content.encode("utf-8"), validate=False) - return raw.decode("utf-8", errors="replace") - except Exception: - return None - - -def _extract_text_recursive(obj: Any, depth: int = 0) -> str | None: - """ - Robust extraction for README markdown. - - Handles: - - str / bytes - - dict with: - - {content: "...", encoding: "base64"} (possibly nested) - - {readme: "..."} etc. - - list of dicts (pick first matching) - """ - if obj is None: - return None - - if isinstance(obj, bytes): - try: - return obj.decode("utf-8", errors="replace") - except Exception: - return None - - if isinstance(obj, str): - return obj - - if depth > 8: - return None - - if isinstance(obj, dict): - # 1) If it looks like "file content" - content = obj.get("content") - encoding = obj.get("encoding") - - # Base64 decode if possible - decoded = _maybe_decode_base64(content, encoding) - if decoded: - return decoded - - # content may already be plain text - if isinstance(content, str) and (not isinstance(encoding, str) or not encoding.strip()): - # Heuristic: treat as markdown if it has typical markdown chars, otherwise still return - return content - - # 2) direct text keys (readme/markdown/text/body/data) - for k in _TEXT_KEYS: - v = obj.get(k) - if isinstance(v, str): - return v - if isinstance(v, bytes): - try: - return v.decode("utf-8", errors="replace") - except Exception: - pass - - # 3) Sometimes nested under "file" / "result" / "payload" etc. - for v in obj.values(): - out = _extract_text_recursive(v, depth + 1) - if out: - return out - - return None - - if isinstance(obj, list): - for item in obj: - out = _extract_text_recursive(item, depth + 1) - if out: - return out - return None - - return None - - class StaticAssetsView(HomeAssistantView): url = "/api/bahmcloud_store_static/{path:.*}" name = "api:bahmcloud_store_static" @@ -299,18 +210,33 @@ class BCSReadmeView(HomeAssistantView): if not repo_id: return web.json_response({"ok": False, "message": "Missing repo_id"}, status=400) - maybe_md = await self.core.fetch_readme_markdown(repo_id) + repos = self.core.list_repos_public() + repo = next((r for r in repos if str(r.get("id", "")) == str(repo_id)), None) + if not repo: + return web.json_response({"ok": False, "message": "Repository not found."}, status=404) + + repo_url = repo.get("url") + provider = repo.get("provider") + default_branch = repo.get("default_branch") + + if not isinstance(repo_url, str) or not repo_url.strip(): + return web.json_response({"ok": False, "message": "Repository URL missing."}, status=404) + + md = await fetch_readme_markdown_from_repo( + self.core.hass, + repo_url=repo_url, + provider=provider if isinstance(provider, str) else None, + default_branch=default_branch if isinstance(default_branch, str) else None, + ) - md = _extract_text_recursive(maybe_md) if not md or not md.strip(): - t = type(maybe_md).__name__ return web.json_response( - {"ok": False, "message": f"README not found or unsupported format (got {t})."}, + { + "ok": False, + "message": "README not found (raw endpoint returned 404).", + }, status=404, ) - # Ensure strict JSON string output (avoid accidental objects) - md_str = str(md) - - html = _render_markdown_server_side(md_str) - return web.json_response({"ok": True, "readme": md_str, "html": html}) + html = _render_markdown_server_side(md) + return web.json_response({"ok": True, "readme": md, "html": html})