Mensajeria y bots
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.
- 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 consetWebhook(mejor en producción).
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
- 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). - Descarga el fichero.
getFiledevuelvefile_pathy el fichero se baja desdehttps://api.telegram.org/file/bot<token>/<file_path>. - Pide los formatos. El fichero se sube a
POST /api/v1/images/process/upload(multipart, máximo 5 MB) con la lista de destinos. - Responde con sendPhoto. Cada salida del array
outputsse 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
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.OPCIONESfuerza la salida enjpg; el valor por defecto de la API eswebpy las fotos de Telegram se envían cómodamente enjpgopng.sendPhotoacepta una URL pública o unfile_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.
/todocon 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_idy envíaIdempotency-Keycon elupdate_id: si repite el fichero o la red reintenta, devuelve las URLs ya generadas sin gastar otro uso. - Consulta
GET /api/v1/creditsantes 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
- Terminal: Procesa imágenes con la API desde la terminal (curl)
- Python: Procesa imágenes con la API de SocialCutter desde Python
- Agentes: Usa SocialCutter desde tu LLM o editor con MCP
- Documentación: https://docs.socialcutter.theboomer.dev
Preguntas frecuentes
¿El bot publica en Instagram, LinkedIn o X?
No. SocialCutter genera las imágenes adaptadas y devuelve una URL pública por formato; el bot las reenvía al chat. Publicar en cada red es tarea de la herramienta de publicación o de tu propia integración con la API de cada plataforma.
¿Cómo recibe el bot la foto del usuario?
Con getUpdates o un webhook. El mensaje trae un array photo con varios tamaños; se usa el último (el de mayor resolución), se pide su file_path con getFile y se descarga con el token del bot.
¿Por qué no paso directamente la URL del fichero de Telegram a SocialCutter?
Porque esa URL incluye el token del bot (https://api.telegram.org/file/bot<token>/<file_path>) y lo estarías entregando a un tercero. Además caduca: Telegram garantiza que el enlace vive al menos 1 hora. Descarga el fichero y súbelo por multipart.
¿Qué formatos puedo pedir?
Instagram post 1080x1080, story 1080x1920 y landscape 1080x566; Facebook post 1200x630, story 1080x1920 y cover 820x312; X post 1200x675 y header 1500x500; LinkedIn post 1200x627 y cover 1128x191; YouTube thumbnail 1280x720 y banner 2560x1440; TikTok cover 1080x1920.
¿Cuánto cuesta cada foto que manda un usuario?
1 uso por destino, es decir por cada combinación de plataforma y formato. Si el bot pide Instagram post y LinkedIn post para la misma foto, son 2 usos; si el usuario elige una sola orden, 1 uso.