NapNodes

← Guías de Bots de Discord

Bots de Discord · actualizada el 23 de octubre de 2026

Bot de Discord que avisa de tus directos de Twitch y tus vídeos de YouTube

Si haces directos o subes vídeos, lo que más gente trae es que tu Discord se entere en el momento. Este bot mira cada dos minutos si estás en directo en Twitch o has subido algo a YouTube y lo anuncia en el canal que elijas, con mención al rol que quieras. Son unas 120 líneas de Python y no pide ninguna clave de YouTube.

¿Se lo pasas a tu grupo?WhatsApp

Cómo funciona

Cada dos minutos el bot pregunta a la API de Twitch qué canales de tu lista están en directo y lee el feed público de cada canal de YouTube (los últimos 15 vídeos, sin clave ni cuota). Lo que ya ha avisado lo apunta en un fichero, avisados.json, para no repetir el aviso si se reinicia.

¿Por qué cada dos minutos y no al instante? Twitch tiene avisos instantáneos (EventSub), pero por webhook necesitan una dirección web con HTTPS a la que Twitch pueda llamar, y por WebSocket piden un token de usuario que hay que ir renovando. Preguntar cada pocos minutos funciona en cualquier sitio, sin abrir puertos, y para un aviso de directo dos minutos de retraso no se notan.

Si aún no has hecho ningún bot, empieza por tu primer bot en Python: este parte del mismo esqueleto.

¿Prefieres no montarlo tú? Hosting de bots de Discord desde 1,50 € al mes, sin permanencia y con devolución íntegra en 72 horas. O crea un bot de Discord gratis, sin tarjeta.

1. Las claves que necesitas

  1. El bot de Discord

    Como en cualquier bot: crea la aplicación en discord.com/developers/applications, saca el token en «Bot» e invítalo con el permiso «bot». Dale en el canal de avisos «Ver canal», «Enviar mensajes» e «Insertar enlaces» (para la tarjeta del directo). Si vas a mencionar a @everyone o a un rol que no se puede mencionar, también «Mencionar @everyone, @here y todos los roles».

  2. La aplicación de Twitch

    En dev.twitch.tv/console, «Registrar tu aplicación». Twitch pide tener activada la verificación en dos pasos en tu cuenta. Ponle un nombre, como URL de redirección http://localhost, una categoría y tipo de cliente «Confidencial». Al guardarla te da el Client ID; pulsa «Nuevo secreto» para el Client Secret. Con los dos, el bot pide solo su token de aplicación (y otro cuando caduca).

  3. El ID del canal de YouTube

    Empieza por UC y tiene 24 caracteres. En la página del canal, abre la descripción («…más»), baja hasta «Compartir canal» y pulsa «Copiar ID del canal». Si la dirección del canal es youtube.com/channel/UC…, ya lo tienes en ella.

  4. Los ID de Discord

    Activa «Ajustes de usuario» → «Avanzado» → «Modo desarrollador». Después, clic derecho en el canal de avisos → «Copiar ID del canal»; y, para mencionar a un rol, en «Ajustes del servidor» → «Roles», clic derecho en el rol → «Copiar ID del rol». La mención se escribe <@&ID>.

2. El código

Tres ficheros en una carpeta. En .env van las claves y las listas (separadas por comas, sin espacios ni comillas): TWITCH_USUARIOS es el nombre del canal tal como sale en twitch.tv/nombre; si solo quieres Twitch o solo YouTube, deja la otra lista vacía. MENCION puede ser <@&ID> de un rol, @everyone o nada.

La primera vez, del YouTube solo apunta los vídeos que ya había (no avisa de lo viejo); de Twitch, si ya estás en directo al encenderlo, avisa una vez. Cada directo tiene su número, así que si el bot se reinicia a mitad no vuelve a avisar.

requirements.txt
discord.py
aiohttp
python-dotenv
.env
DISCORD_TOKEN=pega-aquí-el-token-del-bot
CANAL_AVISOS=123456789012345678
MENCION=<@&123456789012345678>
TWITCH_CLIENT_ID=tu-client-id
TWITCH_CLIENT_SECRET=tu-client-secret
TWITCH_USUARIOS=tu_canal,el_de_tu_amigo
YOUTUBE_CANALES=UCxxxxxxxxxxxxxxxxxxxxxx
app.py
import json
import os
import xml.etree.ElementTree as ET

import aiohttp
import discord
from discord.ext import tasks
from dotenv import load_dotenv

load_dotenv()  # lee el .env

CANAL_AVISOS = int(os.environ["CANAL_AVISOS"])
TWITCH_ID = os.environ.get("TWITCH_CLIENT_ID", "")
TWITCH_SECRETO = os.environ.get("TWITCH_CLIENT_SECRET", "")
TWITCH = [u.strip().lower() for u in os.environ.get("TWITCH_USUARIOS", "").split(",") if u.strip()]
YOUTUBE = [c.strip() for c in os.environ.get("YOUTUBE_CANALES", "").split(",") if c.strip()]
MENCION = os.environ.get("MENCION", "")  # <@&ID_DEL_ROL>, @everyone o nada
FICHERO = "avisados.json"  # lo ya avisado, para no repetir al reiniciar
NS = {"a": "http://www.w3.org/2005/Atom", "yt": "http://www.youtube.com/xml/schemas/2015"}

client = discord.Client(intents=discord.Intents.default())
token_twitch = None

try:
    with open(FICHERO) as f:
        avisados = json.load(f)
except FileNotFoundError:
    avisados = {"twitch": {}, "youtube": {}}


async def pedir_token(sesion):
    global token_twitch
    datos = {"client_id": TWITCH_ID, "client_secret": TWITCH_SECRETO, "grant_type": "client_credentials"}
    async with sesion.post("https://id.twitch.tv/oauth2/token", params=datos) as r:
        r.raise_for_status()
        token_twitch = (await r.json())["access_token"]


async def directos(sesion):
    """Los canales de TWITCH_USUARIOS que están en directo ahora mismo."""
    if not TWITCH:
        return []
    if token_twitch is None:
        await pedir_token(sesion)
    for _ in range(2):
        cabeceras = {"Client-Id": TWITCH_ID, "Authorization": f"Bearer {token_twitch}"}
        consulta = [("user_login", u) for u in TWITCH]
        async with sesion.get("https://api.twitch.tv/helix/streams", params=consulta, headers=cabeceras) as r:
            if r.status == 401:  # el token caduca: se pide otro y se repite
                await pedir_token(sesion)
                continue
            r.raise_for_status()
            return (await r.json())["data"]
    return []


async def videos(sesion, canal):
    """Los últimos vídeos de un canal de YouTube, del más nuevo al más viejo."""
    url = "https://www.youtube.com/feeds/videos.xml"
    async with sesion.get(url, params={"channel_id": canal}) as r:
        r.raise_for_status()
        raiz = ET.fromstring(await r.text())
    return [
        {
            "id": e.find("yt:videoId", NS).text,
            "titulo": e.find("a:title", NS).text,
            "autor": e.find("a:author/a:name", NS).text,
            "enlace": e.find("a:link", NS).get("href"),
        }
        for e in raiz.findall("a:entry", NS)
    ]


@tasks.loop(minutes=2)
async def vigilar():
    canal = client.get_channel(CANAL_AVISOS) or await client.fetch_channel(CANAL_AVISOS)
    async with aiohttp.ClientSession() as sesion:
        try:
            for d in await directos(sesion):
                if avisados["twitch"].get(d["user_login"]) == d["id"]:
                    continue  # de este directo ya se avisó
                avisados["twitch"][d["user_login"]] = d["id"]
                tarjeta = discord.Embed(
                    title=d["title"] or "En directo",
                    url=f"https://www.twitch.tv/{d['user_login']}",
                    description=f"Jugando a {d['game_name']}" if d["game_name"] else None,
                    color=0x9146FF,
                )
                tarjeta.set_image(url=d["thumbnail_url"].replace("{width}", "1280").replace("{height}", "720"))
                await canal.send(f"{MENCION} 🔴 **{d['user_name']}** está en directo".strip(), embed=tarjeta)
        except Exception as error:  # Twitch no responde: se prueba en la siguiente vuelta
            print("Twitch:", error)

        for c in YOUTUBE:
            try:
                lista = await videos(sesion, c)
            except Exception as error:  # el feed a veces falla: siguiente vuelta
                print("YouTube:", c, error)
                continue
            vistos = avisados["youtube"].get(c)
            avisados["youtube"][c] = [v["id"] for v in lista]
            if vistos is None:
                continue  # la primera vez solo apunta los que ya había
            for v in reversed(lista):
                if v["id"] not in vistos:
                    await canal.send(f"{MENCION} 📺 **{v['autor']}** ha subido «{v['titulo']}»\n{v['enlace']}".strip())

    with open(FICHERO, "w") as f:
        json.dump(avisados, f)


@client.event
async def on_ready():
    print(f"Conectado como {client.user}")
    if not vigilar.is_running():
        vigilar.start()


client.run(os.environ["DISCORD_TOKEN"])

3. Pruébalo

Con Python 3.10 o más nuevo: «pip install -r requirements.txt» y «python app.py». Cuando diga «Conectado como…», sube un vídeo de prueba (público: el feed no lleva los ocultos ni los privados) o empieza un directo, y en dos o tres minutos sale el aviso. Si no sale, mira lo que escribe la consola: «Twitch: 401» o «400» casi siempre es el Client ID o el secreto mal copiados; «YouTube: 404», un ID de canal mal puesto (o un fallo pasajero del feed, que se arregla solo en la siguiente vuelta).

¿Quieres cambiar el texto del aviso o la tarjeta? Están en las dos líneas de canal.send: el color, el título y la imagen se cambian en discord.Embed.

4. Déjalo encendido

Un bot de avisos solo sirve si está encendido cuando empiezas el directo: en tu ordenador no avisaría de nada en cuanto lo apagas. Necesita estar alojado en un servidor.

En NapNodes: crea el bot, pásalo a Python en «Configuración» → «Lenguaje», sube los tres ficheros en «Archivos» y pulsa «Encender». Las librerías de requirements.txt se instalan solas al arrancar y app.py es el fichero de inicio que viene puesto. avisados.json se queda en «Archivos» entre reinicios. Si prefieres traer el código de GitHub, no subas el .env al repositorio (súbelo aparte en «Archivos») y pon avisados.json en el .gitignore. Todo el proceso, en la guía de hosting para tu bot.

Gasta muy poco: el bot gratis de NapNodes, de 256 MB, le sobra. Eso sí, el gratis hay que renovarlo con un clic cada 7 días (te avisamos por correo el día antes); si se te pasa, se apaga y deja de avisar hasta que lo enciendas. Para un bot que no puede fallar el día del directo, el plan Mini (512 MB) está encendido siempre sin renovar nada (precios de los bots).

Preguntas frecuentes

¿Cómo hago que mi Discord avise cuando estoy en directo en Twitch?

Con un bot que pregunte a la API de Twitch cada pocos minutos si estás en directo y, cuando empiezas, mande un mensaje al canal que elijas. Necesitas el token del bot de Discord y una aplicación de Twitch (Client ID y Client Secret, gratis en dev.twitch.tv/console).

¿Hace falta una clave de la API de YouTube para avisar de vídeos nuevos?

No. Cada canal tiene un feed público (youtube.com/feeds/videos.xml?channel_id=…) con sus últimos 15 vídeos, y el bot lo lee sin clave ni cuota.

¿Cada cuánto avisa el bot?

Mira cada dos minutos (se cambia en @tasks.loop). Bajarlo a menos de un minuto no aporta mucho y hace más consultas.

¿Por qué no menciona a @everyone?

El bot necesita el permiso «Mencionar @everyone, @here y todos los roles» en ese canal. Sin él, el mensaje sale, pero la mención no avisa a nadie.

¿Dónde dejo el bot para que avise siempre?

En un hosting de bots. En NapNodes el bot gratis de 256 MB le sobra (se renueva con un clic cada semana); el plan Mini está encendido siempre sin renovar (opciones gratis, con sus pegas).

¿Te ha servido esta guía?

¿Lo montamos?

Hosting de bots de Discord desde 1,50 € al mes, sin sobreventa: tu RAM es solo tuya. Y si quieres empezar sin pagar, tienes un bot de Discord gratis, sin tarjeta.

Sigue leyendo

Todas las guías de Bots de Discord, ordenadas →