fix(images): add proxy fallback to direct source url rendering
This commit is contained in:
parent
910ca72c81
commit
fb3465fb10
4 changed files with 26 additions and 8 deletions
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import Request as UrlRequest, urlopen
|
||||
|
||||
|
|
@ -124,6 +125,14 @@ def _is_probably_irrelevant_image(url: str) -> bool:
|
|||
return any(re.search(pattern, lowered) for pattern in patterns)
|
||||
|
||||
|
||||
def _is_http_image_url(url: str) -> bool:
|
||||
try:
|
||||
parsed = urlparse(url)
|
||||
except Exception:
|
||||
return False
|
||||
return parsed.scheme in {"http", "https"} and bool(parsed.netloc)
|
||||
|
||||
|
||||
def _build_image_entries(article: dict, extraction: dict, meta: dict) -> list[dict[str, object]]:
|
||||
all_images = _read_article_images(article, extraction)
|
||||
image_review = meta.get("image_review") if isinstance(meta.get("image_review"), dict) else {}
|
||||
|
|
@ -371,15 +380,19 @@ def admin_article_image_decision(
|
|||
|
||||
@router.get("/admin/images/proxy")
|
||||
def admin_image_proxy(request: Request, url: str):
|
||||
user = _admin_user(request)
|
||||
if not user:
|
||||
return Response(status_code=401)
|
||||
|
||||
if not (url.startswith("http://") or url.startswith("https://")):
|
||||
if not _is_http_image_url(url):
|
||||
return Response(status_code=400)
|
||||
|
||||
try:
|
||||
req = UrlRequest(url=url, headers={"User-Agent": IMAGE_PROXY_USER_AGENT, "Referer": url})
|
||||
referer = request.headers.get("referer", "")
|
||||
req = UrlRequest(
|
||||
url=url,
|
||||
headers={
|
||||
"User-Agent": IMAGE_PROXY_USER_AGENT,
|
||||
"Accept": "image/avif,image/webp,image/apng,image/*,*/*;q=0.8",
|
||||
"Referer": referer or url,
|
||||
},
|
||||
)
|
||||
with urlopen(req, timeout=10) as resp:
|
||||
body = resp.read()
|
||||
content_type = resp.headers.get("Content-Type", "application/octet-stream")
|
||||
|
|
|
|||
|
|
@ -210,6 +210,11 @@ button.secondary {
|
|||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.img-failed {
|
||||
opacity: 0.3;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.image-meta {
|
||||
margin-top: 6px;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@
|
|||
{% for image in article.image_entries %}
|
||||
<article class="image-card {{ 'image-selected' if image.is_selected else '' }} {{ 'image-excluded' if image.is_excluded else '' }}">
|
||||
<a href="{{ image.url }}" target="_blank" rel="noopener">
|
||||
<img src="{{ image.proxy_url }}" alt="Artikelbild" loading="lazy" />
|
||||
<img src="{{ image.proxy_url }}" data-fallback-src="{{ image.url }}" alt="Artikelbild" loading="lazy" onerror="if(!this.dataset.fallbackUsed){this.dataset.fallbackUsed='1';this.src=this.dataset.fallbackSrc;}else{this.classList.add('img-failed');}" />
|
||||
</a>
|
||||
<div class="image-meta">
|
||||
{% if image.is_selected %}<span class="badge ok">Ausgewählt</span>{% endif %}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@
|
|||
<div class="subtle">Legal: {{ "OK" if a.legal_checked else "offen" }}</div>
|
||||
{% if a.selected_image_url %}
|
||||
<div class="subtle">Hauptbild gesetzt</div>
|
||||
<a href="{{ a.selected_image_url }}" target="_blank" rel="noopener"><img src="{{ a.selected_image_proxy_url }}" alt="Hauptbild" class="thumb" loading="lazy" /></a>
|
||||
<a href="{{ a.selected_image_url }}" target="_blank" rel="noopener"><img src="{{ a.selected_image_proxy_url }}" data-fallback-src="{{ a.selected_image_url }}" alt="Hauptbild" class="thumb" loading="lazy" onerror="if(!this.dataset.fallbackUsed){this.dataset.fallbackUsed='1';this.src=this.dataset.fallbackSrc;}else{this.classList.add('img-failed');}" /></a>
|
||||
{% endif %}
|
||||
{% if a.summary %}
|
||||
<div><strong>Summary:</strong> {{ a.summary }}</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue