Miniaturas HD, formulario de patrocinadores y sección Lo más visto

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSyR5Zw5eGj6oPTRVjvP1b
This commit is contained in:
Alex
2026-10-20 16:51:36 +02:00
co-authored by Claude Sonnet 5.5
parent e8b1f765c8
commit c8afac0f6d
7 changed files with 207 additions and 93 deletions
+27 -8
View File
@@ -21,6 +21,12 @@ MOTIVOS = {
'tema': 'Propongo un tema',
'otro': 'Otro asunto',
}
TIPOS = {
'mencion': 'Anunciarme / que me mencionéis en el programa',
'patrocinio': 'Patrocinar episodios o una temporada',
'colab': 'Colaboración o canje',
'otro': 'Otra propuesta',
}
EMAIL_RE = re.compile(r'^[^@\s]{1,64}@[^@\s]{1,190}\.[^@\s]{2,}$')
MAX_BODY = 16 * 1024
RATE_MAX, RATE_WINDOW = 5, 3600 # 5 mensajes por IP y hora
@@ -42,16 +48,16 @@ def rate_ok(ip):
return True
def send(nombre, email, motivo, mensaje):
def send(subject, reply_to, body):
host, user, pwd = os.environ.get('SMTP_HOST'), os.environ.get('SMTP_USER'), os.environ.get('SMTP_PASS')
if not (host and user and pwd and os.environ.get('MAIL_TO')):
raise RuntimeError('SMTP no configurado')
msg = EmailMessage()
msg['Subject'] = f'[Web AQNSN] {MOTIVOS[motivo]} · {nombre}'
msg['Subject'] = subject
msg['From'] = os.environ.get('MAIL_FROM', user)
msg['To'] = os.environ['MAIL_TO']
msg['Reply-To'] = email
msg.set_content(f'Motivo: {MOTIVOS[motivo]}\nNombre: {nombre}\nEmail: {email}\n\n{mensaje}\n')
msg['Reply-To'] = reply_to
msg.set_content(body)
s = smtplib.SMTP(host, int(os.environ.get('SMTP_PORT', '587')), timeout=20)
try:
s.ehlo()
@@ -83,8 +89,9 @@ class H(BaseHTTPRequestHandler):
self.wfile.write(body)
def do_POST(self):
if self.path != '/api/contacto':
if self.path not in ('/api/contacto', '/api/patrocinio'):
return self.reply(404, {'ok': False})
patro = self.path == '/api/patrocinio'
try:
n = int(self.headers.get('Content-Length', '0'))
if n <= 0 or n > MAX_BODY:
@@ -99,12 +106,24 @@ class H(BaseHTTPRequestHandler):
if not rate_ok(ip):
return self.reply(429, {'ok': False, 'error': 'Demasiados mensajes. Inténtalo más tarde.'})
nombre, email = clean(d.get('nombre'), 100), clean(d.get('email'), 200)
motivo = d.get('motivo') if d.get('motivo') in MOTIVOS else 'otro'
mensaje = str(d.get('mensaje') or '').replace('\r', '').strip()[:3000]
bad = {'ok': False, 'error': 'Revisa los campos del formulario.'}
if not nombre or not EMAIL_RE.match(email) or len(mensaje) < 10 or d.get('consent') is not True:
return self.reply(400, {'ok': False, 'error': 'Revisa los campos del formulario.'})
return self.reply(400, bad)
if patro:
empresa, sitio = clean(d.get('empresa'), 120), clean(d.get('sitio'), 200)
tipo = d.get('tipo') if d.get('tipo') in TIPOS else 'otro'
if not empresa or (sitio and not re.match(r'^https?://', sitio)):
return self.reply(400, bad)
subject = f'[Web AQNSN · Patrocinio] {empresa} · {nombre}'
body = (f'Empresa: {empresa}\nContacto: {nombre}\nEmail: {email}\nWeb: {sitio or "—"}\n'
f'Interés: {TIPOS[tipo]}\n\n{mensaje}\n')
else:
motivo = d.get('motivo') if d.get('motivo') in MOTIVOS else 'otro'
subject = f'[Web AQNSN] {MOTIVOS[motivo]} · {nombre}'
body = f'Motivo: {MOTIVOS[motivo]}\nNombre: {nombre}\nEmail: {email}\n\n{mensaje}\n'
try:
send(nombre, email, motivo, mensaje)
send(subject, email, body)
except RuntimeError:
return self.reply(503, {'ok': False, 'error': 'El formulario no está disponible ahora mismo.'})
except Exception as exc: