custom_components/bahmcloud_store/views.py aktualisiert
This commit is contained in:
@@ -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})
|
||||||
|
|||||||
Reference in New Issue
Block a user