Primer prototipo completado

This commit is contained in:
2026-01-06 11:36:41 -05:00
parent eb0e2b5188
commit 8aca78dbe4
5 changed files with 217 additions and 0 deletions

19
.env.example Normal file
View File

@@ -0,0 +1,19 @@
# Configuración IMAP
IMAP_SERVER=imap.gmail.com
IMAP_USER=tu_correo@gmail.com
IMAP_PASS=tu_contraseña_de_aplicacion
IMAP_FOLDER=INBOX
# Filtros (el dominio en el asunto debe coincidir con el doominio del blog)
DOMAIN_TO_SEARCH=dominio.com
# Palabras clave a ignorar separadas por coma
IGNORE_KEYWORDS=publicidad,notificacion,spam
# Opciones de salida (True para activar, False para desactivar)
SAVE_HTML=True
SAVE_XML=True
SEND_WEBHOOK=True
# URL del Webhook de WordPress (reemplaza tudominio por la palabra que definas en el webhook de wordpress)
WP_WEBHOOK_URL=https://tusitio.com/wp-json/tudominio/v1/recibir-comentario
WEBHOOK_SECRET_TOKEN=mi_clave_secreta_123

123
email-processor.py Normal file
View File

@@ -0,0 +1,123 @@
import imaplib
import email
import os
import requests
import re
from email.header import decode_header
from dotenv import load_dotenv
from bs4 import BeautifulSoup
import xml.etree.ElementTree as ET
from datetime import datetime
load_dotenv()
def clean_html(raw_html):
# Permite solo etiquetas básicas
allowed_tags = ['b', 'i', 'u', 'em', 'strong', 'blockquote', 'p', 'br']
soup = BeautifulSoup(raw_html, "html.parser")
for tag in soup.find_all(True):
if tag.name not in allowed_tags:
tag.unwrap() # Elimina la etiqueta pero mantiene el texto interno
else:
tag.attrs = {} # Elimina atributos como class, style, id
return str(soup)
def get_sender_name(from_header):
# Extrae "Nombre" de "Nombre <correo@dominio.com>" o la parte antes del @
name, encoding = decode_header(from_header)[0]
if isinstance(name, bytes):
name = name.decode(encoding or "utf-8")
if "<" in name:
name = name.split("<")[0].strip()
elif "@" in name:
name = name.split("@")[0]
return name.strip()
def process_emails():
mail = imaplib.IMAP4_SSL(os.getenv("IMAP_SERVER"))
mail.login(os.getenv("IMAP_USER"), os.getenv("IMAP_PASS"))
mail.select(os.getenv("IMAP_FOLDER"))
status, messages = mail.search(None, 'ALL')
domain = os.getenv("DOMAIN_TO_SEARCH")
ignore_list = [w.strip().lower() for w in os.getenv("IGNORE_KEYWORDS").split(',')]
print(messages)
for m_id in messages[0].split():
res, msg_data = mail.fetch(m_id, '(RFC822)')
for response_part in msg_data:
if isinstance(response_part, tuple):
msg = email.message_from_bytes(response_part[1])
# 1. Obtener Asunto (Título de la página)
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
subject = subject.decode(encoding or "utf-8")
# 2. Filtrar por dominio en el asunto (URL)
if domain not in subject.lower():
continue
# 3. Obtener Remitente (Nombre sin dominio)
sender_raw = msg.get("From")
sender_name = get_sender_name(sender_raw)
# 4. Obtener Cuerpo y Limpiar HTML
body = ""
if msg.is_multipart():
for part in msg.walk():
if part.get_content_type() == "text/html":
body = part.get_payload(decode=True).decode()
break
else:
body = msg.get_payload(decode=True).decode()
# Validar ignore_list en el contenido
if any(word in body.lower() for word in ignore_list):
continue
clean_body = clean_html(body)
rss_title = f"Comentario de {sender_name} sobre {subject}"
# --- ACCIONES ---
# HTML: Negritas, cursivas, etc.
if os.getenv("SAVE_HTML") == "True":
with open(f"msg_{m_id.decode()}.html", "w", encoding="utf-8") as f:
f.write(f"<h3>Remitente: {sender_name}</h3><div>{clean_body}</div>")
# XML: Formato RSS
if os.getenv("SAVE_XML") == "True":
rss = ET.Element("rss", version="2.0")
channel = ET.SubElement(rss, "channel")
item = ET.SubElement(channel, "item")
ET.SubElement(item, "title").text = rss_title
ET.SubElement(item, "description").text = clean_body
ET.SubElement(item, "author").text = sender_name
ET.SubElement(item, "pubDate").text = datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")
ET.ElementTree(rss).write(f"msg_{m_id.decode()}.xml", encoding="utf-8", xml_declaration=True)
# Webhook: Enviar a la URL citada en el asunto
if os.getenv("SEND_WEBHOOK") == "True":
# El asunto es la URL de la página citada
target_url = subject if "http" in subject else f"http://{subject}"
try:
requests.post(os.getenv("WP_WEBHOOK_URL"), json={
"auth_token": os.getenv("WEBHOOK_SECRET_TOKEN"),
"target_page": target_url,
"author_name": sender_name,
"comment_content": clean_body
})
except: pass
# Borrar
mail.store(m_id, '+FLAGS', '\\Deleted')
mail.expunge()
mail.logout()
if __name__ == "__main__":
process_emails()

62
functions.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
/**
* Registrar el endpoint del Webhook en la API REST de WordPress
* Reemplaza tudominio en register_rest_route por la palabra que quieras, pero recuerda cambiarlo en el archivo .env tambien
*/
add_action('rest_api_init', function () {
register_rest_route('tudominio/v1', '/recibir-comentario', array(
'methods' => 'POST',
'callback' => 'procesar_webhook_comentario',
'permission_callback' => '__return_true', // La validación se hace dentro del callback
));
});
function procesar_webhook_comentario(WP_REST_Request $request) {
// 1. Obtener datos del JSON enviado por Python
$params = $request->get_json_params();
$token_recibido = $params['auth_token'] ?? '';
$token_esperado = 'mi_clave_secreta_123'; // Debe coincidir con WEBHOOK_SECRET_TOKEN
// 2. Validar Token de Seguridad
if ($token_recibido !== $token_esperado) {
return new WP_Error('forbidden', 'No autorizado', array('status' => 403));
}
// 3. Extraer y sanear datos
$target_url = esc_url_raw($params['target_page']);
$author_name = sanitize_text_field($params['author_name']);
$comment_body = wp_kses($params['comment_content'], array(
'b' => array(), 'i' => array(), 'u' => array(),
'em' => array(), 'strong' => array(), 'blockquote' => array(), 'p' => array(), 'br' => array()
));
// 4. Buscar el ID del post/página mediante la URL
$post_id = url_to_postid($target_url);
if ($post_id === 0) {
return new WP_REST_Response(array('error' => 'URL de destino no encontrada en este sitio'), 404);
}
// 5. Insertar el comentario
$comment_data = array(
'comment_post_ID' => $post_id,
'comment_author' => $author_name,
'comment_author_email' => 'webhook@tudominio.com', // Email genérico para identificar origen
'comment_content' => $comment_body,
'comment_type' => 'comment',
'comment_parent' => 0,
'comment_approved' => 0, // 0 = Pendiente de moderación, 1 = Aprobado automáticamente
);
$comment_id = wp_insert_comment($comment_data);
if ($comment_id) {
return new WP_REST_Response(array(
'success' => true,
'message' => 'Comentario recibido y enviado a moderación',
'comment_id' => $comment_id
), 200);
} else {
return new WP_Error('db_error', 'No se pudo insertar el comentario', array('status' => 500));
}
}

6
msg_29.html Normal file
View File

@@ -0,0 +1,6 @@
<h3>Remitente: channel2rss</h3><div><!DOCTYPE html>
<p>sdfgsdfgsdfg</p><p> </p><p>sdfgsdfg</p><p> </p><p>sdfgsdfg</p><p> </p><p> </p><p>que no se pierdan los saltos ded linea</p>
</div>

7
msg_29.xml Normal file
View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<rss version="2.0"><channel><item><title>Comentario de channel2rss sobre https://interlan.ec/blog/2025/12/26/analisis-de-logs-la-teoria-del-internet-parece-real/</title><description>&lt;!DOCTYPE html&gt;
&lt;p&gt;sdfgsdfgsdfg&lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;p&gt;sdfgsdfg&lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;p&gt;sdfgsdfg&lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;p&gt; &lt;/p&gt;&lt;p&gt;que no se pierdan los saltos ded linea&lt;/p&gt;
</description><author>channel2rss</author><pubDate>Tue, 06 Jan 2026 11:09:57 GMT</pubDate></item></channel></rss>