Adds a script to process Hugo Markdown files, extract previews from linked 'reply' URLs, and flag Mastodon links. This enhances content display and context for replies, especially on Mastodon. It also creates a new Github Action to run the script on a schedule.
245 lines
9.7 KiB
Python
245 lines
9.7 KiB
Python
import os
|
|
import re
|
|
import requests
|
|
import frontmatter
|
|
from bs4 import BeautifulSoup
|
|
|
|
# --- Funzioni di Supporto ---
|
|
|
|
HUGO_CONTENT_PATH = "content"
|
|
|
|
|
|
def get_page_content(url):
|
|
"""
|
|
Recupera il contenuto HTML di una pagina web dato il suo URL.
|
|
Gestisce errori di rete e timeout.
|
|
"""
|
|
try:
|
|
response = requests.get(url, timeout=10)
|
|
response.raise_for_status()
|
|
return response.text
|
|
except requests.exceptions.RequestException as e:
|
|
print(f"Errore nel recupero dell'URL {url}: {e}")
|
|
return None
|
|
|
|
|
|
def extract_preview_from_html(html_content, max_length=200):
|
|
"""
|
|
Estrae una porzione di testo pulita dal contenuto HTML per una preview.
|
|
Prioritizza l'estrazione da:
|
|
1. Primo elemento con classe 'e-content'.
|
|
2. Se 'e-content' manca, il primo elemento con classe 'p-summary'.
|
|
Se entrambi mancano, restituisce una stringa vuota.
|
|
Rimuove il markup HTML, gli script e gli stili in modo robusto.
|
|
"""
|
|
if not html_content:
|
|
return ""
|
|
|
|
soup = BeautifulSoup(html_content, "html.parser")
|
|
|
|
# Rimuove tutti i tag <script> e <style>
|
|
for script_or_style in soup(["script", "style"]):
|
|
script_or_style.decompose()
|
|
|
|
text = ""
|
|
|
|
# 1. Cerca il primo elemento con la classe 'e-content'
|
|
e_content_element = soup.find(class_="e-content")
|
|
|
|
if e_content_element:
|
|
text = e_content_element.get_text()
|
|
else:
|
|
# 2. Se 'e-content' manca, cerca il primo elemento con la classe 'p-summary'
|
|
p_summary_element = soup.find(class_="p-summary")
|
|
if p_summary_element:
|
|
text = p_summary_element.get_text()
|
|
# 3. Se entrambi mancano, 'text' rimane una stringa vuota
|
|
|
|
text = re.sub(r"\s+", " ", text).strip()
|
|
|
|
if len(text) > max_length:
|
|
return text[:max_length] + "..."
|
|
return text
|
|
|
|
|
|
def is_mastodon_link(url):
|
|
"""
|
|
Checks if a given URL is a Mastodon link.
|
|
|
|
Args:
|
|
url: The URL string to check.
|
|
|
|
Returns:
|
|
True if the URL is a Mastodon link, False otherwise.
|
|
"""
|
|
# Regular expression to match common Mastodon URL patterns.
|
|
# This includes:
|
|
# - https:// or http://
|
|
# - A domain name (e.g., example.social, mastodon.online)
|
|
# - Optional port number (:port)
|
|
# - Optional path components, including user profiles (@user or /@user)
|
|
# - Handles various subdomains and TLDs.
|
|
mastodon_pattern = re.compile(
|
|
r"^(https?://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(:\d{1,5})?(/.*)?$"
|
|
)
|
|
|
|
# Some common Mastodon instance keywords or patterns in the domain
|
|
# This is a heuristic and might not catch all Mastodon instances,
|
|
# but covers many common ones.
|
|
mastodon_keywords = [
|
|
"mastodon",
|
|
"masto",
|
|
"social",
|
|
"toot",
|
|
"floss.social",
|
|
"fosstodon",
|
|
"pleroma.social", # Pleroma is compatible and often hosted similarly
|
|
# Add more specific instance names if known
|
|
]
|
|
|
|
# Check for the presence of '@' in the path, which is common for user profiles
|
|
# (e.g., https://mastodon.social/@username)
|
|
if "@" in url:
|
|
return True
|
|
|
|
# Check if the URL matches the general pattern
|
|
if mastodon_pattern.match(url):
|
|
# Further refine by checking for common Mastodon keywords in the hostname
|
|
from urllib.parse import urlparse
|
|
|
|
parsed_url = urlparse(url)
|
|
hostname = parsed_url.hostname.lower()
|
|
|
|
for keyword in mastodon_keywords:
|
|
if keyword in hostname:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
# --- Funzione Principale ---
|
|
|
|
|
|
def process_hugo_markdown_files(root_dir):
|
|
"""
|
|
Scorre ricorsivamente i file Markdown di un sito Hugo.
|
|
Trova i campi 'reply' nel front matter, genera una preview
|
|
dal contenuto della pagina collegata e aggiunge un flag Mastodon se applicabile.
|
|
"""
|
|
for dirpath, _, filenames in os.walk(root_dir):
|
|
for filename in filenames:
|
|
if filename.endswith((".md", ".markdown")):
|
|
filepath = os.path.join(dirpath, filename)
|
|
print(f"Elaborazione del file: {filepath}")
|
|
|
|
modified = False # Flag per sapere se il file è stato modificato e deve essere salvato
|
|
|
|
try:
|
|
with open(filepath, encoding="utf-8") as f:
|
|
post = frontmatter.load(f)
|
|
|
|
if "reply" in post.metadata and post.metadata["reply"]:
|
|
reply_url = post.metadata["reply"]
|
|
print(f" Trovata URL di risposta: {reply_url}")
|
|
|
|
# --- Rilevamento Mastodon ---
|
|
if is_mastodon_link(reply_url):
|
|
if post.metadata.get("mastodon_reply") is not True:
|
|
post.metadata["mastodon_reply"] = True
|
|
modified = True
|
|
print(
|
|
f" Flag 'mastodon_reply: true' aggiunto/aggiornato per {reply_url}"
|
|
)
|
|
elif post.metadata.get("mastodon_reply") is True:
|
|
# Se non è più un link Mastodon ma il flag era presente, rimuovilo
|
|
del post.metadata["mastodon_reply"]
|
|
modified = True
|
|
print(
|
|
f" Flag 'mastodon_reply' rimosso per {reply_url} (non più Mastodon)."
|
|
)
|
|
# --- Fine Rilevamento Mastodon ---
|
|
|
|
html_content = get_page_content(reply_url)
|
|
if html_content:
|
|
preview_text = extract_preview_from_html(html_content)
|
|
|
|
if preview_text:
|
|
# Aggiunge o aggiorna il campo 'preview_text_from_reply'
|
|
if (
|
|
post.metadata.get("preview_text_from_reply")
|
|
!= preview_text
|
|
):
|
|
post.metadata["preview_text_from_reply"] = (
|
|
preview_text
|
|
)
|
|
modified = True
|
|
print(
|
|
f" Preview generata/aggiornata: {preview_text}"
|
|
)
|
|
else:
|
|
# Se la preview è vuota e il campo esisteva, rimuovilo
|
|
if "preview_text_from_reply" in post.metadata:
|
|
del post.metadata["preview_text_from_reply"]
|
|
modified = True
|
|
print(
|
|
f" Nessun contenuto valido. Rimosso il campo preview da {filename}."
|
|
)
|
|
else:
|
|
print(
|
|
f" Nessun contenuto valido da estrarre per {reply_url}. Nessun campo preview aggiunto."
|
|
)
|
|
else:
|
|
# Se impossibile recuperare, rimuovi la preview se esisteva
|
|
if "preview_text_from_reply" in post.metadata:
|
|
del post.metadata["preview_text_from_reply"]
|
|
modified = True
|
|
print(
|
|
f" Impossibile recuperare il contenuto per {reply_url}."
|
|
+ " Rimosso il campo preview da {filename}."
|
|
)
|
|
else:
|
|
print(
|
|
f" Impossibile recuperare il contenuto per {reply_url}. Nessun campo preview presente."
|
|
)
|
|
else:
|
|
print(" Nessun campo 'reply' trovato o è vuoto.")
|
|
# Se il campo 'reply' non c'è, assicurati che i campi di preview e mastodon_reply siano rimossi
|
|
if "preview_text_from_reply" in post.metadata:
|
|
del post.metadata["preview_text_from_reply"]
|
|
modified = True
|
|
print(
|
|
f" Campo 'reply' mancante. Rimosso il campo preview da {filename}."
|
|
)
|
|
if "mastodon_reply" in post.metadata:
|
|
del post.metadata["mastodon_reply"]
|
|
modified = True
|
|
print(
|
|
f" Campo 'reply' mancante. Rimosso il flag Mastodon da {filename}."
|
|
)
|
|
|
|
# Salva il file SOLO se sono state apportate modifiche
|
|
if modified:
|
|
with open(filepath, "wb") as f_write:
|
|
frontmatter.dump(post, f_write)
|
|
print(f" File {filename} salvato con le modifiche.")
|
|
|
|
except Exception as e:
|
|
print(f"Errore durante l'elaborazione del file {filepath}: {e}")
|
|
|
|
|
|
# --- Configurazione e Esecuzione ---
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if not os.path.isdir(HUGO_CONTENT_PATH):
|
|
print(f"Errore: La cartella {HUGO_CONTENT_PATH} non esiste.")
|
|
print(
|
|
"Assicurati di impostare il percorso corretto alla cartella 'content' del tuo sito Hugo."
|
|
)
|
|
else:
|
|
print(
|
|
"Avvio del processo di generazione delle preview e rilevamento Mastodon..."
|
|
)
|
|
process_hugo_markdown_files(HUGO_CONTENT_PATH)
|
|
print("Processo completato.")
|