diff --git a/README.md b/README.md index fcf7f00..37b1f2e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,70 @@ -# opml-to-blogroll +# Opml-to-blogroll -Convierte un archivo OPML exportado a una salida BlogRoll segun una plantilla definida para insertar o copiar en un blog \ No newline at end of file +Script en python bastante manual basado en lo que vi en el blog de [DanQ](https://danq.me/2026/01/20/blogroll-88x31s/) + +Me encantan los iconitos pixelados que pone el generador de el, pero no tengo idea de donde los saca ni he visto a nadie por este ecosistema hacer uso de ellos. + +Un script de Python versátil diseñado para convertir archivos de suscripciones RSS (OPML/XML) en formatos listos para publicar en la web. Ideal para crear "blogrolls" o directorios de lectura en WordPress, blogs estáticos o documentación personal. + +# Descripción (Español) + +Este script permite transformar tus exportaciones de FreshRSS (o cualquier lector RSS) en piezas de contenido visualmente atractivas o estructuradas. + +# Funcionalidades + +- Entrada Dual: Soporta archivos .opml y .xml. +- Modo Híbrido: + - Ejecución por línea de comandos para automatización. + - Interfaz Gráfica (GUI) con Tkinter para un uso sencillo. +- Formatos de Salida: + - HTML: Genera tarjetas con bordes redondeados y diseño responsivo utilizando CSS Inline (100% compatible con WordPress y otros CMS que bloquean etiquetas \). + - Markdown: Crea tablas limpias escapando caracteres especiales (como \|) para evitar errores de formato. + - JSON: Exporta una estructura de datos limpia para desarrolladores. +Portabilidad: No requiere librerías externas (solo Python 3.x). + +# Uso + +- **Gráfico**: Ejecuta python script.py sin argumentos. +- **Comandos**: python script.py archivo.opml -f markdown + +# Description (English) + +A versatile Python script designed to convert RSS subscription files (OPML/XML) into web-ready formats. Perfect for creating "blogrolls" or reading directories on WordPress, static blogs, or personal documentation. + +# Features + +- Dual Input: Supports both .opml and .xml files. +- Hybrid Mode: + - Command Line Interface (CLI) for automation. + - Graphical User Interface (GUI) via Tkinter for ease of use. +- Output Formats: + - HTML: Generates responsive, rounded cards using Inline CSS (100% compatible with WordPress and CMS platforms that strip \ tags). + - Markdown: Creates clean tables with auto-escaping for special characters (like |) to ensure formatting stability. + - JSON: Exports a clean data structure for developers. +- Portability: Built using standard Python libraries only (Python 3.x). + +# Usage + +- **GUI**: Run python script.py without arguments. +- **CLI**: python script.py file.opml -f html + +# Instalación / Installation + +Clona este repositorio / Clone this repo: +```bash +git clone https://git.interlan.ec/Drk0027/opml-to-blogroll +``` + +Asegúrate de tener Python instalado / Ensure Python is installed: + +```bash +python main.py +``` + +# Licencia / License + +MIT License - Siéntete libre de usarlo y mejorarlo. / Feel free to use and improve it. + +# Nota de mejora: + +Este script ha sido optimizado para la compatibilidad moderna con WordPress Gutenberg y motores de renderizado de Markdown estrictos, asegurando que los enlaces y estilos se mantengan intactos independientemente de la plataforma de destino. \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..816990d --- /dev/null +++ b/index.html @@ -0,0 +1,113 @@ + + + + + + OPML-to-BlogRoll + + + + +
+

OPML-to-BlogRoll

+ +
+ + +
+ +
+ + +
+ + + +
Versión Web Estática - No necesita instalacion
+
+ + + + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..6a91b1c --- /dev/null +++ b/main.py @@ -0,0 +1,101 @@ +import xml.etree.ElementTree as ET +import json +import argparse +import sys +import os +from tkinter import filedialog, ttk, Tk, messagebox + +def escapar_markdown(texto): + if not texto: return "" + return texto.replace("|", "\\|").replace("[", "\\[").replace("]", "\\]") + +def procesar_feeds(ruta_entrada, formato_salida): + try: + tree = ET.parse(ruta_entrada) + root = tree.getroot() + except Exception as e: + return f"Error: {e}" + + feeds = [] + for outline in root.findall(".//outline[@xmlUrl]"): + feeds.append({ + "titulo": outline.get('title') or outline.get('text') or "Sin título", + "url_web": outline.get('htmlUrl') or "#", + "url_rss": outline.get('xmlUrl') or "#" + }) + + nombre_base = os.path.splitext(ruta_entrada)[0] + + # Con formatos integrados inline para evitar dramas de etiquetas prohibidas. + if formato_salida == 'html': + # Contenedor principal con CSS Inline + output = '
' + + for f in feeds: + # Estilos directos en cada tarjeta y enlace + card_style = 'border: 1px solid #e1e4e8; border-radius: 12px; padding: 15px; display: flex; justify-content: space-between; align-items: center; background: #ffffff; box-shadow: 0 2px 4px rgba(0,0,0,0.05);' + title_style = 'font-weight: bold; color: #0366d6; text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1;' + rss_style = 'background: #f68140; color: #ffffff !important; padding: 5px 10px; border-radius: 6px; font-size: 12px; text-decoration: none; font-weight: bold; margin-left: 12px;' + + output += f'\n
' + output += f'\n {f["titulo"]}' + output += f'\n RSS' + output += f'\n
' + + output += "\n
" + ext = ".html" + + elif formato_salida == 'markdown': + output = "| Sitio Web | Feed RSS |\n| :--- | :---: |\n" + for f in feeds: + titulo_limpio = escapar_markdown(f['titulo']) + output += f"| [{titulo_limpio}]({f['url_web']}) | [RSS]({f['url_rss']}) |\n" + ext = ".md" + + elif formato_salida == 'json': + output = json.dumps(feeds, indent=4, ensure_ascii=False) + ext = ".json" + + archivo_final = nombre_base + ext + with open(archivo_final, "w", encoding="utf-8") as f: + f.write(output) + return archivo_final + +def iniciar_gui(): + root = Tk() + root.title("OPML-to-BlogRoll") + root.geometry("400x250") + archivo_ruta = {"path": ""} + + def seleccionar(): + path = filedialog.askopenfilename(filetypes=[("Feeds", "*.opml *.xml")]) + if path: + archivo_ruta["path"] = path + lbl.config(text=os.path.basename(path)) + + def ejecutar(): + if not archivo_ruta["path"]: return + res = procesar_feeds(archivo_ruta["path"], combo.get().lower()) + messagebox.showinfo("Éxito", f"Generado: {res}") + root.destroy() + + ttk.Label(root, text="Convertidor OPML/XML", font=("Arial", 12, "bold")).pack(pady=15) + ttk.Button(root, text="Seleccionar Archivo", command=seleccionar).pack() + lbl = ttk.Label(root, text="Ninguno", foreground="gray") + lbl.pack(pady=5) + combo = ttk.Combobox(root, values=["HTML", "Markdown", "JSON"], state="readonly") + combo.set("HTML") + combo.pack(pady=10) + ttk.Button(root, text="Convertir", command=ejecutar).pack(pady=10) + root.mainloop() + +if __name__ == "__main__": + if len(sys.argv) > 1: + parser = argparse.ArgumentParser() + parser.add_argument("entrada") + parser.add_argument("-f", "--formato", choices=['html', 'markdown', 'json'], default='html') + args = parser.parse_args() + if os.path.exists(args.entrada): + print(f"Generado: {procesar_feeds(args.entrada, args.formato)}") + else: + iniciar_gui()