Referencia

Plantillas de inicio de bots en Node.js y Python (Telegram, Discord, Slack, WhatsApp)

Cada plantilla hace una cosa bien: conectar, gestionar un comando o mensaje, responder y fallar de forma visible. Los secretos vienen de variables de entorno. Los comentarios del código están en inglés, como es habitual.

Bot de Telegram en Node.js (grammY, long polling)

Comandos, un manejador de eco y registro de errores. Ejecuta con BOT_TOKEN=... node bot.mjs.

Instalar: npm i grammy

bot.mjsjavascript
import { Bot } from "grammy";

const bot = new Bot(process.env.BOT_TOKEN);

bot.command("start", (ctx) =>
  ctx.reply("Hello! Send me any text and I will echo it. /help lists commands."));
bot.command("help", (ctx) => ctx.reply("/start - greeting\n/help - this message"));

bot.on("message:text", (ctx) => ctx.reply(ctx.message.text));

bot.catch((err) => console.error("Handler error:", err.error));

// Long polling: no public URL needed. Stops cleanly on Ctrl+C.
bot.start({ onStart: (me) => console.log(`Running as @${me.username}`) });
process.once("SIGINT", () => bot.stop());
process.once("SIGTERM", () => bot.stop());

Bot de Telegram en Python (python-telegram-bot v21+)

Manejadores asíncronos para /start y mensajes de texto con long polling.

Instalar: pip install python-telegram-bot

bot.pypython
import logging
import os

from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters

logging.basicConfig(level=logging.INFO)


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text("Hello! Send me any text and I will echo it.")


async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text(update.message.text)


def main() -> None:
    app = Application.builder().token(os.environ["BOT_TOKEN"]).build()
    app.add_handler(CommandHandler("start", start))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
    app.run_polling(allowed_updates=Update.ALL_TYPES)


if __name__ == "__main__":
    main()

Bot de Telegram con webhook en Cloudflare Workers

Verifica la cabecera secret_token, responde rápido y llama a sendMessage. Configura el webhook con setWebhook y el mismo secreto.

Instalar: npm create cloudflare@latest

worker.jsjavascript
export default {
  async fetch(request, env, ctx) {
    if (request.method !== "POST") return new Response("ok");
    // Telegram sends the secret you passed to setWebhook in this header.
    if (request.headers.get("X-Telegram-Bot-Api-Secret-Token") !== env.WEBHOOK_SECRET) {
      return new Response("forbidden", { status: 403 });
    }
    const update = await request.json();
    const msg = update.message;
    if (msg?.text) {
      // Reply after returning 200 so Telegram does not retry.
      ctx.waitUntil(send(env, msg.chat.id, msg.text === "/start" ? "Hello from a Worker!" : msg.text));
    }
    return new Response("ok");
  },
};

async function send(env, chatId, text) {
  const res = await fetch(`https://api.telegram.org/bot${env.BOT_TOKEN}/sendMessage`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ chat_id: chatId, text }),
  });
  if (!res.ok) console.error("sendMessage failed", res.status, await res.text());
}

Bot de Discord con comandos slash en Node.js (discord.js v14)

Registra un comando de servidor al arrancar y responde de forma efímera. Necesita DISCORD_TOKEN, CLIENT_ID y GUILD_ID.

Instalar: npm i discord.js

bot.mjsjavascript
import { Client, Events, GatewayIntentBits, MessageFlags, REST, Routes, SlashCommandBuilder } from "discord.js";

const { DISCORD_TOKEN, CLIENT_ID, GUILD_ID } = process.env;

const commands = [
  new SlashCommandBuilder().setName("ping").setDescription("Check that the bot is alive").toJSON(),
];

// Guild commands update instantly; use Routes.applicationCommands for global ones.
await new REST().setToken(DISCORD_TOKEN).put(Routes.applicationGuildCommands(CLIENT_ID, GUILD_ID), { body: commands });

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

client.once(Events.ClientReady, (c) => console.log(`Ready as ${c.user.tag}`));

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName === "ping") {
    await interaction.reply({ content: `Pong (${client.ws.ping} ms)`, flags: MessageFlags.Ephemeral });
  }
});

client.login(DISCORD_TOKEN);

Bot de Discord con comandos slash en Python (discord.py 2.x)

Usa un árbol app_commands sincronizado con un servidor para iterar rápido.

Instalar: pip install discord.py

bot.pypython
import os

import discord
from discord import app_commands

intents = discord.Intents.default()
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)
GUILD = discord.Object(id=int(os.environ["GUILD_ID"]))


@tree.command(name="ping", description="Check that the bot is alive", guild=GUILD)
async def ping(interaction: discord.Interaction) -> None:
    await interaction.response.send_message(f"Pong ({round(client.latency * 1000)} ms)", ephemeral=True)


@client.event
async def on_ready() -> None:
    await tree.sync(guild=GUILD)
    print(f"Ready as {client.user}")


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

App de Slack en Node.js (Bolt, Socket Mode)

Socket Mode no necesita URL pública. Requiere un token de bot (xoxb-) y un token de app (xapp-) con connections:write.

Instalar: npm i @slack/bolt

app.mjsjavascript
import bolt from "@slack/bolt";

const { App } = bolt;

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,
  socketMode: true,
});

app.message("hello", async ({ message, say }) => {
  await say(`Hi <@${message.user}>!`);
});

app.command("/status", async ({ ack, respond }) => {
  await ack(); // acknowledge within 3 seconds
  await respond("All systems normal.");
});

await app.start();
console.log("Bolt app running in Socket Mode");

App de Slack en Python (Bolt, Socket Mode)

Mismo comportamiento que la versión de Node.js.

Instalar: pip install slack_bolt

app.pypython
import os

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ["SLACK_BOT_TOKEN"])


@app.message("hello")
def hello(message, say):
    say(f"Hi <@{message['user']}>!")


@app.command("/status")
def status(ack, respond):
    ack()  # acknowledge within 3 seconds
    respond("All systems normal.")


if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

Webhook de WhatsApp Cloud API en Node.js (sin dependencias)

Gestiona el handshake de verificación, comprueba X-Hub-Signature-256 con el cuerpo en bruto y responde a mensajes de texto.

Instalar: Node.js 20+

server.mjsjavascript
import crypto from "node:crypto";
import http from "node:http";

const { VERIFY_TOKEN, APP_SECRET, WHATSAPP_TOKEN, PHONE_NUMBER_ID, PORT = 3000 } = process.env;
const GRAPH = "https://graph.facebook.com/v21.0"; // use the current Graph API version

http.createServer(async (req, res) => {
  const url = new URL(req.url, "http://localhost");
  if (url.pathname !== "/webhook") return res.writeHead(404).end();

  if (req.method === "GET") {
    const ok = url.searchParams.get("hub.mode") === "subscribe" && url.searchParams.get("hub.verify_token") === VERIFY_TOKEN;
    return res.writeHead(ok ? 200 : 403).end(ok ? url.searchParams.get("hub.challenge") : "");
  }

  const chunks = [];
  for await (const c of req) chunks.push(c);
  const raw = Buffer.concat(chunks);
  const expected = "sha256=" + crypto.createHmac("sha256", APP_SECRET).update(raw).digest("hex");
  const got = String(req.headers["x-hub-signature-256"] || "");
  if (got.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
    return res.writeHead(401).end();
  }
  res.writeHead(200).end(); // acknowledge first

  const body = JSON.parse(raw.toString("utf8"));
  for (const entry of body.entry ?? []) {
    for (const change of entry.changes ?? []) {
      for (const msg of change.value?.messages ?? []) {
        if (msg.type !== "text") continue;
        await fetch(`${GRAPH}/${PHONE_NUMBER_ID}/messages`, {
          method: "POST",
          headers: { Authorization: `Bearer ${WHATSAPP_TOKEN}`, "Content-Type": "application/json" },
          body: JSON.stringify({ messaging_product: "whatsapp", to: msg.from, type: "text", text: { body: `You said: ${msg.text.body}` } }),
        });
      }
    }
  }
}).listen(PORT, () => console.log(`Listening on :${PORT}`));

Webhook de WhatsApp Cloud API en Python (FastAPI)

Ejecuta con uvicorn main:app. Las respuestas se envían en una tarea en segundo plano tras el 200.

Instalar: pip install fastapi uvicorn httpx

main.pypython
import hashlib
import hmac
import os

import httpx
from fastapi import BackgroundTasks, FastAPI, Request, Response

VERIFY_TOKEN = os.environ["VERIFY_TOKEN"]
APP_SECRET = os.environ["APP_SECRET"].encode()
WHATSAPP_TOKEN = os.environ["WHATSAPP_TOKEN"]
PHONE_NUMBER_ID = os.environ["PHONE_NUMBER_ID"]
GRAPH = "https://graph.facebook.com/v21.0"  # use the current Graph API version

app = FastAPI()


@app.get("/webhook")
def verify(request: Request) -> Response:
    p = request.query_params
    if p.get("hub.mode") == "subscribe" and p.get("hub.verify_token") == VERIFY_TOKEN:
        return Response(p.get("hub.challenge", ""))
    return Response(status_code=403)


async def reply(to: str, text: str) -> None:
    async with httpx.AsyncClient(timeout=10) as client:
        await client.post(
            f"{GRAPH}/{PHONE_NUMBER_ID}/messages",
            headers={"Authorization": f"Bearer {WHATSAPP_TOKEN}"},
            json={"messaging_product": "whatsapp", "to": to, "type": "text", "text": {"body": f"You said: {text}"}},
        )


@app.post("/webhook")
async def receive(request: Request, tasks: BackgroundTasks) -> Response:
    raw = await request.body()
    expected = "sha256=" + hmac.new(APP_SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("x-hub-signature-256", "")):
        return Response(status_code=401)
    body = await request.json()
    for entry in body.get("entry", []):
        for change in entry.get("changes", []):
            for msg in change.get("value", {}).get("messages", []):
                if msg.get("type") == "text":
                    tasks.add_task(reply, msg["from"], msg["text"]["body"])
    return Response(status_code=200)

Ejemplo de .env para las plantillas

Cópialo a .env, rellena los valores y nunca subas el archivo real. Revísalo con el validador de .env.

.env.examplebash
# Telegram
BOT_TOKEN=
WEBHOOK_SECRET=

# Discord
DISCORD_TOKEN=
CLIENT_ID=
GUILD_ID=

# Slack
SLACK_BOT_TOKEN=
SLACK_APP_TOKEN=

# WhatsApp Cloud API
VERIFY_TOKEN=
APP_SECRET=
WHATSAPP_TOKEN=
PHONE_NUMBER_ID=

Siguientes pasos