Files
manybot/src/commands/figurinha.js

256 lines
7.1 KiB
JavaScript

import fs from "fs";
import path from "path";
import os from "os";
import { execFile } from "child_process";
import { promisify } from "util";
import pkg from "whatsapp-web.js";
import { createSticker } from "wa-sticker-formatter";
import { client } from "../client/whatsappClient.js";
import { botMsg } from "../utils/botMsg.js";
import { emptyFolder } from "../utils/file.js";
import { stickerSessions } from "./index.js";
const { MessageMedia } = pkg;
const execFileAsync = promisify(execFile);
const DOWNLOADS_DIR = path.resolve("downloads");
const FFMPEG = os.platform() === "win32"
? ".\\bin\\ffmpeg.exe"
: "./bin/ffmpeg";
const MAX_STICKER_SIZE = 900 * 1024;
const SESSION_TIMEOUT = 2 * 60 * 1000;
const MAX_MEDIA = 10;
// ───────────────── Helpers ─────────────────
function ensureDownloadsDir() {
if (!fs.existsSync(DOWNLOADS_DIR)) {
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
}
}
function cleanupFiles(...files) {
for (const f of files) {
if (f && fs.existsSync(f)) fs.unlinkSync(f);
}
}
// Converte vídeo/gif → GIF 512x512 com paleta preservada
async function convertVideoToGif(inputPath, outputPath, fps = 12) {
const clampedFps = Math.min(fps, 12);
const filter = [
`fps=${clampedFps},scale=512:512:flags=lanczos,split[s0][s1]`,
`[s0]palettegen=max_colors=256:reserve_transparent=1[p]`,
`[s1][p]paletteuse=dither=bayer`
].join(";");
await execFileAsync(FFMPEG, [
"-i", inputPath,
"-filter_complex", filter, // <-- era -vf, tem que ser -filter_complex pro split funcionar
"-loop", "0",
"-y",
outputPath
]);
}
// Força imagem estática para 512x512
async function resizeToSticker(inputPath, outputPath) {
await execFileAsync(FFMPEG, [
"-i", inputPath,
"-vf", "scale=512:512:flags=lanczos", // lanczos = melhor qualidade no resize
"-y",
outputPath
]);
}
async function createStickerWithFallback(stickerInputPath, isAnimated) {
const qualities = [80, 60, 40, 20];
for (const quality of qualities) {
const buffer = await createSticker(
fs.readFileSync(stickerInputPath),
{
pack: "Criada por ManyBot\n",
author: "\ngithub.com/synt-xerror/manybot",
type: isAnimated ? "FULL" : "STATIC",
categories: ["🤖"],
quality,
}
);
if (buffer.length <= MAX_STICKER_SIZE) {
return buffer;
}
}
throw new Error("Não foi possível reduzir o sticker para menos de 900 KB.");
}
// ───────────────── Sessão ─────────────────
export function iniciarSessao(chatId, author) {
if (stickerSessions.has(chatId)) return false;
const timeout = setTimeout(() => {
stickerSessions.delete(chatId);
client.sendMessage(chatId, botMsg("Sessão de figurinha expirou."));
}, SESSION_TIMEOUT);
stickerSessions.set(chatId, {
author,
medias: [],
timeout
});
return true;
}
// ───────────────── Coleta de mídia ─────────────────
export async function coletarMidia(msg) {
const chat = await msg.getChat();
const chatId = chat.id._serialized;
const session = stickerSessions.get(chatId);
if (!session) return;
const sender = msg.author || msg.from;
if (sender !== session.author) return;
if (!msg.hasMedia) return;
const media = await msg.downloadMedia();
if (!media) return;
const isGif =
media.mimetype === "image/gif" ||
(media.mimetype === "video/mp4" && msg._data?.isGif);
if (
!media.mimetype ||
(!media.mimetype.startsWith("image/") &&
!media.mimetype.startsWith("video/") &&
!isGif)
) {
return;
}
if (session.medias.length < MAX_MEDIA) {
session.medias.push(media);
}
}
// ───────────────── Criar stickers ─────────────────
export async function gerarSticker(msg, chatId) {
const sender = msg.author || msg.from;
const session = stickerSessions.get(chatId);
if (!session) {
return msg.reply(botMsg("Nenhuma sessão de figurinha ativa."));
}
if (session.author !== sender) {
return msg.reply(botMsg("Apenas quem iniciou a sessão pode criar as figurinhas."));
}
const medias = session.medias;
if (!medias.length) {
return msg.reply(botMsg("Nenhuma imagem recebida."));
}
clearTimeout(session.timeout);
console.log("midias:", medias.length);
await msg.reply(botMsg("Aguarde! Estou criando as suas figurinhas..."));
ensureDownloadsDir();
for (const media of medias) {
try {
const ext = media.mimetype.split("/")[1];
const isVideo = media.mimetype.startsWith("video/");
const isGif = media.mimetype === "image/gif";
const isAnimated = isVideo || isGif;
const id = Date.now() + "-" + Math.random().toString(36).slice(2);
const inputPath = path.join(DOWNLOADS_DIR, `${id}.${ext}`);
const gifPath = path.join(DOWNLOADS_DIR, `${id}.gif`);
const resizedPath = path.join(DOWNLOADS_DIR, `${id}-scaled.${ext}`);
fs.writeFileSync(inputPath, Buffer.from(media.data, "base64"));
// LOG 1 — arquivo de entrada
const inputSize = fs.statSync(inputPath).size;
console.log(`[1] mimetype: ${media.mimetype} | isAnimated: ${isAnimated} | inputPath: ${inputPath} | size: ${inputSize} bytes`);
let stickerInputPath = inputPath;
if (isAnimated) {
console.log("[2] Convertendo para GIF...");
await convertVideoToGif(inputPath, gifPath, isVideo ? 12 : 24);
// LOG 2 — gif gerado
if (fs.existsSync(gifPath)) {
console.log(`[2] GIF gerado: ${fs.statSync(gifPath).size} bytes`);
} else {
console.error("[2] ERRO: gifPath não foi criado pelo ffmpeg!");
}
stickerInputPath = gifPath;
} else {
console.log("[2] Redimensionando imagem estática...");
await resizeToSticker(inputPath, resizedPath);
if (fs.existsSync(resizedPath)) {
console.log(`[2] Resized gerado: ${fs.statSync(resizedPath).size} bytes`);
} else {
console.error("[2] ERRO: resizedPath não foi criado!");
}
stickerInputPath = resizedPath;
}
// LOG 3 — antes de criar o sticker
console.log(`[3] stickerInputPath: ${stickerInputPath} | exists: ${fs.existsSync(stickerInputPath)} | size: ${fs.existsSync(stickerInputPath) ? fs.statSync(stickerInputPath).size : "N/A"} bytes`);
const stickerBuffer = await createStickerWithFallback(stickerInputPath, isAnimated);
// LOG 4 — sticker gerado
console.log(`[4] Sticker buffer: ${stickerBuffer.length} bytes`);
const stickerMedia = new MessageMedia("image/webp", stickerBuffer.toString("base64"));
await client.sendMessage(chatId, stickerMedia, { sendMediaAsSticker: true });
cleanupFiles(inputPath, gifPath, resizedPath);
} catch (err) {
console.error("Erro ao gerar sticker:", err);
await msg.reply(botMsg("Erro ao gerar uma das figurinhas."));
}
}
await msg.reply(botMsg("Figurinhas geradas com sucesso!"));
stickerSessions.delete(chatId);
emptyFolder("downloads");
}