custom_components/bahmcloud_store/views.py aktualisiert

This commit is contained in:
2026-01-15 17:19:45 +00:00
parent f861b2490a
commit 745979b9a6

View File

@@ -16,14 +16,12 @@ _LOGGER = logging.getLogger(__name__)
def _render_markdown_server_side(md: str) -> str | None: def _render_markdown_server_side(md: str) -> str | None:
"""Render Markdown -> sanitized HTML (server-side)."""
text = (md or "").strip() text = (md or "").strip()
if not text: if not text:
return None return None
html: str | None = None html: str | None = None
# 1) python-markdown
try: try:
import markdown as mdlib # type: ignore import markdown as mdlib # type: ignore
@@ -39,7 +37,6 @@ def _render_markdown_server_side(md: str) -> str | None:
if not html: if not html:
return None return None
# 2) Sanitize via bleach
try: try:
import bleach # type: ignore import bleach # type: ignore
@@ -124,16 +121,6 @@ def _maybe_decode_base64(content: str, encoding: Any) -> str | None:
def _extract_text_recursive(obj: Any, depth: int = 0) -> str | 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: if obj is None:
return None return None
@@ -150,20 +137,16 @@ def _extract_text_recursive(obj: Any, depth: int = 0) -> str | None:
return None return None
if isinstance(obj, dict): if isinstance(obj, dict):
# 1) If it looks like "file content"
content = obj.get("content") content = obj.get("content")
encoding = obj.get("encoding") encoding = obj.get("encoding")
# Base64 decode if possible
decoded = _maybe_decode_base64(content, encoding) decoded = _maybe_decode_base64(content, encoding)
if decoded: if decoded:
return decoded return decoded
# content may already be plain text
if isinstance(content, str) and (not isinstance(encoding, str) or not encoding.strip()): if isinstance(content, str) and (not isinstance(encoding, str) or not encoding.strip()):
return content return content
# 2) direct text keys (readme/markdown/text/body/data)
for k in _TEXT_KEYS: for k in _TEXT_KEYS:
v = obj.get(k) v = obj.get(k)
if isinstance(v, str): if isinstance(v, str):
@@ -174,7 +157,6 @@ def _extract_text_recursive(obj: Any, depth: int = 0) -> str | None:
except Exception: except Exception:
pass pass
# 3) Sometimes nested under "file" / "result" / "payload" etc.
for v in obj.values(): for v in obj.values():
out = _extract_text_recursive(v, depth + 1) out = _extract_text_recursive(v, depth + 1)
if out: if out:
@@ -197,7 +179,7 @@ class StaticAssetsView(HomeAssistantView):
name = "api:bahmcloud_store_static" name = "api:bahmcloud_store_static"
requires_auth = False requires_auth = False
async def get(self, request: web.Request, path: str) -> web.Response: async def get(self, request: web.Request, path: str) -> web.StreamResponse:
base = Path(__file__).resolve().parent / "panel" base = Path(__file__).resolve().parent / "panel"
base_resolved = base.resolve() base_resolved = base.resolve()
@@ -217,24 +199,7 @@ class StaticAssetsView(HomeAssistantView):
_LOGGER.error("BCS static asset not found: %s", target) _LOGGER.error("BCS static asset not found: %s", target)
return web.Response(status=404) return web.Response(status=404)
content_type = "text/plain" resp = web.FileResponse(path=target)
charset = None
if target.suffix == ".js":
content_type = "application/javascript"
charset = "utf-8"
elif target.suffix == ".html":
content_type = "text/html"
charset = "utf-8"
elif target.suffix == ".css":
content_type = "text/css"
charset = "utf-8"
elif target.suffix == ".svg":
content_type = "image/svg+xml"
elif target.suffix == ".png":
content_type = "image/png"
resp = web.Response(body=target.read_bytes(), content_type=content_type, charset=charset)
resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" resp.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
resp.headers["Pragma"] = "no-cache" resp.headers["Pragma"] = "no-cache"
return resp return resp
@@ -254,33 +219,16 @@ class BCSApiView(HomeAssistantView):
) )
async def post(self, request: web.Request) -> web.Response: async def post(self, request: web.Request) -> web.Response:
"""
Supported operations:
1) Full refresh:
POST /api/bcs?action=refresh
2) JSON operations (body based):
{
"op": "add_custom_repo",
"url": "...",
"name": "..."
}
"""
# --- Phase B: Manual full refresh trigger ---
action = request.query.get("action") action = request.query.get("action")
if action == "refresh": if action == "refresh":
_LOGGER.info("BCS manual refresh triggered via API") _LOGGER.info("BCS manual refresh triggered via API")
try: try:
# Centralized service (same path as timer), includes logging and signal_updated
await self.core.full_refresh(source="manual") await self.core.full_refresh(source="manual")
return web.json_response({"ok": True}) return web.json_response({"ok": True})
except Exception as e: except Exception as e:
_LOGGER.error("BCS manual refresh failed: %s", e) _LOGGER.error("BCS manual refresh failed: %s", e)
return web.json_response({"ok": False, "message": "Refresh failed"}, status=500) return web.json_response({"ok": False, "message": "Refresh failed"}, status=500)
# --- Existing JSON based operations ---
try: try:
data = await request.json() data = await request.json()
except Exception: except Exception:
@@ -340,6 +288,5 @@ class BCSReadmeView(HomeAssistantView):
) )
md_str = str(md) md_str = str(md)
html = _render_markdown_server_side(md_str) html = _render_markdown_server_side(md_str)
return web.json_response({"ok": True, "readme": md_str, "html": html}) return web.json_response({"ok": True, "readme": md_str, "html": html})