# Bot de Telegram que adapta tus fotos con SocialCutter
> Bot de Telegram en Python que recibe una foto, la procesa con la API de SocialCutter y la devuelve en el formato exacto de cada red con sendPhoto.
- URL: https://socialcutter.theboomer.dev/guias/telegram/
- Idioma: es
- Familia: mensajeria
- Actualizado: 2026-09-24
- Palabras clave: Telegram, bot, sendPhoto, Python, requests, SocialCutter, redes sociales
## Qué hace este bot

Un bot que recibe una foto en Telegram y contesta con esa misma foto ya adaptada a las medidas de cada red social. El usuario manda la imagen con una orden en el pie (`/ig`, `/story`, `/li`, `/todo`) y recibe una respuesta por formato, cada una con la plataforma, las medidas y el enlace de descarga.

Conviene decir esto claro: **SocialCutter no publica en redes sociales**. Genera los ficheros con el encuadre centrado y devuelve una URL pública por salida. El envío al chat lo hace el bot; la publicación en Instagram, LinkedIn, X o TikTok la hace la herramienta de publicación o tu integración con la API de cada plataforma.

## Requisitos

- Token del bot, creado con **@BotFather**.
- Python 3 y la librería `requests` (`pip install requests`).
- Una clave `sc_` de SocialCutter, creada en https://dash.socialcutter.theboomer.dev en **Perfil → API keys**.
- Forma de recibir mensajes: sondeo con `getUpdates` (sencillo) o webhook HTTPS con `setWebhook` (mejor en producción).

```bash
pip install requests
export TELEGRAM_BOT_TOKEN="123456:ABC-tu-token"
export SOCIALCUTTER_API_KEY="sc_tu_clave"
```

La referencia del Bot API está en https://core.telegram.org/bots/api y la de SocialCutter en https://docs.socialcutter.theboomer.dev.

## El flujo, paso a paso

1. **Llega la foto y la orden.** El usuario manda una foto con el pie `/ig linkedin`. Si no hay orden, el bot aplica un destino por defecto (uno solo, para no gastar de más).
2. **Descarga el fichero.** `getFile` devuelve `file_path` y el fichero se baja desde `https://api.telegram.org/file/bot<token>/<file_path>`.
3. **Pide los formatos.** El fichero se sube a `POST /api/v1/images/process/upload` (multipart, máximo 5 MB) con la lista de destinos.
4. **Responde con sendPhoto.** Cada salida del array `outputs` se envía con su URL pública. Telegram descarga la imagen y la muestra en el chat.

## Por qué se descarga el fichero en vez de pasar la URL de Telegram

La tentación es dar a SocialCutter la URL del fichero de Telegram como `source`. No lo hagas: esa URL **contiene el token del bot**, así que entregarla a un tercero es filtrar la credencial que controla tu bot. Además Telegram solo garantiza que el enlace viva **1 hora** y limita la descarga directa a **20 MB** (https://core.telegram.org/bots/api#file). Descargando y subiendo por multipart, la credencial no sale de tu servidor y el límite es el de SocialCutter: 5 MB.

## Código completo

```python
import json
import os
import time

import requests

BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
SC_KEY = os.environ["SOCIALCUTTER_API_KEY"]
TG = f"https://api.telegram.org/bot{BOT_TOKEN}"
SC_URL = "https://api.socialcutter.theboomer.dev"

# Orden del pie del mensaje -> destinos de SocialCutter. 1 uso por destino.
ORDENES = {
    "/ig": [{"platform": "instagram", "format": "post"}],
    "/story": [{"platform": "instagram", "format": "story"}],
    "/li": [{"platform": "linkedin", "format": "post"}],
    "/todo": [
        {"platform": "instagram", "format": "post"},
        {"platform": "linkedin", "format": "post"},
        {"platform": "twitter", "format": "post"},
    ],
}
POR_DEFECTO = ORDENES["/ig"]          # una sola salida si el usuario no pide nada
OPCIONES = {"format": "jpg", "quality": 88}


def tg(metodo, **payload):
    r = requests.post(f"{TG}/{metodo}", json=payload, timeout=60)
    r.raise_for_status()
    return r.json()


def descargar(file_id):
    info = tg("getFile", file_id=file_id)["result"]
    r = requests.get(f"https://api.telegram.org/file/bot{BOT_TOKEN}/{info['file_path']}", timeout=60)
    r.raise_for_status()
    return r.content


def procesar(imagen, destinos):
    r = requests.post(
        f"{SC_URL}/api/v1/images/process/upload",
        headers={"X-API-Key": SC_KEY},
        files={"file": ("foto.jpg", imagen, "image/jpeg")},
        data={"destinations": json.dumps(destinos), "options": json.dumps(OPCIONES)},
        timeout=120,
    )
    r.raise_for_status()
    return r.json()


def atender(message):
    chat_id = message["chat"]["id"]
    if "photo" not in message:
        tg("sendMessage", chat_id=chat_id, text="Mándame una foto con la orden: /ig, /story, /li o /todo.")
        return

    orden = (message.get("caption") or "").split()
    destinos = ORDENES.get(orden[0] if orden else "", POR_DEFECTO)

    foto = message["photo"][-1]                       # el ultimo es el de mayor resolucion
    if foto.get("file_size", 0) > 5 * 1024 * 1024:
        tg("sendMessage", chat_id=chat_id, text="La foto pasa de 5 MB: el límite de SocialCutter.")
        return

    aviso = tg("sendMessage", chat_id=chat_id, text=f"Generando {len(destinos)} formato(s)...")["result"]
    try:
        datos = procesar(descargar(foto["file_id"]), destinos)
    except requests.HTTPError as e:
        codigo = e.response.status_code
        mensaje = {401: "La clave de API no es válida.", 413: "Imagen de más de 5 MB.",
                   429: "Cuota agotada."}.get(codigo, f"Error {codigo} al procesar.")
        tg("editMessageText", chat_id=chat_id, message_id=aviso["message_id"], text=mensaje)
        return
    except requests.RequestException:
        tg("editMessageText", chat_id=chat_id, message_id=aviso["message_id"],
           text="No pude contactar con el servicio. Prueba otra vez.")
        return

    tg("deleteMessage", chat_id=chat_id, message_id=aviso["message_id"])
    for out in datos["outputs"]:
        tg("sendPhoto", chat_id=chat_id, photo=out["url"],
           caption=f"{out['platform']} {out['format']} · {out['width']}x{out['height']}")


def main():
    offset = None
    while True:
        params = {"timeout": 50, "offset": offset} if offset else {"timeout": 50}
        for update in requests.get(f"{TG}/getUpdates", params=params, timeout=60).json()["result"]:
            offset = update["update_id"] + 1
            if "message" in update:
                atender(update["message"])
        time.sleep(0.5)


if __name__ == "__main__":
    main()
```

Detalles que importan:

- `message["photo"]` es un array de tamaños: usa el último, el mayor.
- `OPCIONES` fuerza la salida en `jpg`; el valor por defecto de la API es `webp` y las fotos de Telegram se envían cómodamente en `jpg` o `png`.
- `sendPhoto` acepta una URL pública o un `file_id`; con URL, el máximo documentado es **5 MB** (https://core.telegram.org/bots/api#sendphoto). En producción, cambia el sondeo por un webhook (`setWebhook`).

## Cómo limitar el coste

Cada destino consume **1 uso**, así que el coste lo fija el bot, no el usuario:

- Una orden = una lista de destinos. `/todo` con tres destinos cuesta 3 usos por foto; deja que sea una decisión explícita y avisa en el mensaje.
- Destinos por defecto mínimos (uno), y órdenes combinadas solo cuando se pidan: `/ig /story`.
- Deduplica por `file_unique_id` y envía `Idempotency-Key` con el `update_id`: si repite el fichero o la red reintenta, devuelve las URLs ya generadas sin gastar otro uso.
- Consulta `GET /api/v1/credits` antes de lotes grandes y corta con un mensaje claro si queda poco saldo.

## Errores típicos

| Síntoma | Causa | Solución |
|---|---|---|
| `401` de SocialCutter | Clave ausente o revocada | Revisa `X-API-Key`; solo hay una clave activa por cuenta |
| `413` de SocialCutter | Fichero de más de 5 MB | Comprime antes de subir o pide al usuario una foto menor |
| `400` de Telegram al enviar la foto | El formato de salida no le sirve al cliente | Pide `options.format` = `jpg` o `png` |
| El bot no responde | El proceso murió o el webhook falla | Comprueba `getWebhookInfo` o los logs del sondeo |
| `429` de SocialCutter | Cuota diaria agotada | Para el bot y avisa; el cupo se renueva cada día |

## Privacidad

Las salidas se sirven como **URLs públicas y sin autenticación** (los endpoints de storage y de imagen son abiertos): quien tenga el enlace ve la imagen, así que trátalas como material público y no proceses documentos internos. La foto del usuario sale de Telegram y viaja a la API de SocialCutter. Dilo en el propio bot, sobre todo si es de uso interno.

## Coste

- **1 uso por destino** (plataforma y formato) por foto; los destinos repetidos no se cobran dos veces.
- Los procesamientos fallidos se devuelven.
- Todos los planes incluyen API y MCP: Free 3 usos/día, Basic 10, Pro 30, Agency 100.

## Siguientes pasos

- Automatización: [Automatiza el recorte de imágenes con n8n](/guias/n8n/)
- Terminal: [Procesa imágenes con la API desde la terminal (curl)](/guias/curl/)
- Python: [Procesa imágenes con la API de SocialCutter desde Python](/guias/python/)
- Agentes: [Usa SocialCutter desde tu LLM o editor con MCP](/guias/mcp/)
- Documentación: https://docs.socialcutter.theboomer.dev