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:
co-authored by
Claude Sonnet 5.5
parent
e8b1f765c8
commit
c8afac0f6d
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user