new logs, removing sharp as dependency, stickers are resized
This commit is contained in:
@@ -6,49 +6,97 @@ import os from "os";
|
||||
|
||||
export const { Client, LocalAuth, MessageMedia } = pkg;
|
||||
|
||||
// detecta termux, e usa o executável do chromium do sistema em vez do puppeteer
|
||||
// ── Logger ──────────────────────────────────────────────────
|
||||
const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
|
||||
blue: "\x1b[34m", magenta: "\x1b[35m",
|
||||
};
|
||||
|
||||
const now = () =>
|
||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "short" });
|
||||
|
||||
export const logger = {
|
||||
info: (...a) => console.log(`${c.gray}${now()}${c.reset} ℹ️ `, ...a),
|
||||
success: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.green}✅${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.yellow}⚠️ ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.red}❌${c.reset}`, ...a),
|
||||
bot: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.magenta}🤖${c.reset}`, ...a),
|
||||
};
|
||||
|
||||
// ── Banner ───────────────────────────────────────────────────
|
||||
function printBanner() {
|
||||
console.log(`${c.blue}${c.bold}`);
|
||||
console.log(` _____ _____ _ `);
|
||||
console.log(`| |___ ___ _ _| __ |___| |_ `);
|
||||
console.log(`| | | | .'| | | | __ -| . | _|`);
|
||||
console.log(`|_|_|_|__,|_|_|_ |_____|___|_| `);
|
||||
console.log(` |___| `);
|
||||
console.log();
|
||||
console.log(` website : ${c.reset}${c.cyan}www.mlplovers.com.br/manybot${c.reset}`);
|
||||
console.log(` repo : ${c.reset}${c.cyan}github.com/synt-xerror/manybot${c.reset}`);
|
||||
console.log();
|
||||
console.log(` ${c.bold}✨ A Amizade é Mágica!${c.reset}`);
|
||||
console.log();
|
||||
}
|
||||
|
||||
// ── Ambiente ─────────────────────────────────────────────────
|
||||
const isTermux =
|
||||
(os.platform() === "linux" || os.platform() === "android") &&
|
||||
process.env.PREFIX?.startsWith("/data/data/com.termux");
|
||||
|
||||
logger.info(isTermux
|
||||
? `Ambiente: ${c.yellow}${c.bold}Termux${c.reset} — usando Chromium do sistema`
|
||||
: `Ambiente: ${c.blue}${c.bold}${os.platform()}${c.reset} — usando Puppeteer padrão`
|
||||
);
|
||||
|
||||
const puppeteerConfig = isTermux
|
||||
? {
|
||||
executablePath: "/data/data/com.termux/files/usr/bin/chromium-browser",
|
||||
args: [
|
||||
"--headless=new",
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-gpu",
|
||||
"--single-process",
|
||||
"--no-zygote",
|
||||
"--disable-software-rasterizer"
|
||||
]
|
||||
executablePath: "/data/data/com.termux/files/usr/bin/chromium-browser",
|
||||
args: [
|
||||
"--headless=new", "--no-sandbox", "--disable-setuid-sandbox",
|
||||
"--disable-dev-shm-usage", "--disable-gpu", "--single-process",
|
||||
"--no-zygote", "--disable-software-rasterizer",
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
// ── Cliente ──────────────────────────────────────────────────
|
||||
export const client = new Client({
|
||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||
puppeteer: {
|
||||
headless: true,
|
||||
...puppeteerConfig
|
||||
}
|
||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||
puppeteer: { headless: true, ...puppeteerConfig },
|
||||
});
|
||||
|
||||
client.on("qr", qr => {
|
||||
console.log("[BOT] Escaneie o QR Code");
|
||||
console.log(qr);
|
||||
client.on("qr", async qr => {
|
||||
if (isTermux) {
|
||||
try {
|
||||
await QRCode.toFile(QR_PATH, qr, { width: 400 });
|
||||
logger.bot(`QR Code salvo em: ${c.cyan}${c.bold}${QR_PATH}${c.reset}`);
|
||||
logger.bot(`Abra com: ${c.yellow}termux-open qr.png${c.reset}`);
|
||||
} catch (err) {
|
||||
logger.error("Falha ao salvar QR Code:", err.message);
|
||||
}
|
||||
} else {
|
||||
logger.bot(`Escaneie o ${c.yellow}${c.bold}QR Code${c.reset} abaixo:`);
|
||||
qrcode.generate(qr, { small: true });
|
||||
}
|
||||
});
|
||||
|
||||
client.on("ready", () => {
|
||||
exec("clear");
|
||||
console.log("[BOT] WhatsApp conectado.");
|
||||
exec("clear");
|
||||
printBanner();
|
||||
logger.success(`${c.green}${c.bold}WhatsApp conectado e pronto!${c.reset}`);
|
||||
logger.info(`Client ID: ${c.cyan}${CLIENT_ID}${c.reset}`);
|
||||
});
|
||||
|
||||
client.on("disconnected", reason => {
|
||||
console.warn("[BOT] Reconectando:", reason);
|
||||
setTimeout(() => client.initialize(), 5000);
|
||||
logger.warn(`Desconectado — motivo: ${c.yellow}${reason}${c.reset}`);
|
||||
logger.info(`Reconectando em ${c.cyan}5s${c.reset}...`);
|
||||
setTimeout(() => {
|
||||
logger.bot("Reinicializando cliente...");
|
||||
client.initialize();
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
export default client;
|
||||
@@ -1,97 +1,256 @@
|
||||
import pkg from "whatsapp-web.js";
|
||||
const { MessageMedia } = pkg;
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { execFile } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import webpmux from "node-webpmux";
|
||||
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
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 exec = promisify(execFile);
|
||||
const { MessageMedia } = pkg;
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export async function gerarSticker(msg) {
|
||||
const DOWNLOADS_DIR = path.resolve("downloads");
|
||||
const FFMPEG = os.platform() === "win32"
|
||||
? ".\\bin\\ffmpeg.exe"
|
||||
: "./bin/ffmpeg";
|
||||
|
||||
if (!msg.hasMedia) {
|
||||
await msg.reply(botMsg("Envie uma imagem junto com o comando: `!figurinha`."));
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
const media = await msg.downloadMedia();
|
||||
const ext = media.mimetype.split("/")[1];
|
||||
}
|
||||
|
||||
const input = `downloads/${msg.id._serialized}.${ext}`;
|
||||
const output = `downloads/${msg.id._serialized}.webp`;
|
||||
throw new Error("Não foi possível reduzir o sticker para menos de 900 KB.");
|
||||
}
|
||||
|
||||
fs.writeFileSync(input, Buffer.from(media.data, "base64"));
|
||||
// ───────────────── Sessão ─────────────────
|
||||
|
||||
const so = os.platform();
|
||||
const cmd = so === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg";
|
||||
export function iniciarSessao(chatId, author) {
|
||||
|
||||
await exec(cmd, [
|
||||
"-y",
|
||||
"-i", input,
|
||||
"-vf",
|
||||
"scale=512:512:force_original_aspect_ratio=decrease,pad=512:512:(ow-iw)/2:(oh-ih)/2:color=0x00000000",
|
||||
"-vcodec", "libwebp",
|
||||
"-lossless", "1",
|
||||
"-qscale", "75",
|
||||
"-preset", "default",
|
||||
"-an",
|
||||
"-vsync", "0",
|
||||
output
|
||||
]);
|
||||
if (stickerSessions.has(chatId)) return false;
|
||||
|
||||
const img = new webpmux.Image();
|
||||
await img.load(output);
|
||||
const timeout = setTimeout(() => {
|
||||
|
||||
const metadata = {
|
||||
"sticker-pack-id": "manybot",
|
||||
"sticker-pack-name": "Feito por ManyBot",
|
||||
"sticker-pack-publisher": "My Little Pony Lovers",
|
||||
"emojis": ["🤖"]
|
||||
};
|
||||
stickerSessions.delete(chatId);
|
||||
client.sendMessage(chatId, botMsg("Sessão de figurinha expirou."));
|
||||
|
||||
const json = Buffer.from(JSON.stringify(metadata));
|
||||
}, SESSION_TIMEOUT);
|
||||
|
||||
const exif = Buffer.concat([
|
||||
Buffer.from([
|
||||
0x49,0x49,0x2A,0x00,
|
||||
0x08,0x00,0x00,0x00,
|
||||
0x01,0x00,
|
||||
0x41,0x57,
|
||||
0x07,0x00
|
||||
]),
|
||||
Buffer.from([
|
||||
json.length & 0xff,
|
||||
(json.length >> 8) & 0xff,
|
||||
(json.length >> 16) & 0xff,
|
||||
(json.length >> 24) & 0xff
|
||||
]),
|
||||
json
|
||||
]);
|
||||
stickerSessions.set(chatId, {
|
||||
author,
|
||||
medias: [],
|
||||
timeout
|
||||
});
|
||||
|
||||
img.exif = exif;
|
||||
await img.save(output);
|
||||
return true;
|
||||
|
||||
const data = fs.readFileSync(output);
|
||||
}
|
||||
|
||||
const sticker = new MessageMedia(
|
||||
"image/webp",
|
||||
data.toString("base64"),
|
||||
"sticker.webp"
|
||||
);
|
||||
// ───────────────── Coleta de mídia ─────────────────
|
||||
|
||||
const chat = await msg.getChat();
|
||||
export async function coletarMidia(msg) {
|
||||
|
||||
await client.sendMessage(
|
||||
chat.id._serialized,
|
||||
sticker,
|
||||
{ sendMediaAsSticker: true }
|
||||
);
|
||||
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");
|
||||
|
||||
fs.unlinkSync(input);
|
||||
fs.unlinkSync(output);
|
||||
}
|
||||
@@ -1,71 +1,129 @@
|
||||
import { enqueueDownload } from "../download/queue.js";
|
||||
import { gerarSticker } from "./figurinha.js";
|
||||
import { iniciarSessao, gerarSticker } from "./figurinha.js";
|
||||
import { botMsg } from "../utils/botMsg.js";
|
||||
import { iniciarJogo, pararJogo, processarJogo } from "../games/adivinhacao.js";
|
||||
import { iniciarJogo, pararJogo } from "../games/adivinhacao.js";
|
||||
import { processarInfo } from "./info.js";
|
||||
|
||||
export const stickerSessions = new Map();
|
||||
|
||||
const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m",
|
||||
};
|
||||
|
||||
const now = () =>
|
||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" });
|
||||
|
||||
const log = {
|
||||
cmd: (cmd, ...a) => console.log(`${c.gray}${now()}${c.reset} ${c.cyan}⚙️ ${c.bold}${cmd}${c.reset}`, ...a),
|
||||
ok: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.green}✅${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.yellow}⚠️ ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}${now()}${c.reset} ${c.red}❌${c.reset}`, ...a),
|
||||
};
|
||||
|
||||
export async function processarComando(msg, chat, chatId) {
|
||||
const tokens = msg.body.trim().split(/\s+/);
|
||||
try {
|
||||
switch(tokens[0]) {
|
||||
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>`"
|
||||
));
|
||||
break;
|
||||
const tokens = msg.body.trim().split(/\s+/);
|
||||
const cmd = tokens[0]?.toLowerCase();
|
||||
|
||||
case "!ping":
|
||||
await msg.reply(botMsg("pong 🏓"));
|
||||
break;
|
||||
if (!cmd?.startsWith("!") && cmd !== "a") return;
|
||||
|
||||
case "!video":
|
||||
if (!tokens[1]) return;
|
||||
await msg.reply(botMsg("⏳ Baixando vídeo..."));
|
||||
enqueueDownload("video", tokens[1], msg, chatId);
|
||||
break;
|
||||
log.cmd(cmd);
|
||||
|
||||
case "!audio":
|
||||
if (!tokens[1]) return;
|
||||
await msg.reply(botMsg("⏳ Baixando áudio..."));
|
||||
enqueueDownload("audio", tokens[1], msg, chatId);
|
||||
break;
|
||||
try {
|
||||
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>`"
|
||||
));
|
||||
break;
|
||||
|
||||
case "!figurinha":
|
||||
await gerarSticker(msg);
|
||||
break;
|
||||
case "!ping":
|
||||
await msg.reply(botMsg("pong 🏓"));
|
||||
log.ok("pong enviado");
|
||||
break;
|
||||
|
||||
case "!adivinhação":
|
||||
if (!tokens[1]) {
|
||||
await chat.sendMessage(botMsg("`!adivinhação começar`\n`!adivinhação parar`"));
|
||||
return;
|
||||
}
|
||||
if (tokens[1] === "começar") {
|
||||
iniciarJogo();
|
||||
await chat.sendMessage(botMsg("Jogo iniciado! Tente adivinhar o número de 1 a 100."));
|
||||
}
|
||||
if (tokens[1] === "parar") {
|
||||
pararJogo();
|
||||
await chat.sendMessage(botMsg("Jogo parado."));
|
||||
}
|
||||
break;
|
||||
case "!video":
|
||||
if (!tokens[1]) { log.warn("!video sem link"); return; }
|
||||
await msg.reply(botMsg("⏳ Baixando vídeo..."));
|
||||
enqueueDownload("video", tokens[1], msg, chatId);
|
||||
log.ok("vídeo enfileirado →", tokens[1]);
|
||||
break;
|
||||
|
||||
case "!info":
|
||||
if (!tokens[1]) {
|
||||
await chat.sendMessage(botMsg("Use:\n`!info <comando>`"));
|
||||
return;
|
||||
} else {
|
||||
processarInfo(tokens[1], chat);
|
||||
}
|
||||
break;
|
||||
case "!audio":
|
||||
if (!tokens[1]) { log.warn("!audio sem link"); return; }
|
||||
await msg.reply(botMsg("⏳ Baixando áudio..."));
|
||||
enqueueDownload("audio", tokens[1], msg, chatId);
|
||||
log.ok("áudio enfileirado →", tokens[1]);
|
||||
break;
|
||||
|
||||
case "!figurinha":
|
||||
const author = msg.author || msg.from;
|
||||
|
||||
if (tokens[1] === "criar") {
|
||||
await gerarSticker(msg, chatId);
|
||||
} else {
|
||||
if (stickerSessions.has(chatId)) {
|
||||
return msg.reply("Já existe uma sessão ativa.");
|
||||
}
|
||||
|
||||
iniciarSessao(chatId, author);
|
||||
|
||||
await msg.reply(
|
||||
`Sessão de figurinha iniciada por @${author.split("@")[0]}. Envie no máximo 10 imagens, quando estiver pronto mande \`!figurinha criar\``,
|
||||
null,
|
||||
{ mentions: [author] }
|
||||
);
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
await chat.sendMessage(botMsg("Erro:\n`"+err.message+"`"));
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case "!adivinhação":
|
||||
if (!tokens[1]) {
|
||||
await chat.sendMessage(botMsg("`!adivinhação começar`\n`!adivinhação parar`"));
|
||||
return;
|
||||
}
|
||||
if (tokens[1] === "começar") {
|
||||
iniciarJogo();
|
||||
await chat.sendMessage(botMsg("Jogo iniciado! Tente adivinhar o número de 1 a 100."));
|
||||
log.ok("jogo iniciado");
|
||||
} else if (tokens[1] === "parar") {
|
||||
pararJogo();
|
||||
await chat.sendMessage(botMsg("Jogo parado."));
|
||||
log.ok("jogo parado");
|
||||
} else {
|
||||
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!"));
|
||||
break;
|
||||
|
||||
case "a":
|
||||
if (!tokens[1]) await msg.reply(botMsg("B"));
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
log.error("Falha em", cmd, "—", err.message);
|
||||
await chat.sendMessage(botMsg("Erro:\n`" + err.message + "`"));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export async function get_video(url, id) {
|
||||
'--fragment-retries', '5',
|
||||
'--socket-timeout', '15',
|
||||
'--sleep-interval', '1', '--max-sleep-interval', '4',
|
||||
'--no-playlist',
|
||||
url
|
||||
];
|
||||
|
||||
|
||||
141
src/main.js
141
src/main.js
@@ -1,29 +1,134 @@
|
||||
console.log("Iniciando...");
|
||||
|
||||
import client from "./client/whatsappClient.js";
|
||||
import { CHATS } from "./config.js";
|
||||
import { CHATS, BOT_PREFIX } from "./config.js"; // <- importar PREFIX
|
||||
import { processarComando } from "./commands/index.js";
|
||||
import { coletarMidia } from "./commands/figurinha.js";
|
||||
import { processarJogo } from "./games/adivinhacao.js";
|
||||
import { getChatId } from "./utils/getChatId.js";
|
||||
|
||||
client.on("message_create", async msg => {
|
||||
// ── Cores ────────────────────────────────────────────────────
|
||||
const c = {
|
||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
|
||||
blue: "\x1b[34m", magenta: "\x1b[35m",
|
||||
};
|
||||
|
||||
const now = () =>
|
||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" });
|
||||
|
||||
const SEP = `${c.gray}${"─".repeat(52)}${c.reset}`;
|
||||
|
||||
// ── Logger ───────────────────────────────────────────────────
|
||||
const logger = {
|
||||
info: (...a) => console.log(`${SEP}\n${c.gray}[${now()}]${c.reset} ${c.cyan}INFO ${c.reset}`, ...a),
|
||||
success: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.green}OK ${c.reset}`, ...a),
|
||||
warn: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.yellow}WARN ${c.reset}`, ...a),
|
||||
error: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.red}ERROR ${c.reset}`, ...a),
|
||||
|
||||
msg: async (chatName, chatId, from, body, msg = {}) => {
|
||||
const number = String(from).split("@")[0];
|
||||
const isGroup = /@g\.us$/.test(chatId);
|
||||
let name = msg?.notifyName || number;
|
||||
|
||||
try {
|
||||
const chat = await msg.getChat();
|
||||
const chatId = getChatId(chat);
|
||||
if (!CHATS.includes(chatId)) return;
|
||||
if (typeof client?.getContactById === "function") {
|
||||
const contact = await client.getContactById(from);
|
||||
name = contact?.pushname || contact?.formattedName || name;
|
||||
}
|
||||
} catch {}
|
||||
|
||||
console.log("==================================");
|
||||
console.log(`CHAT NAME : ${chat.name || chat.id.user}`);
|
||||
console.log(`CHAT ID : ${chatId}`);
|
||||
console.log(`FROM : ${msg.from}`);
|
||||
console.log(`BODY : ${msg.body}`);
|
||||
console.log("==================================\n");
|
||||
const type = msg?.type || "text";
|
||||
const typeLabel =
|
||||
type === "sticker" ? `${c.magenta}sticker${c.reset}` :
|
||||
type === "image" ? `${c.cyan}imagem${c.reset}` :
|
||||
type === "video" ? `${c.cyan}vídeo${c.reset}` :
|
||||
type === "audio" ? `${c.cyan}áudio${c.reset}` :
|
||||
type === "ptt" ? `${c.cyan}áudio${c.reset}` :
|
||||
type === "document" ? `${c.cyan}arquivo${c.reset}` :
|
||||
type === "chat" ? `${c.white}texto${c.reset}` :
|
||||
`${c.gray}${type}${c.reset}`;
|
||||
|
||||
await processarComando(msg, chat, chatId);
|
||||
await processarJogo(msg, chat);
|
||||
} catch(err) {
|
||||
console.error("[ERRO]", err);
|
||||
const isCommand = body?.trimStart().startsWith(BOT_PREFIX);
|
||||
|
||||
const context = isGroup
|
||||
? `${c.bold}${chatName}${c.reset} ${c.dim}(grupo)${c.reset}`
|
||||
: `${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}`
|
||||
: `${c.dim}<${typeLabel}>${c.reset}`;
|
||||
|
||||
// Resolve reply
|
||||
let replyLine = "";
|
||||
if (msg?.hasQuotedMsg) {
|
||||
try {
|
||||
const quoted = await msg.getQuotedMessage();
|
||||
const quotedNumber = String(quoted.from).split("@")[0];
|
||||
let quotedName = quotedNumber;
|
||||
try {
|
||||
const quotedContact = await client.getContactById(quoted.from);
|
||||
quotedName = quotedContact?.pushname || quotedContact?.formattedName || quotedNumber;
|
||||
} catch {}
|
||||
const quotedPreview = quoted.body?.trim()
|
||||
? `"${quoted.body.length > 60 ? quoted.body.slice(0, 60) + "…" : quoted.body}"`
|
||||
: `<${quoted.type}>`;
|
||||
replyLine =
|
||||
`\n${c.gray} ↩ Para: ${c.reset}${c.white}${quotedName}${c.reset} ${c.dim}+${quotedNumber}${c.reset}` +
|
||||
`\n${c.gray} ↩ Msg: ${c.reset}${c.dim}${quotedPreview}${c.reset}`;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${SEP}\n` +
|
||||
`${c.gray}[${now()}]${c.reset} ${c.cyan}MSG ${c.reset}${context}\n` +
|
||||
`${c.gray} De: ${c.reset}${c.white}${name}${c.reset} ${c.dim}+${number}${c.reset}\n` +
|
||||
`${c.gray} Tipo: ${c.reset}${typeLabel}${isCommand ? ` ${c.yellow}(bot)${c.reset}` : ""}\n` +
|
||||
`${c.gray} Text: ${c.reset}${bodyPreview}` +
|
||||
replyLine
|
||||
);
|
||||
},
|
||||
|
||||
cmd: (cmd, extra = "") =>
|
||||
console.log(
|
||||
`${c.gray}[${now()}]${c.reset} ${c.yellow}CMD ${c.reset}` +
|
||||
`${c.bold}${cmd}${c.reset}` +
|
||||
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
||||
),
|
||||
|
||||
done: (cmd, detail = "") =>
|
||||
console.log(
|
||||
`${c.gray}[${now()}]${c.reset} ${c.green}DONE ${c.reset}` +
|
||||
`${c.dim}${cmd}${c.reset}` +
|
||||
(detail ? ` — ${detail}` : "")
|
||||
),
|
||||
};
|
||||
|
||||
export { logger };
|
||||
|
||||
// ── Boot ─────────────────────────────────────────────────────
|
||||
logger.info("Iniciando ManyBot...");
|
||||
|
||||
client.on("message_create", async msg => {
|
||||
try {
|
||||
const chat = await msg.getChat();
|
||||
const chatId = getChatId(chat);
|
||||
|
||||
if (!CHATS.includes(chatId)) return;
|
||||
|
||||
await logger.msg(chat.name || chat.id.user, chatId, msg.from, msg.body, msg);
|
||||
|
||||
await coletarMidia(msg);
|
||||
await processarComando(msg, chat, chatId);
|
||||
await processarJogo(msg, chat);
|
||||
|
||||
logger.done("message_create", `de +${String(msg.from).split("@")[0]}`);
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`Falha ao processar — ${err.message}`,
|
||||
`\n Stack: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
client.initialize();
|
||||
client.initialize();
|
||||
logger.info("Cliente inicializado. Aguardando conexão com WhatsApp...");
|
||||
Reference in New Issue
Block a user