Retro-Artikel #46-#57: BSN-Standard-Fixes + Abkürzungs-Check (Daniel 24.06.2026)

This commit is contained in:
Hermes Agent
2026-06-24 23:07:48 +02:00
parent 8902915f03
commit 0a63f0439f
68 changed files with 128219 additions and 5973 deletions
+261
View File
@@ -0,0 +1,261 @@
#!/usr/bin/env python3
"""fix_retro_articles.py — Wendet BSN-Standard auf alle Retro-Artikel an."""
import re, os, sys
from datetime import datetime
WORKSPACE = '/home/hermes/workspace'
TODAY = '24. Juni 2026'
STANDARD_CSS = ''' body {
font-family: Georgia, 'Times New Roman', serif;
line-height: 1.8;
max-width: 720px;
margin: 0 auto;
padding: 2rem 1.5rem;
color: #1a1a1a;
background: #fff;
}
article { }
.dachzeile {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #c00;
margin-bottom: 0.3rem;
font-weight: 600;
}
h1 {
font-size: 2rem;
line-height: 1.25;
margin-top: 0.2rem;
margin-bottom: 0.5rem;
}
.meta {
font-size: 0.9rem;
color: #666;
margin-bottom: 2rem;
}
h2 {
font-size: 1.4rem;
margin-top: 2.5rem;
margin-bottom: 0.8rem;
}
p {
margin-bottom: 1.2rem;
}
blockquote {
margin: 1.5rem 0;
padding: 0.8rem 1.2rem;
border-left: 3px solid #c00;
background: #f9f9f9;
font-style: italic;
color: #444;
}
blockquote p {
margin-bottom: 0.5rem;
}
a {
color: #c00;
text-decoration: underline;
}
a:hover {
text-decoration: none;
}
.vgwort {
margin: 2rem 0 0.5rem;
}
.read-more {
display: none;
}
hr {
border: none;
border-top: 1px solid #ddd;
margin: 2rem 0;
}
.quellen {
font-size: 0.85rem;
color: #555;
margin-top: 3rem;
padding-top: 1rem;
border-top: 1px solid #ddd;
}
.quellen h3 {
font-size: 1rem;
margin-bottom: 0.5rem;
}
.quellen ul {
list-style: none;
padding-left: 0;
}
.quellen li {
margin-bottom: 0.4rem;
word-break: break-all;
}
.infobox {
background: #f0f4f8;
border: 1px solid #d0d8e0;
border-radius: 6px;
padding: 1rem 1.3rem;
margin: 1.2rem 0;
font-size: 0.9rem;
}
.infobox p {
margin: 0.3rem 0;
}
.affiliate-box {
background: #f5f0eb;
border: 1px solid #d4c5b2;
border-radius: 8px;
padding: 1rem 1.5rem;
margin: 1.5rem 0;
}
'''
VG_WORT_PIXEL = '<img id="vgwort-pixel" class="skip-lazy" loading="eager" src="https://vg08.met.vgwort.de/na/be410c78c3944feea4dd471d2ca72234" width="1" height="1" alt="">'
DACHZEILE_MAP = {
'wingspan': 'Retrospektive',
'tainted_grail': 'Retrospektive',
'spirit_island': 'Retrospektive',
'sleeping_gods': 'Retrospektive',
'root': 'Retrospektive',
'oathsworn': 'Retrospektive',
'nemesis': 'Retrospektive',
'gloomhaven': 'Retrospektive',
'earthborne_rangers': 'Retrospektive',
'blood_on_the_clocktower': 'Retrospektive',
'7th_continent': 'Retrospektive',
'sky_team': 'Retrospektive',
'deep_regrets': 'Spielevorstellung',
}
def get_dachzeile(filename):
for key, val in DACHZEILE_MAP.items():
if key in filename:
return val
return 'Retrospektive'
def fix_article(filepath):
with open(filepath) as f:
content = f.read()
name = os.path.basename(filepath)
dachzeile_text = get_dachzeile(name)
# 1. Replace custom <style>...</style> with standard CSS
content = re.sub(
r'<style>.*?</style>',
f'<style>\n{STANDARD_CSS}\n</style>',
content, flags=re.DOTALL
)
# 2. Add meta description if missing
if '<meta name="description"' not in content:
# Extract first 150 chars of body as description
body_match = re.search(r'<article>(.*?)</article>', content, re.DOTALL)
if body_match:
body_text = body_match.group(1)
body_text = re.sub(r'<[^>]+>', ' ', body_text)
body_text = re.sub(r'\s+', ' ', body_text).strip()[:150]
meta_desc = f'<meta name="description" content="{body_text}">'
content = content.replace('<meta name="viewport"',
f'{meta_desc}\n<meta name="viewport"')
# 3. Replace old meta line (<p class="meta">DATE · Retrospektive · Lesezeit: X Minuten</p>)
# with just <p class="meta">DATUM</p>
content = re.sub(
r'<p class="meta">[^<]*</p>',
f'<p class="meta">{TODAY}</p>',
content
)
# 4. Add VG-Wort pixel after <article> tag
if 'vgwort-pixel' not in content:
content = content.replace(
'<article>',
f'<article>\n\n{VG_WORT_PIXEL}\n'
)
# 5. Add dachzeile after VG-Wort pixel, before h1
if 'class="dachzeile"' not in content:
content = re.sub(
r'(<article>\s*' + re.escape(VG_WORT_PIXEL) + r'\s*)<h1>',
rf'\1<p class="dachzeile">{dachzeile_text}</p>\n\n<h1>',
content
)
# 6. Add border-bottom div after h1
if 'border-bottom:2px' not in content:
content = re.sub(
r'(<h1>.*?</h1>)',
r'\1\n<div style="border-bottom:2px solid #e0e0e0;margin-bottom:1.5rem;"></div>',
content
)
# 7. Add read-more after first content paragraph (first <p> after meta)
if '<hr id="system-readmore"' not in content:
# Find the first real paragraph after meta
meta_match = re.search(r'<p class="meta">[^<]*</p>', content)
if meta_match:
meta_end = meta_match.end()
# Find next <p> after meta
next_p = re.search(r'<p>', content[meta_end:])
if next_p:
para_start = meta_end + next_p.start()
# Find end of this paragraph
para_end_match = re.search(r'</p>', content[para_start:])
if para_end_match:
para_end = para_start + para_end_match.end()
content = (content[:para_end] +
f'\n\n<hr id="system-readmore" />\n\n' +
content[para_end:])
# 8. Fix Quellen format: <h2>Quellen</h2> → proper format
if '<h2>Quellen</h2>' in content:
content = content.replace(
'<h2>Quellen</h2>',
'<p><span><strong>Quellen</strong></span></p>'
)
# 9. Add Recherchestand before closing </article> if missing
if 'Recherchestand' not in content:
content = content.replace(
'</article>',
f'<p style="color:#999;font-size:0.8rem;margin-top:2rem;">Recherchestand: {TODAY}.</p>\n</article>'
)
# 10. Ensure all external links have target="_blank"
# Only add to links that don't already have it
def add_target_blank(m):
link = m.group(0)
if 'target=' in link:
return link
return link.replace('<a ', '<a target="_blank" ')
content = re.sub(r'<a href="https?://[^"]*"[^>]*>', add_target_blank, content)
# Write back
with open(filepath, 'w') as f:
f.write(content)
return True
def main():
retro_files = [
f for f in os.listdir(WORKSPACE)
if (('retro_2026-06-15' in f or 'sky_team_2026' in f or 'deep_regrets_2026' in f)
and f.endswith('.html'))
]
for f in sorted(retro_files):
filepath = os.path.join(WORKSPACE, f)
print(f"Fixing: {f}")
try:
fix_article(filepath)
print(f" ✅ Done")
except Exception as e:
print(f" ❌ Error: {e}")
if __name__ == '__main__':
main()