"""Genera index.html a partir del feed RSS del canal (se ejecuta cada 15 min en el contenedor).""" import html import os import re import unicodedata from collections import Counter import shutil import sys import urllib.request import xml.etree.ElementTree as ET OUT = os.environ.get('SITE_DIR', 'public') CHANNEL = 'UC8gVtzB3Lqm7nnfMhMvXGxQ' # YouTube separa cada tipo en su propia lista: UULF = solo vídeos, UUSH = solo Shorts FEED_VIDEOS = os.environ.get('FEED_VIDEOS_URL', f'https://www.youtube.com/feeds/videos.xml?playlist_id=UULF{CHANNEL[2:]}') FEED_SHORTS = os.environ.get('FEED_SHORTS_URL', f'https://www.youtube.com/feeds/videos.xml?playlist_id=UUSH{CHANNEL[2:]}') VIDEOS_SHOWN = 8 # además del destacado SHORTS_SHOWN = 12 NS = {'a': 'http://www.w3.org/2005/Atom', 'y': 'http://www.youtube.com/xml/schemas/2015', 'm': 'http://search.yahoo.com/mrss/'} MESES = ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'] YT = 'https://www.youtube.com/@andaquenosabesna' TT = 'https://www.tiktok.com/@andaquenosabesna' IG = 'https://www.instagram.com/andaquenosabesna' MAIL = 'andaquenosabesnada@gmail.com' def fetch(url, timeout=20): req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0 (aqnsn-site)'}) return urllib.request.urlopen(req, timeout=timeout).read() def read_feed(url): root = ET.fromstring(fetch(url)) out = [] for e in root.findall('a:entry', NS)[:15]: d = e.find('a:published', NS).text[:10].split('-') out.append({ 'id': e.find('y:videoId', NS).text, 'title': e.find('a:title', NS).text, 'date': f'{int(d[2])} {MESES[int(d[1]) - 1]} {d[0]}', 'desc': e.find('m:group/m:description', NS).text or '', }) return out try: videos, shorts = read_feed(FEED_VIDEOS), read_feed(FEED_SHORTS) except Exception as exc: # sin red o feed caído: se conserva la web anterior print('feed no disponible, se mantiene index.html:', exc, file=sys.stderr) sys.exit(0) videos, shorts = videos[:VIDEOS_SHOWN + 1], shorts[:SHORTS_SHOWN] def sync_thumbs(vids): img_dir = os.path.join(OUT, 'img') os.makedirs(img_dir, exist_ok=True) keep = {'avatar.jpg', 'banner.jpg'} ok = [] for v in vids: path = os.path.join(img_dir, v['id'] + '.jpg') if not os.path.exists(path): try: open(path, 'wb').write(fetch(f"https://i.ytimg.com/vi/{v['id']}/hqdefault.jpg")) except Exception as exc: print('miniatura no descargada', v['id'], exc, file=sys.stderr) continue keep.add(v['id'] + '.jpg') ok.append(v) for f in os.listdir(img_dir): if f not in keep: os.remove(os.path.join(img_dir, f)) return ok ok = sync_thumbs(videos + shorts) okids = {v['id'] for v in ok} videos = [v for v in videos if v['id'] in okids] shorts = [v for v in shorts if v['id'] in okids] if not videos: sys.exit(0) # --- Temas: hashtags de las descripciones de YouTube ------------------------------------------- STOP = {'podcast', 'podcastclips', 'andaquenosabesna', 'andaquenosabesna', 'aqnsn', 'shorts', 'short', 'fyp', 'viral', 'parati', 'galaxoide', 'youtube', 'video', 'clips'} LABELS = { # clave normalizada -> texto a mostrar (sinónimos incluidos) 'egipto': 'Egipto', 'egiptomania': 'Egipto', 'educacion': 'Educación', 'arabe': 'Lengua árabe', 'lenguas': 'Lengua árabe', 'religion': 'Religiones', 'religiones': 'Religiones', 'magia': 'Magia', 'burocracia': 'Burocracia', 'poligamia': 'Poligamia', 'economia': 'Economía', 'gastronomia': 'Gastronomía', 'comidaegipcia': 'Comida egipcia', 'robotica': 'Robótica', 'tecnologia': 'Tecnología', 'agricultura': 'Agricultura', 'curiosidades': 'Curiosidades', } PINNED = ['Cultura granaína', 'Entrevistas'] # siempre presentes MAX_TOPICS = 12 def norm(tag): t = unicodedata.normalize('NFKD', tag.lower()) return ''.join(c for c in t if not unicodedata.combining(c)) def topics(vids): count = Counter() for v in vids: seen = set() for tag in re.findall(r'#(\w+)', v['desc']): k = norm(tag) if k in STOP: continue label = LABELS.get(k) or (tag[0].upper() + tag[1:]) if label in seen: # sinónimos del mismo vídeo cuentan una sola vez continue seen.add(label) count[label] += 1 ranked = [t for t, _ in sorted(count.items(), key=lambda kv: (-kv[1], kv[0]))] out = [t for t in ranked if t not in PINNED][:max(MAX_TOPICS - len(PINNED), 0)] return out + PINNED chips = ''.join(f'
  • {html.escape(t)}
  • ' for t in topics(videos + shorts)) esc = html.escape featured, rest = videos[0], videos[1:] # el destacado es el último vídeo (no Short) def url(v): return f"https://www.youtube.com/watch?v={v['id']}" def card(v, n): return f''' {esc(v['title'])} ''' def short_card(v, n): return f''' {esc(v['title'])} ''' cards = ''.join(card(v, i) for i, v in enumerate(rest)) scards = ''.join(short_card(v, i) for i, v in enumerate(shorts)) page = f''' Anda Que No Sabes Ná · El programa nocturno 100% granaíno
    Anda que no sabes ná
    Anda Que No Sabes Ná

    Con invitados que están aquí y otros que están pa allá

    El programa nocturno
    100% granaíno

    Charlas sobre lo que sabemos, lo que no, y lo que creemos saber.

    Últimos vídeos

    Todos los vídeos →
    {cards}

    Últimos Shorts

    Todos los Shorts →
    {scards}

    De qué va

    Un programa de noche, con tertulia y sin prisa. Cada semana entra gente distinta, con su oficio y su manía, y se habla de lo que sale: de cómo se educa a un adolescente, de la vida en las calles de El Cairo, de si el árabe es tan antiguo como dicen, de qué tienen en común las religiones o de cómo se hace un truco de cartas.

    Quiénes somos

    Somos Alejandro y Juan, las dos personas detrás de Anda Que No Sabes Ná: un programa hecho en Granada, de noche, con ganas de aprender de quien se sienta a charlar.

    AlejandroDa forma a las ideasArquitecto de formación y líder de equipos de ingeniería. Construye productos digitales y herramientas para equipos técnicos, y anda metido en cómo usar la IA en el día a día.
    JuanCaptura la realidadConsultor en metodología BIM y escáner láser 3D. Ayuda a las empresas de la arquitectura, la ingeniería y la construcción a dar el salto digital con mejor calidad y menos costes.

    ¿Quieres venir al programa?

    Cuéntanos quién eres y de qué te gustaría hablar.

    Escríbenos

    Síguenos

    ''' tmp = os.path.join(OUT, 'index.html.tmp') open(tmp, 'w', encoding='utf-8').write(page) shutil.move(tmp, os.path.join(OUT, 'index.html')) print('index.html', len(page), 'bytes;', len(videos), 'videos; destacado:', featured['id'])