d02c9906a9
- GET /review-builder: Upload-Formular (Word .docx + Bilder Drag/Drop + Metadaten)
- POST /review-builder/build: Word parsen → LLM (DeepSeek Flash) strukturiert
Teaser/Spielerklärung/Fazit → HTML bauen → im Workspace speichern
- Bild-Resize: 1920x1080 @ 72dpi (PIL), Bilderverzeichnis/01/{buchstabe}/{spiel}/
- review_config.json: 25+ Autoren, 5 Disclosures, 5 Rubriken, Komplexitätsstufen
- Live-Publish via Joomla-API optional (Button: Vorschau / Veröffentlichen)
- Link im Dashboard: 📝 Review-Builder
125 lines
5.0 KiB
Bash
Executable File
125 lines
5.0 KiB
Bash
Executable File
#!/bin/bash
|
|
# verify_article.sh — Prüft Artikel-HTML auf Einhaltung aller redaktionellen Standards
|
|
# Usage: bash verify_article.sh <artikel_datei.html>
|
|
# Exit 0 = alle Checks bestanden, Exit 1 = Fehler gefunden
|
|
|
|
set -euo pipefail
|
|
|
|
FILE="${1:-}"
|
|
ERRORS=0
|
|
WARNINGS=0
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
NC='\033[0m'
|
|
|
|
ok() { echo -e "${GREEN}✅ $1${NC}"; }
|
|
fail() { echo -e "${RED}❌ $1${NC}"; ERRORS=$((ERRORS+1)); }
|
|
warn() { echo -e "${YELLOW}⚠️ $1${NC}"; WARNINGS=$((WARNINGS+1)); }
|
|
|
|
if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
|
|
echo "Usage: $0 <artikel_datei.html>"
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== Verifiziere: $FILE ==="
|
|
echo ""
|
|
|
|
# CHECK 1: Genau 1 Read-More
|
|
READMORE_COUNT=$(grep -c 'system-readmore' "$FILE" || true)
|
|
if [ "$READMORE_COUNT" = "1" ]; then ok "Read-More: genau 1"
|
|
elif [ "$READMORE_COUNT" = "0" ]; then fail "Read-More: KEIN <hr id=\"system-readmore\" /> gefunden"
|
|
else fail "Read-More: $READMORE_COUNT Stück (muss genau 1 sein)"; fi
|
|
|
|
# CHECK 2: Kein <br> nach Read-More
|
|
if grep -A1 'system-readmore' "$FILE" | grep -q '<br'; then
|
|
fail "<br> direkt nach Read-More"; else ok "Kein <br> nach Read-More"; fi
|
|
|
|
# CHECK 3: p.meta nicht div.meta
|
|
if grep -q '<div class="meta">' "$FILE"; then
|
|
fail "<div class=\"meta\"> statt <p class=\"meta\">"; else ok "<p class=\"meta\"> korrekt"; fi
|
|
|
|
# CHECK 4: Keine Emojis
|
|
BODY=$(sed -n '/<article>/,/<\/article>/p' "$FILE")
|
|
if echo "$BODY" | grep -qP '[\x{1F300}-\x{1F9FF}\x{2600}-\x{27BF}\x{1F600}-\x{1F64F}]'; then
|
|
fail "Emojis im Artikel-Body gefunden"; else ok "Keine Emojis"; fi
|
|
|
|
# CHECK 5: Endet mit </html>
|
|
LAST_LINE=$(tail -1 "$FILE")
|
|
if echo "$LAST_LINE" | grep -q '</html>'; then ok "Datei endet mit </html>"
|
|
else fail "Letzte Zeile ist nicht </html>"; fi
|
|
|
|
# CHECK 6: Meta-Zeile im Body (Warnung)
|
|
if echo "$BODY" | grep -qP '<p[^>]*>.*\d{1,2}\.\s+\w+\s+\d{4}.*·.*Lesezeit'; then
|
|
warn "Meta-Zeile möglicherweise im Body"; fi
|
|
|
|
# CHECK 7: Quellen als span-strong, nicht h2
|
|
if echo "$BODY" | grep -q '<h2>Quellen</h2>'; then
|
|
fail "<h2>Quellen</h2> statt span-strong"; else ok "Quellen-Überschrift korrekt"; fi
|
|
|
|
# CHECK 8: Alle Links target="_blank"
|
|
LINKS_TOTAL=$(echo "$BODY" | grep -o '<a ' | wc -l)
|
|
LINKS_BLANK=$(echo "$BODY" | grep -o 'target="_blank"' | wc -l)
|
|
if [ "$LINKS_TOTAL" -gt 0 ] && [ "$LINKS_TOTAL" != "$LINKS_BLANK" ]; then
|
|
fail "$((LINKS_TOTAL - LINKS_BLANK)) von $LINKS_TOTAL Links ohne target=\"_blank\""
|
|
else ok "Alle $LINKS_TOTAL Links mit target=\"_blank\""; fi
|
|
|
|
# CHECK 9: Meta-Description
|
|
if grep -q '<meta name="description" content="[^"]*[a-zA-Z]' "$FILE"; then
|
|
ok "Meta-Description vorhanden"; else fail "Meta-Description fehlt oder leer"; fi
|
|
|
|
# CHECK 10: QC inside p.qc
|
|
QC_COUNT=$(echo "$BODY" | grep -c 'class=\"qc\"' || true)
|
|
QC_RAW=$(echo "$BODY" | grep -cP 'QC:\s*[✓✔]' || true)
|
|
if [ "$QC_RAW" -gt "$QC_COUNT" ]; then fail "QC-Hinweis nicht in <p class=\"qc\">"
|
|
else ok "QC-Zeile korrekt in <p class=\"qc\">"; fi
|
|
|
|
# CHECK 11: Umlaute (fix_umlauts.py) — 29.06.2026
|
|
UMLAUT_SCRIPT="$(dirname "$0")/fix_umlauts.py"
|
|
if [ -f "$UMLAUT_SCRIPT" ]; then
|
|
UMLAUT_OUTPUT=$(python3 "$UMLAUT_SCRIPT" "$FILE" 2>&1)
|
|
if echo "$UMLAUT_OUTPUT" | grep -q "⚠"; then
|
|
fail "Umlaut-Fehler gefunden. fix_umlauts.py angewandt — bitte erneut prüfen"
|
|
echo "$UMLAUT_OUTPUT" | grep "⚠" | head -3
|
|
else
|
|
ok "Umlaute: sauber"
|
|
fi
|
|
else
|
|
warn "fix_umlauts.py nicht gefunden, Umlaut-Check übersprungen"
|
|
fi
|
|
|
|
# CHECK 12: vorbestell-box ohne Inline-Styles (10.07.2026)
|
|
if echo "$BODY" | grep -qP '<div[^>]*class="[^"]*vorbestell-box[^"]*"[^>]*style='; then
|
|
fail "vorbestell-box hat Inline-Styles — muss clean sein (nur class=)"
|
|
else ok "vorbestell-box: keine Inline-Styles"; fi
|
|
|
|
# CHECK 13: bsn-facts ohne Inline-Styles (10.07.2026)
|
|
if echo "$BODY" | grep -qP '<div[^>]*class="[^"]*bsn-facts[^"]*"[^>]*style='; then
|
|
fail "bsn-facts hat Inline-Styles — muss clean sein (nur class=)"
|
|
else ok "bsn-facts: keine Inline-Styles"; fi
|
|
|
|
# CHECK 14: Keine veralteten affiliate-box / dotted / cream (10.07.2026)
|
|
if echo "$BODY" | grep -qP 'affiliate-box|border:[^;]*dotted|background:#f5f0eb'; then
|
|
fail "Veraltete Box-Formate (affiliate-box/dotted/cream) — vorbestell-box verwenden!"
|
|
else ok "Keine veralteten Box-Formate"; fi
|
|
|
|
# CHECK 15: Keine strukturellen Inline-Styles im Body (10.07.2026)
|
|
INLINE_DIV_COUNT=$(echo "$BODY" | grep -oP '<div[^>]*style="[^"]*(?:background|border)[^"]*"' | wc -l || true)
|
|
if [ "$INLINE_DIV_COUNT" -gt 0 ]; then
|
|
fail "$INLINE_DIV_COUNT div(s) mit Inline-background/border — BSN-Klassen verwenden!"
|
|
else ok "Keine strukturellen Inline-Styles im Body"; fi
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════"
|
|
if [ $ERRORS -eq 0 ] && [ $WARNINGS -eq 0 ]; then
|
|
echo -e "${GREEN}✅ ALLE CHECKS BESTANDEN${NC}"
|
|
exit 0
|
|
elif [ $ERRORS -eq 0 ]; then
|
|
echo -e "${YELLOW}⚠️ $WARNINGS Warnung(en) — KEINE Fehler${NC}"
|
|
exit 0
|
|
else
|
|
echo -e "${RED}❌ $ERRORS Fehler, $WARNINGS Warnungen${NC}"
|
|
exit 1
|
|
fi
|