2 Commits
2.3.0 ... 2.3.1

Author SHA1 Message Date
synt-xerror
cb50d4b8f8 new messages and more security when making stickers 2026-03-15 17:31:06 -03:00
synt-xerror
f2e6c12af4 Bump version to 2.3.0 2026-03-15 17:31:04 -03:00
10 changed files with 225 additions and 173 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "whatsapp-bot",
"version": "2.2.1",
"version": "2.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "whatsapp-bot",
"version": "2.2.1",
"version": "2.3.0",
"dependencies": {
"node-addon-api": "^7",
"node-gyp": "^12.2.0",

View File

@@ -1,6 +1,6 @@
{
"name": "whatsapp-bot",
"version": "2.2.1",
"version": "2.3.0",
"type": "module",
"dependencies": {
"node-addon-api": "^7",

View File

@@ -51,7 +51,7 @@ async function convertVideoToGif(inputPath, outputPath, fps = 12) {
await execFileAsync(FFMPEG, [
"-i", inputPath,
"-filter_complex", filter, // <-- era -vf, tem que ser -filter_complex pro split funcionar
"-filter_complex", filter,
"-loop", "0",
"-y",
outputPath
@@ -63,7 +63,7 @@ async function resizeToSticker(inputPath, outputPath) {
await execFileAsync(FFMPEG, [
"-i", inputPath,
"-vf", "scale=512:512:flags=lanczos", // lanczos = melhor qualidade no resize
"-vf", "scale=512:512:flags=lanczos",
"-y",
outputPath
]);
@@ -98,33 +98,40 @@ async function createStickerWithFallback(stickerInputPath, isAnimated) {
// ───────────────── Sessão ─────────────────
export function iniciarSessao(chatId, author) {
export const help =
"📌 *Como criar figurinhas:*\n\n" +
"1⃣ Digite `!figurinha` para iniciar\n" +
"2⃣ Envie as imagens, GIFs ou vídeos que quer transformar\n" +
"3⃣ Digite `!figurinha criar` para gerar as figurinhas\n\n" +
"⏳ A sessão expira em 2 minutos se nenhuma mídia for enviada.";
export function iniciarSessao(chatId, author, msg) {
if (stickerSessions.has(chatId)) return false;
const timeout = setTimeout(() => {
const timeout = setTimeout(async () => {
stickerSessions.delete(chatId);
client.sendMessage(chatId, botMsg("Sessão de figurinha expirou."));
try {
await msg.reply(botMsg(
"⏰ *Sessão expirada!*\n\n" +
"Você demorou mais de 2 minutos para enviar as mídias.\n" +
"Digite `!figurinha` para começar de novo."
));
} catch (err) {
console.error("Erro ao notificar expiração:", err.message);
}
}, SESSION_TIMEOUT);
stickerSessions.set(chatId, {
author,
medias: [],
timeout
});
stickerSessions.set(chatId, { author, medias: [], timeout });
return true;
}
// ───────────────── Coleta de mídia ─────────────────
export async function coletarMidia(msg) {
;
// figurinha.js — coletarMidia
const chat = await msg.getChat();
const chatId = chat.id._serialized;
const chatId = chat.id._serialized; // ← volta pra isso
const session = stickerSessions.get(chatId);
if (!session) return;
@@ -158,33 +165,40 @@ export async function coletarMidia(msg) {
// ───────────────── Criar stickers ─────────────────
export async function gerarSticker(msg, chatId) {
console.log("[gerarSticker] chatId:", chatId);
const sender = msg.author || msg.from;
const session = stickerSessions.get(chatId);
if (!session) {
return msg.reply(botMsg("Nenhuma sessão de figurinha ativa."));
return msg.reply(botMsg(
"❌ *Nenhuma sessão ativa.*\n\n" + help
));
}
if (session.author !== sender) {
return msg.reply(botMsg("Apenas quem iniciou a sessão pode criar as figurinhas."));
return msg.reply(botMsg(
"🚫 Só quem digitou `!figurinha` pode usar `!figurinha criar`."
));
}
const medias = session.medias;
if (!medias.length) {
return msg.reply(botMsg("Nenhuma imagem recebida."));
return msg.reply(botMsg(
"📭 *Você ainda não enviou nenhuma mídia!*\n\n" + help
));
}
clearTimeout(session.timeout);
console.log("midias:", medias.length);
await msg.reply(botMsg("Aguarde! Estou criando as suas figurinhas..."));
await msg.reply(botMsg("⏳ Gerando suas figurinhas, aguarde um momento..."));
ensureDownloadsDir();
for (const media of medias) {
for (const media of medias) {
try {
const ext = media.mimetype.split("/")[1];
const isVideo = media.mimetype.startsWith("video/");
@@ -198,7 +212,6 @@ export async function gerarSticker(msg, chatId) {
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`);
@@ -208,7 +221,6 @@ export async function gerarSticker(msg, chatId) {
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 {
@@ -229,12 +241,10 @@ export async function gerarSticker(msg, chatId) {
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"));
@@ -244,11 +254,17 @@ export async function gerarSticker(msg, chatId) {
} catch (err) {
console.error("Erro ao gerar sticker:", err);
await msg.reply(botMsg("Erro ao gerar uma das figurinhas."));
await msg.reply(botMsg(
"⚠️ Não consegui criar uma das figurinhas.\n" +
"Tente reenviar essa mídia ou use outro formato (JPG, PNG, GIF, MP4)."
));
}
}
await msg.reply(botMsg("Figurinhas geradas com sucesso!"));
await msg.reply(botMsg(
"✅ *Figurinhas criadas com sucesso!*\n" +
"Salve as que quiser no seu WhatsApp. 😄"
));
stickerSessions.delete(chatId);
emptyFolder("downloads");

View File

@@ -1,8 +1,9 @@
import { enqueueDownload } from "../download/queue.js";
import { iniciarSessao, gerarSticker } from "./figurinha.js";
import { iniciarSessao, gerarSticker, help } from "./figurinha.js";
import { botMsg } from "../utils/botMsg.js";
import { iniciarJogo, pararJogo } from "../games/adivinhacao.js";
import { processarInfo } from "./info.js";
import client from "../client/whatsappClient.js";
export const stickerSessions = new Map();
@@ -34,88 +35,93 @@ export async function processarComando(msg, chat, chatId) {
switch (cmd) {
case "!many":
await chat.sendMessage(botMsg(
"Comandos:\n\n" +
"- `!ping`\n" +
"- `!video <link>`\n" +
"- `!audio <link>`\n" +
"- `!figurinha`\n" +
"- `!adivinhação começar|parar`\n" +
"- `!info <comando>`"
"*Comandos disponíveis:*\n\n" +
"🎬 `!video <link>` — baixa um vídeo\n" +
"🎵 `!audio <link>` — baixa um áudio\n" +
"🖼️ `!figurinha` — cria figurinhas\n" +
"🎮 `!adivinhação começar|parar` — jogo de adivinhar número\n"
));
break;
case "!ping":
await msg.reply(botMsg("pong 🏓"));
log.ok("pong enviado");
break;
case "!video":
if (!tokens[1]) { log.warn("!video sem link"); return; }
await msg.reply(botMsg("⏳ Baixando vídeo..."));
if (!tokens[1]) {
await msg.reply(botMsg("❌ Você precisa informar um link.\n\nExemplo: `!video https://youtube.com/...`"));
log.warn("!video sem link");
return;
}
await msg.reply(botMsg("⏳ Baixando o vídeo, aguarde..."));
enqueueDownload("video", tokens[1], msg, chatId);
log.ok("vídeo enfileirado →", tokens[1]);
break;
case "!audio":
if (!tokens[1]) { log.warn("!audio sem link"); return; }
await msg.reply(botMsg("⏳ Baixando áudio..."));
if (!tokens[1]) {
await msg.reply(botMsg("❌ Você precisa informar um link.\n\nExemplo: `!audio https://youtube.com/...`"));
log.warn("!audio sem link");
return;
}
await msg.reply(botMsg("⏳ Baixando o áudio, aguarde..."));
enqueueDownload("audio", tokens[1], msg, chatId);
log.ok("áudio enfileirado →", tokens[1]);
break;
case "!figurinha":
const author = msg.author || msg.from;
const name = msg._data?.notifyName || author.replace(/(:\d+)?@.*$/, "");
const groupId = chat.id._serialized; // < fonte única de verdade
if (tokens[1] === "criar") {
await gerarSticker(msg, chatId);
await gerarSticker(msg, groupId);
} else {
if (stickerSessions.has(chatId)) {
return msg.reply(botMsg("Já existe uma sessão ativa."));
if (stickerSessions.has(groupId)) {
return msg.reply(botMsg(
"⚠️ Já existe uma sessão aberta.\n\n" +
"Envie as mídias e depois use `!figurinha criar`.\n" +
"Ou aguarde 2 minutos para a sessão expirar."
));
}
iniciarSessao(chatId, author);
iniciarSessao(groupId, author, msg);
await msg.reply(botMsg(
`Sessão de figurinha iniciada por @${author.split("@")[0]}. Envie no máximo 10 imagens, quando estiver pronto mande \`!figurinha criar\``,
null,
{ mentions: [author] }
`Sessão iniciada por *${name}*!\n\n` + help
));
}
break;
case "!adivinhação":
if (!tokens[1]) {
await chat.sendMessage(botMsg("`!adivinhação começar`\n`!adivinhação parar`"));
await chat.sendMessage(botMsg(
"🎮 *Jogo de adivinhação:*\n\n" +
"`!adivinhação começar` — inicia o jogo\n" +
"`!adivinhação parar` — encerra o jogo"
));
return;
}
if (tokens[1] === "começar") {
iniciarJogo();
await chat.sendMessage(botMsg("Jogo iniciado! Tente adivinhar o número de 1 a 100."));
await chat.sendMessage(botMsg(
"🎮 *Jogo iniciado!*\n\n" +
"Estou pensando em um número de 1 a 100.\n" +
"Tente adivinhar! 🤔"
));
log.ok("jogo iniciado");
} else if (tokens[1] === "parar") {
pararJogo();
await chat.sendMessage(botMsg("Jogo parado."));
await chat.sendMessage(botMsg("🛑 Jogo encerrado."));
log.ok("jogo parado");
} else {
await chat.sendMessage(botMsg(
`❌ Subcomando *${tokens[1]}* não existe.\n\n` +
"Use `!adivinhação começar` ou `!adivinhação parar`."
));
log.warn("!adivinhação — subcomando desconhecido:", tokens[1]);
}
break;
case "!info":
if (!tokens[1]) {
await chat.sendMessage(botMsg("Use:\n`!info <comando>`"));
return;
}
processarInfo(tokens[1], chat);
log.ok("info →", tokens[1]);
break;
case "!obrigado":
case "!valeu":
case "!brigado":
await msg.reply(botMsg("Por nada!"));
await msg.reply(botMsg("😊 Por nada!"));
break;
case "a":
@@ -124,6 +130,9 @@ export async function processarComando(msg, chat, chatId) {
}
} catch (err) {
log.error("Falha em", cmd, "—", err.message);
await chat.sendMessage(botMsg("Erro:\n`" + err.message + "`"));
await chat.sendMessage(botMsg(
"❌ Algo deu errado ao executar esse comando.\n" +
"Tente novamente em instantes."
));
}
}

View File

@@ -5,20 +5,30 @@ import os from "os";
const so = os.platform();
export async function get_audio(url, id) {
const video = await get_video(url, id);
const output = `downloads/${id}.mp3`;
const cmd = so === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg";
const args = ['-i', video, '-vn', '-acodec', 'libmp3lame', '-q:a', '2', output];
const video = await get_video(url, id);
const output = `downloads/${id}.mp3`;
await runCmd(cmd, args);
return output;
const cmd = so === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg";
const args = ["-i", video, "-vn", "-acodec", "libmp3lame", "-q:a", "2", output];
await runCmd(cmd, args);
return output;
}
async function runCmd(cmd, args) {
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args);
proc.stdout.on("data", data => console.log("[cmd]", data.toString()));
proc.stderr.on("data", data => console.error("[cmd ERR]", data.toString()));
proc.on("close", code => code === 0 ? resolve() : reject(new Error("Processo saiu com código "+code)));
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args);
proc.stdout.on("data", data => console.log("[cmd]", data.toString()));
proc.stderr.on("data", data => console.error("[cmd ERR]", data.toString()));
proc.on("close", code => {
if (code !== 0) {
return reject(new Error(
"Não foi possível converter o áudio. Tente novamente com outro link."
));
}
resolve();
});
});
}

View File

@@ -1,42 +1,53 @@
import { get_video } from "./video.js";
import { get_audio } from "./audio.js";
import pkg from "whatsapp-web.js";
const { MessageMedia } = pkg;
import fs from "fs";
import { botMsg } from "../utils/botMsg.js";
import { emptyFolder } from "../utils/file.js";
import client from "../client/whatsappClient.js";
const { MessageMedia } = pkg;
let downloadQueue = [];
let processingQueue = false;
export function enqueueDownload(type, url, msg, chatId) {
downloadQueue.push({ type, url, msg, chatId });
if (!processingQueue) processQueue();
downloadQueue.push({ type, url, msg, chatId });
if (!processingQueue) processQueue();
}
async function processQueue() {
processingQueue = true;
while (downloadQueue.length) {
const job = downloadQueue.shift();
try {
let path;
if (job.type === "video") path = await get_video(job.url, job.msg.id._serialized);
else path = await get_audio(job.url, job.msg.id._serialized);
processingQueue = true;
const file = fs.readFileSync(path);
const media = new MessageMedia(
job.type === "video" ? "video/mp4" : "audio/mpeg",
file.toString("base64"),
path.split("/").pop()
);
await client.sendMessage(job.chatId, media);
fs.unlinkSync(path);
emptyFolder("downloads");
while (downloadQueue.length) {
const job = downloadQueue.shift();
const label = job.type === "video" ? "vídeo" : "áudio";
} catch (err) {
await client.sendMessage(job.chatId, botMsg(`❌ Erro ao baixar ${job.type}\n\`${err.message}\``));
}
try {
const filePath = job.type === "video"
? await get_video(job.url, job.msg.id._serialized)
: await get_audio(job.url, job.msg.id._serialized);
const file = fs.readFileSync(filePath);
const media = new MessageMedia(
job.type === "video" ? "video/mp4" : "audio/mpeg",
file.toString("base64"),
filePath.split("/").pop()
);
await client.sendMessage(job.chatId, media);
fs.unlinkSync(filePath);
emptyFolder("downloads");
} catch (err) {
console.error(`[queue] Erro ao baixar ${label}:`, err.message);
await job.msg.reply(botMsg(
`❌ Não consegui baixar o ${label}.\n\n` +
"Verifique se o link é válido e tente novamente.\n" +
"Se o problema persistir, o conteúdo pode estar indisponível ou protegido."
));
}
processingQueue = false;
}
processingQueue = false;
}

View File

@@ -6,50 +6,54 @@ import os from "os";
const platform = os.platform();
export async function get_video(url, id) {
// garante que a pasta exista
const downloadsDir = path.resolve("downloads");
fs.mkdirSync(downloadsDir, { recursive: true });
const downloadsDir = path.resolve("downloads");
fs.mkdirSync(downloadsDir, { recursive: true });
const cmd = platform === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp";
const args = [
'--extractor-args', 'youtube:player_client=android',
'-f', 'bv+ba/best',
'--print', 'after_move:filepath',
'--output', path.join(downloadsDir, `${id}.%(ext)s`),
'--cookies', 'cookies.txt',
'--add-header', 'User-Agent:Mozilla/5.0',
'--add-header', 'Referer:https://www.youtube.com/',
'--retries', '4',
'--fragment-retries', '5',
'--socket-timeout', '15',
'--sleep-interval', '1', '--max-sleep-interval', '4',
'--no-playlist',
url
];
const cmd = platform === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp";
const args = [
"--extractor-args", "youtube:player_client=android",
"-f", "bv+ba/best",
"--print", "after_move:filepath",
"--output", path.join(downloadsDir, `${id}.%(ext)s`),
"--cookies", "cookies.txt",
"--add-header", "User-Agent:Mozilla/5.0",
"--add-header", "Referer:https://www.youtube.com/",
"--retries", "4",
"--fragment-retries", "5",
"--socket-timeout", "15",
"--sleep-interval", "1", "--max-sleep-interval", "4",
"--no-playlist",
url,
];
return await runCmd(cmd, args);
return await runCmd(cmd, args);
}
async function runCmd(cmd, args) {
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args);
let stdout = "";
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args);
let stdout = "";
proc.stdout.on("data", data => stdout += data.toString());
proc.stderr.on("data", data => console.error("[yt-dlp ERR]", data.toString()));
proc.stdout.on("data", data => stdout += data.toString());
proc.stderr.on("data", data => console.error("[yt-dlp ERR]", data.toString()));
proc.on("close", code => {
if (code !== 0) return reject(new Error("yt-dlp saiu com código " + code));
proc.on("close", code => {
if (code !== 0) {
return reject(new Error(
"Não foi possível baixar o vídeo. Verifique se o link é válido e tente novamente."
));
}
// Pega a última linha, que é o caminho final do arquivo
const lines = stdout.trim().split("\n").filter(l => l.trim());
const filepath = lines[lines.length - 1];
const lines = stdout.trim().split("\n").filter(l => l.trim());
const filepath = lines[lines.length - 1];
if (!fs.existsSync(filepath)) {
return reject(new Error("Arquivo não encontrado: " + filepath));
}
if (!fs.existsSync(filepath)) {
return reject(new Error(
"O download foi concluído, mas o arquivo não foi encontrado. Tente novamente."
));
}
resolve(filepath);
});
resolve(filepath);
});
});
}

View File

@@ -3,27 +3,33 @@ import { botMsg } from "../utils/botMsg.js";
let jogoAtivo = null;
export function iniciarJogo() {
jogoAtivo = Math.floor(Math.random()*100)+1;
return jogoAtivo;
jogoAtivo = Math.floor(Math.random() * 100) + 1;
return jogoAtivo;
}
export function pararJogo() {
jogoAtivo = null;
jogoAtivo = null;
}
export async function processarJogo(msg, chat) {
if (!jogoAtivo) return;
if (!jogoAtivo) return;
const tentativa = msg.body.trim();
if (!/^\d+$/.test(tentativa)) return;
const tentativa = msg.body.trim();
if (!/^\d+$/.test(tentativa)) return;
const num = parseInt(tentativa);
if (num === jogoAtivo) {
await msg.reply(botMsg(`Acertou! Número: ${jogoAtivo}`));
pararJogo();
} else if (num > jogoAtivo) {
await chat.sendMessage(botMsg("Menor."));
} else {
await chat.sendMessage(botMsg("Maior."));
}
const num = parseInt(tentativa);
if (num === jogoAtivo) {
await msg.reply(botMsg(
`🎉 *Acertou!* O número era ${jogoAtivo}!\n\n` +
"Use `!adivinhação começar` para jogar de novo."
));
pararJogo();
} else if (num < 1 || num > 100) {
await msg.reply(botMsg("⚠️ Digite um número entre 1 e 100."));
} else if (num > jogoAtivo) {
await chat.sendMessage(botMsg("📉 Tente um número *menor*!"));
} else {
await chat.sendMessage(botMsg("📈 Tente um número *maior*!"));
}
}

View File

@@ -55,7 +55,7 @@ const logger = {
: `${c.bold}${chatName}${c.reset} ${c.dim}(privado)${c.reset}`;
const bodyPreview = body?.trim()
? `${isCommand ? c.yellow : c.green}"${body.length > 80 ? body.slice(0, 80) + "" : body}"${c.reset}`
? `${isCommand ? c.yellow : c.green}"${body.length > 200 ? body.slice(0, 200) + "..." : body}"${c.reset}`
: `${c.dim}<${typeLabel}>${c.reset}`;
// Resolve reply
@@ -70,7 +70,7 @@ const logger = {
quotedName = quotedContact?.pushname || quotedContact?.formattedName || quotedNumber;
} catch {}
const quotedPreview = quoted.body?.trim()
? `"${quoted.body.length > 60 ? quoted.body.slice(0, 60) + "…" : quoted.body}"`
? `"${quoted.body.length > 80 ? quoted.body.slice(0, 80) + "…" : quoted.body}"`
: `<${quoted.type}>`;
replyLine =
`\n${c.gray} ↩ Para: ${c.reset}${c.white}${quotedName}${c.reset} ${c.dim}+${quotedNumber}${c.reset}` +

View File

@@ -1,4 +1,6 @@
// get_id.js
import { CLIENT_ID } from "../config.js";
const arg = process.argv[2]; // argumento passado no node
if (!arg) {
@@ -12,8 +14,6 @@ import pkg from 'whatsapp-web.js';
const { Client, LocalAuth } = pkg;
import qrcode from 'qrcode-terminal';
const CLIENT_ID = "bot_permanente"; // sempre o mesmo
const client = new Client({
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
puppeteer: { headless: true }
@@ -24,10 +24,10 @@ client.on('qr', qr => {
qrcode.generate(qr, { small: true });
});
client.on('ready', async () => {
client.on('change_state', async state => {
console.log("[WPP] Conectado");
const chats = await client.getChats(); // <- precisa do await
const chats = await client.getChats();
let filtered = [];
@@ -40,18 +40,14 @@ client.on('ready', async () => {
filtered = chats.filter(c => (c.name || c.id.user).toLowerCase().includes(search));
}
if (filtered.length === 0) {
console.log("Nenhum chat encontrado com esse filtro.");
} else {
console.log(`Encontrados ${filtered.length} chats:`);
filtered.forEach(c => {
console.log("================================");
console.log("NAME:", c.name || c.id.user);
console.log("ID:", c.id._serialized);
console.log("GROUP:", c.isGroup);
});
}
filtered.forEach(c => {
console.log("================================");
console.log("NAME:", c.name || c.id.user);
console.log("ID:", c.id._serialized);
console.log("GROUP:", c.isGroup);
});
await client.destroy();
process.exit(0);
});