Hoy vamos a ver cómo agregar un globo o bocadillo a cualquier imagen con un bot de Telegram usando Node y JavaScript para dicha tarea.
Una cosa será el bot de Telegram y otra el procesamiento de la imagen. Podemos usar ambas cosas por separado y mejorar cada una de ellas o combinarlas.
Técnicamente hablando vamos a escuchar si el usuario envía una imagen en Telegram (ya sea en grupos o en privado) y una vez que la envíe veremos el texto que la acompaña.
En caso de que el texto coincida con la orden vamos a combinar las 2 imágenes, redimensionando el globo o bocadillo según sea el caso y después vamos a responder con esa imagen ya modificada.
Nota: esto fue hecho por diversión para hacer memes y comentar rápidamente en Facebook o similares, aunque se pueden sacar varias cosas de aquí.
Requisitos
Vamos a usar Node y los paquetes dotenv (variables del entorno), node-telegram-bot-api
para el bot de Telegram y también sharp para la modificación de imágenes.
También vas a necesitar tu token de bot para Telegram en caso de que quieras probar el proyecto.
Una vez que lo tengas necesitas crear el archivo .env
según el archivo .env.example
y colocar tu token ahí.
En cuanto a los paquetes ya van en el package.json
solo tienes que hacer un npm install
.
Escuchando petición de usuario
Vamos a usar bot.on
para escuchar cuando el usuario envíe una imagen. Si lo hace vamos a ver el caption
que la acompaña para ver si nos está pidiendo que le agreguemos un globo de texto:
bot.on('photo', async (msg) => {
const caption = msg.caption;
if (!caption || !msg.photo || msg.photo.length <= 0) {
return;
}
const chatId = msg.chat.id;
const opciones = [
"hazle un globo de texto",
"hacer un globo de texto",
"haga un globo de texto",
"poner un globo de texto",
"ponga un globo de texto",
];
for (const opcion of opciones) {
if (caption.toLowerCase().includes(opcion)) {
// Aquí ponemos el globo de texto
}
}
});
Si el caption contiene (ya sea al inicio, centro o final) alguna de las opciones de la línea 7 entonces vamos a agregar el bocadillo a la imagen.
Agregar globo de texto a imagen con Node y sharp
A continuación veamos la función que permitirá agregar el speech bubble a la imagen usando JavaScript del lado del servidor y un Telegram bot.
const agregarGloboDeTextoYDevolverBuffer = async (nombreEntrada, porcentaje) => {
const entrada = sharp(nombreEntrada);
const informacionEntrada = await entrada.metadata();
const ALTURA_GLOBO = parseInt((porcentaje * informacionEntrada.height) / 100);
const globo = sharp(NOMBRE_IMAGEN_GLOBO)
.resize({ width: informacionEntrada.width, height: ALTURA_GLOBO, fit: "fill" })
return entrada
.extend({ top: ALTURA_GLOBO, background: { r: 255, g: 255, b: 255, } })
.composite([{ input: await globo.toBuffer(), left: 0, top: 0 }])
.toBuffer();
}
En la línea 5 cargamos el globo de texto (o sea, la imagen que vamos a poner encima de la original) y lo redimensionamos para que sea del mismo largo que la imagen.
Luego extendemos la imagen original rellenándola de blanco y con composite
combinamos ambas imágenes colocando el globo en la esquina superior izquierda, es decir, en left y top 0.
Regresamos un búfer de la imagen ya que la librería así lo permite. De este modo no necesitamos guardarla de manera temporal en el almacenamiento o cosas similares.
Enviando imagen de vuelta al chat
Ahora ya tenemos la imagen modificada como un búfer y ya sabemos que el usuario solicitó la modificación, queda enviársela usando el chatId
con la función bot.sendPhoto
:
const mejorFoto = msg.photo[msg.photo.length - 1]; // al momento de escribir esto, la foto con mejor calidad estaba en la última posición
const nombreFotoDescargada = await bot.downloadFile(mejorFoto.file_id, "./");
const fotoConGloboDeTexto = await agregarGloboDeTextoYDevolverBuffer(nombreFotoDescargada, 15);
fs.unlink(nombreFotoDescargada, () => {
bot.sendPhoto(chatId, fotoConGloboDeTexto);
});
Por cierto, en este caso estoy descargando la imagen con bot.downloadFile
ya que no encontré la manera de obtenerla como búfer, así que en este caso sí estoy creando una imagen temporal pero la elimino después con fs.unlink
.
Poniendo todo junto
El código completo queda como se ve a continuación, pero si lo quieres junto con el package.json
y actualizado te recomiendo que vayas a GitHub ya que si hago actualizaciones las haré ahí:
const sharp = require('sharp')
const fs = require("fs");
require("dotenv").config();
const NOMBRE_IMAGEN_GLOBO = "globo.png";
// Función de recuerdo. No sirve para nada pero es la primera que escribí para probar esta cosa
const agregarGloboDeTexto = async (porcentaje) => {
const entrada = sharp("./esther.jpeg");
const informacionEntrada = await entrada.metadata();
const ALTURA_GLOBO = parseInt((porcentaje * informacionEntrada.height) / 100);
const globo = sharp("./globo.png")
.resize({ width: informacionEntrada.width, height: ALTURA_GLOBO, fit: "fill" })
entrada
.extend({ top: ALTURA_GLOBO, background: { r: 255, g: 255, b: 255, } })
.composite([{ input: await globo.toBuffer(), left: 0, top: 0 }])
.toFile('./kangta.new.jpg', function (err) {
if (err) console.log(err);
})
}
const agregarGloboDeTextoYDevolverBuffer = async (nombreEntrada, porcentaje) => {
const entrada = sharp(nombreEntrada);
const informacionEntrada = await entrada.metadata();
const ALTURA_GLOBO = parseInt((porcentaje * informacionEntrada.height) / 100);
const globo = sharp(NOMBRE_IMAGEN_GLOBO)
.resize({ width: informacionEntrada.width, height: ALTURA_GLOBO, fit: "fill" })
return entrada
.extend({ top: ALTURA_GLOBO, background: { r: 255, g: 255, b: 255, } })
.composite([{ input: await globo.toBuffer(), left: 0, top: 0 }])
.toBuffer();
}
const TelegramBot = require('node-telegram-bot-api');
const token = process.env.TELEGRAM_BOT_TOKEN;
const bot = new TelegramBot(token, { polling: true });
bot.on('photo', async (msg) => {
const caption = msg.caption;
if (!caption || !msg.photo || msg.photo.length <= 0) {
return;
}
const chatId = msg.chat.id;
const opciones = [
"hazle un globo de texto",
"hacer un globo de texto",
"haga un globo de texto",
"poner un globo de texto",
"ponga un globo de texto",
];
for (const opcion of opciones) {
if (caption.toLowerCase().includes(opcion)) {
const mejorFoto = msg.photo[msg.photo.length - 1]; // al momento de escribir esto, la foto con mejor calidad estaba en la última posición
const nombreFotoDescargada = await bot.downloadFile(mejorFoto.file_id, "./");
const fotoConGloboDeTexto = await agregarGloboDeTextoYDevolverBuffer(nombreFotoDescargada, 15);
fs.unlink(nombreFotoDescargada, () => {
bot.sendPhoto(chatId, fotoConGloboDeTexto);
});
}
}
});
Recuerda que se ejecuta con node index.js
y puedes correrlo incluso localmente en Windows aunque también se puede usar en un servidor.
Aquí te dejo con más bots de Telegram que he creado.