custom_components/bahmcloud_store/views.py aktualisiert

This commit is contained in:
2026-01-15 12:55:47 +00:00
parent 2c8da4a049
commit 2dce858a51

View File

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