diff --git a/src/plugins/a/index.js b/src/plugins/a/index.js deleted file mode 100644 index 7850963..0000000 --- a/src/plugins/a/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { forcaAtiva } from "../forca/index.js"; - -export default async function ({ msg }) { - if (msg.body.trim().toLowerCase() !== "a") return; - if (msg.args.length > 1) return; - if (forcaAtiva) return; - - await msg.reply("B!"); -} \ No newline at end of file diff --git a/src/plugins/adivinhação/index.js b/src/plugins/adivinhação/index.js deleted file mode 100644 index e50f639..0000000 --- a/src/plugins/adivinhação/index.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * plugins/adivinhacao/index.js - * - * Estado dos jogos fica aqui dentro — isolado no plugin. - * Múltiplos grupos jogam simultaneamente sem conflito. - */ - -const RANGE = { min: 1, max: 100 }; -const jogosAtivos = new Map(); -import { CMD_PREFIX } from "../../config.js" - -const sorteio = () => - Math.floor(Math.random() * (RANGE.max - RANGE.min + 1)) + RANGE.min; - -export default async function ({ msg, api }) { - const chatId = api.chat.id; - - // ── Comando adivinhação ────────────────────────────────── - if (msg.is(CMD_PREFIX + "adivinhação")) { - const sub = msg.args[1]; - - if (!sub) { - await api.send( - "🎮 *Jogo de adivinhação:*\n\n" + - `\`${CMD_PREFIX}adivinhação começar\` — inicia o jogo\n` + - `\`${CMD_PREFIX}adivinhação parar\` — encerra o jogo` - ); - return; - } - - if (sub === "começar") { - jogosAtivos.set(chatId, sorteio()); - await api.send( - "🎮 *Jogo iniciado!*\n\n" + - "Estou pensando em um número de 1 a 100.\n" + - "Tente adivinhar! 🤔" - ); - api.log.info(CMD_PREFIX + "adivinhação — jogo iniciado"); - return; - } - - if (sub === "parar") { - jogosAtivos.delete(chatId); - await api.send("🛑 Jogo encerrado."); - api.log.info(CMD_PREFIX + "adivinhação — jogo parado"); - return; - } - - await api.send( - `❌ Subcomando *${sub}* não existe.\n\n` + - `Use ${CMD_PREFIX} + \`adivinhação começar\` ou ${CMD_PREFIX} + \`adivinhação parar\`.` - ); - return; - } - - // ── Tentativas durante o jogo ───────────────────────────── - const numero = jogosAtivos.get(chatId); - if (numero === undefined) return; - - const tentativa = msg.body.trim(); - if (!/^\d+$/.test(tentativa)) return; - - const num = parseInt(tentativa, 10); - - if (num < RANGE.min || num > RANGE.max) { - await msg.reply(`⚠️ Digite um número entre ${RANGE.min} e ${RANGE.max}.`); - return; - } - - if (num === numero) { - await msg.reply( - `🎉 *Acertou!* O número era ${numero}!\n\n` + - `Use ${CMD_PREFIX} + \`adivinhação começar\` para jogar de novo.` - ); - jogosAtivos.delete(chatId); - } else { - await api.send(num > numero ? "📉 Tente um número *menor*!" : "📈 Tente um número *maior*!"); - } -} \ No newline at end of file diff --git a/src/plugins/audio/index.js b/src/plugins/audio/index.js deleted file mode 100644 index 9a8dcdb..0000000 --- a/src/plugins/audio/index.js +++ /dev/null @@ -1,123 +0,0 @@ -/** - * plugins/audio/index.js - * - * Baixa vídeo via yt-dlp, converte para mp3 via ffmpeg e envia no chat. - * Todo o processo (download + conversão + envio + limpeza) fica aqui. - */ - -import { spawn } from "child_process"; -import { execFile } from "child_process"; -import { promisify } from "util"; -import fs from "fs"; -import path from "path"; -import os from "os"; -import { enqueue } from "../../download/queue.js"; -import { emptyFolder } from "../../utils/file.js"; -import { CMD_PREFIX } from "../../config.js"; - -const logStream = fs.createWriteStream("logs/audio-error.log", { flags: "a" }); - -const execFileAsync = promisify(execFile); - -const DOWNLOADS_DIR = path.resolve("downloads"); -const YT_DLP = os.platform() === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp"; -const FFMPEG = os.platform() === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg"; - -const ARGS_BASE = [ - "--extractor-args", "youtube:player_client=android", - "--print", "after_move:filepath", - "--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", - "-f", "bv+ba/best", -]; - -function downloadRaw(url, id) { - return new Promise((resolve, reject) => { - fs.mkdirSync(DOWNLOADS_DIR, { recursive: true }); - - const output = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`); - const proc = spawn(YT_DLP, [...ARGS_BASE, "--output", output, url]); - let stdout = ""; - - proc.on("error", err => reject(new Error( - err.code === "EACCES" - ? "Sem permissão para executar o yt-dlp. Rode: chmod +x ./bin/yt-dlp" - : err.code === "ENOENT" - ? "yt-dlp não encontrado em ./bin/yt-dlp" - : `Erro ao iniciar o yt-dlp: ${err.message}` - ))); - - proc.stdout.on("data", d => { stdout += d.toString(); }); - proc.stderr.on("data", d => logStream.write(d)); - - proc.on("close", code => { - if (code !== 0) return reject(new Error( - "Não foi possível baixar o áudio. Verifique se o link é válido e tente novamente." - )); - - const filePath = stdout.trim().split("\n").filter(Boolean).at(-1); - if (!filePath || !fs.existsSync(filePath)) return reject(new Error( - "Download concluído mas arquivo não encontrado. Tente novamente." - )); - - resolve(filePath); - }); - }); -} - -async function convertToMp3(videoPath, id) { - const mp3Path = path.join(DOWNLOADS_DIR, `${id}.mp3`); - - await execFileAsync(FFMPEG, [ - "-i", videoPath, - "-vn", // sem vídeo - "-ar", "44100", // sample rate - "-ac", "2", // stereo - "-b:a", "192k", // bitrate - "-y", // sobrescreve se existir - mp3Path, - ]); - - fs.unlinkSync(videoPath); // remove o vídeo intermediário - return mp3Path; -} - -export default async function ({ msg, api }) { - if (!msg.is(CMD_PREFIX + "audio")) return; - - const url = msg.args[1]; - - if (!url) { - await msg.reply(`❌ Você precisa informar um link.\n\nExemplo: \`${CMD_PREFIX}audio https://youtube.com/...\``); - return; - } - - await msg.reply("⏳ Baixando o áudio, aguarde..."); - - const id = `audio-${Date.now()}`; - - enqueue( - async () => { - const videoPath = await downloadRaw(url, id); - const mp3Path = await convertToMp3(videoPath, id); - await api.sendAudio(mp3Path); - fs.unlinkSync(mp3Path); - emptyFolder(DOWNLOADS_DIR); - api.log.info(`${CMD_PREFIX}audio concluído → ${url}`); - }, - async () => { - await msg.reply( - "❌ Não consegui baixar o áudio.\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." - ); - } - ); -} \ No newline at end of file diff --git a/src/plugins/figurinha/index.js b/src/plugins/figurinha/index.js deleted file mode 100644 index 7553b6d..0000000 --- a/src/plugins/figurinha/index.js +++ /dev/null @@ -1,247 +0,0 @@ -/** - * plugins/figurinha/index.js - * - * Modos de uso: - * comando + mídia anexa → cria 1 sticker direto - * comando + respondendo mídia → cria 1 sticker direto - * comando + mídia anexa + respondendo mídia → cria 2 stickers direto - * comando (sem mídia nenhuma) → abre sessão - * comando criar (com sessão aberta) → processa as mídias da sessão - */ - -import fs from "fs"; -import path from "path"; -import os from "os"; -import { execFile } from "child_process"; -import { promisify } from "util"; - -import { createSticker } from "wa-sticker-formatter"; -import { emptyFolder } from "../../utils/file.js"; -import { CMD_PREFIX } from "../../config.js"; - -const execFileAsync = promisify(execFile); - -// ── Constantes ──────────────────────────────────────────────── -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 = 30; - -const HELP = - "📌 *Como criar figurinhas:*\n\n" + - `1️⃣ Envie \`${CMD_PREFIX}figurinha\` junto com uma mídia, ou respondendo uma mídia\n` + - " — o sticker é criado na hora\n\n" + - "2️⃣ Ou use o modo sessão para várias mídias de uma vez:\n" + - ` — \`${CMD_PREFIX}figurinha\` sem mídia para iniciar\n` + - " — envie as imagens, GIFs ou vídeos\n" + - ` — \`${CMD_PREFIX}figurinha criar\` para gerar todas\n\n` + - "⏳ A sessão expira em 2 minutos se nenhuma mídia for enviada."; - -// ── Estado interno ──────────────────────────────────────────── -// { chatId → { author, medias[], timeout } } -const sessions = new Map(); - -// ── Conversão ───────────────────────────────────────────────── -function ensureDir() { - fs.mkdirSync(DOWNLOADS_DIR, { recursive: true }); -} - -function cleanup(...files) { - for (const f of files) { - try { if (f && fs.existsSync(f)) fs.unlinkSync(f); } catch { } - } -} - -async function convertToGif(input, output, fps = 12) { - const filter = [ - `fps=${Math.min(fps, 12)},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", input, "-filter_complex", filter, "-loop", "0", "-y", output]); -} - -async function resizeImage(input, output) { - await execFileAsync(FFMPEG, ["-i", input, "-vf", "scale=512:512:flags=lanczos", "-y", output]); -} - -async function buildSticker(inputPath, isAnimated) { - for (const quality of [80, 60, 40, 20]) { - const buf = await createSticker(fs.readFileSync(inputPath), { - pack: "Criada por ManyBot\n", - author: "\ngithub.com/synt-xerror/manybot", - type: isAnimated ? "FULL" : "STATIC", - categories: ["🤖"], - quality, - }); - if (buf.length <= MAX_STICKER_SIZE) return buf; - } - throw new Error("Não foi possível reduzir o sticker para menos de 900 KB."); -} - -/** - * Converte um objeto { mimetype, data } em sticker e envia. - * Retorna true se ok, false se falhou. - */ -async function processarUmaMedia(media, isGif, api, msg) { - ensureDir(); - - const ext = media.mimetype.split("/")[1]; - const isVideo = media.mimetype.startsWith("video/"); - 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}`); - - try { - fs.writeFileSync(inputPath, Buffer.from(media.data, "base64")); - - let stickerInput; - if (isAnimated) { - await convertToGif(inputPath, gifPath, isVideo ? 12 : 24); - stickerInput = gifPath; - } else { - await resizeImage(inputPath, resizedPath); - stickerInput = resizedPath; - } - - const buf = await buildSticker(stickerInput, isAnimated); - await api.sendSticker(buf); - return true; - } catch (err) { - api.log.error(`Erro ao gerar sticker: ${err.message}`); - await msg.reply( - "⚠️ Não consegui criar uma das figurinhas.\n" + - "Tente reenviar essa mídia ou use outro formato (JPG, PNG, GIF, MP4)." - ); - return false; - } finally { - cleanup(inputPath, gifPath, resizedPath); - } -} - -/** - * Verifica se uma mídia é suportada para sticker. - */ -function isSupported(media, isGif) { - return ( - media.mimetype?.startsWith("image/") || - media.mimetype?.startsWith("video/") || - isGif - ); -} - -// ── Plugin ──────────────────────────────────────────────────── -export default async function ({ msg, api }) { - const chatId = api.chat.id; - - if (!msg.is(CMD_PREFIX + "figurinha")) { - // ── Coleta de mídia durante sessão ────────────────────── - const session = sessions.get(chatId); - if (!session) return; - if (!msg.hasMedia) return; - if (msg.sender !== session.author) return; - - const media = await msg.downloadMedia(); - if (!media) return; - - const gif = media.mimetype === "image/gif" || - (media.mimetype === "video/mp4" && msg.isGif); - - if (isSupported(media, gif) && session.medias.length < MAX_MEDIA) { - session.medias.push({ media, isGif: gif }); - } - return; - } - - // ── figurinha criar ────────────────────────────────────── - const sub = msg.args[1]; - - if (sub === "criar") { - const session = sessions.get(chatId); - - if (!session) { - await msg.reply(`❌ *Nenhuma sessão ativa.*\n\n${HELP}`); - return; - } - if (!session.medias.length) { - await msg.reply(`📭 *Você ainda não enviou nenhuma mídia!*\n\n${HELP}`); - return; - } - - clearTimeout(session.timeout); - await msg.reply("⏳ Gerando suas figurinhas, aguarde um momento..."); - - for (const { media, isGif } of session.medias) { - await processarUmaMedia(media, isGif, api, msg); - } - - await msg.reply("✅ *Figurinhas criadas com sucesso!*\nSalve as que quiser no seu WhatsApp. 😄"); - sessions.delete(chatId); - emptyFolder(DOWNLOADS_DIR); - return; - } - - // ── figurinha com mídia direta ─────────────────────────── - const mediasParaCriar = []; - - // Mídia anexa à própria mensagem - if (msg.hasMedia) { - const media = await msg.downloadMedia(); - if (media) { - const gif = media.mimetype === "image/gif" || - (media.mimetype === "video/mp4" && msg.isGif); - if (isSupported(media, gif)) mediasParaCriar.push({ media, isGif: gif }); - } - } - - // Mídia da mensagem citada - if (msg.hasReply) { - const quoted = await msg.getReply(); - if (quoted?.hasMedia) { - const media = await quoted.downloadMedia(); - if (media) { - const gif = media.mimetype === "image/gif" || - (media.mimetype === "video/mp4" && quoted.isGif); - if (isSupported(media, gif)) mediasParaCriar.push({ media, isGif: gif }); - } - } - } - - // Tem mídia para criar direto - if (mediasParaCriar.length > 0) { - await msg.reply("⏳ Gerando figurinha, aguarde..."); - for (const { media, isGif } of mediasParaCriar) { - await processarUmaMedia(media, isGif, api, msg); - } - emptyFolder(DOWNLOADS_DIR); - return; - } - - // ── figurinha sem mídia → abre sessão ─────────────────── - if (sessions.has(chatId)) { - await msg.reply( - "⚠️ Já existe uma sessão aberta.\n\n" + - `Envie as mídias e depois use \`${CMD_PREFIX}figurinha criar\`.\n` + - "Ou aguarde 2 minutos para a sessão expirar." - ); - return; - } - - const timeout = setTimeout(async () => { - sessions.delete(chatId); - try { - await msg.reply( - "⏰ *Sessão expirada!*\n\n" + - "Você demorou mais de 2 minutos para enviar as mídias.\n" + - `Digite \`${CMD_PREFIX}figurinha\` para começar de novo.` - ); - } catch { } - }, SESSION_TIMEOUT); - - sessions.set(chatId, { author: msg.sender, medias: [], timeout }); - await msg.reply(`✅ Sessão iniciada por *${msg.senderName}*!\n\n${HELP}`); -} \ No newline at end of file diff --git a/src/plugins/forca/index.js b/src/plugins/forca/index.js deleted file mode 100644 index aad8518..0000000 --- a/src/plugins/forca/index.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * plugins/forca/index.js - * - * Estado dos jogos de forca fica aqui dentro — isolado no plugin. - * Múltiplos grupos jogam simultaneamente sem conflito. - */ - -import { CMD_PREFIX } from "../../config.js"; - -// Estados dos jogos -const jogosAtivos = new Map(); // chatId -> { palavra, tema, vidas, progresso } -const participantesAtivos = new Map(); // chatId -> Set de usuários que reagiram -export let forcaAtiva = false; - - -// Palavras de exemplo -const PALAVRAS = [ - { palavra: "python", tema: "Linguagem de programação" }, - { palavra: "javascript", tema: "Linguagem de programação" }, - { palavra: "java", tema: "Linguagem de programação" }, - { palavra: "cachorro", tema: "Animal" }, - { palavra: "gato", tema: "Animal" }, - { palavra: "elefante", tema: "Animal" }, - { palavra: "girafa", tema: "Animal" }, - { palavra: "guitarra", tema: "Instrumento musical" }, - { palavra: "piano", tema: "Instrumento musical" }, - { palavra: "bateria", tema: "Instrumento musical" }, - { palavra: "violino", tema: "Instrumento musical" }, - { palavra: "futebol", tema: "Esporte" }, - { palavra: "basquete", tema: "Esporte" }, - { palavra: "natação", tema: "Esporte" }, - { palavra: "tênis", tema: "Esporte" }, - { palavra: "brasil", tema: "País" }, - { palavra: "japão", tema: "País" }, - { palavra: "canadá", tema: "País" }, - { palavra: "frança", tema: "País" }, - { palavra: "marte", tema: "Planeta" }, - { palavra: "vênus", tema: "Planeta" }, - { palavra: "júpiter", tema: "Planeta" }, - { palavra: "saturno", tema: "Planeta" }, - { palavra: "minecraft", tema: "Jogo" }, - { palavra: "fortnite", tema: "Jogo" }, - { palavra: "roblox", tema: "Jogo" }, - { palavra: "amongus", tema: "Jogo" }, - { palavra: "rosa", tema: "Flor" }, - { palavra: "girassol", tema: "Flor" }, - { palavra: "tulipa", tema: "Flor" }, - { palavra: "orquídea", tema: "Flor" }, - { palavra: "tesoura", tema: "Objeto" }, - { palavra: "caderno", tema: "Objeto" }, - { palavra: "computador", tema: "Objeto" }, - { palavra: "telefone", tema: "Objeto" }, - { palavra: "lua", tema: "Corpo celeste" }, - { palavra: "sol", tema: "Corpo celeste" }, - { palavra: "estrela", tema: "Corpo celeste" }, - { palavra: "cometa", tema: "Corpo celeste" }, - { palavra: "oceano", tema: "Natureza" }, - { palavra: "montanha", tema: "Natureza" }, -]; - -// Função para gerar a palavra com underscores -const gerarProgresso = palavra => - palavra.replace(/[a-zA-Z]/g, "_"); - -export default async function ({ msg, api }) { - const chatId = api.chat.id; - const sub = msg.args[1]; - - // ── Comando principal do jogo - if (msg.is(CMD_PREFIX + "forca")) { - if (!sub) { - await api.send( - `🎮 *Jogo da Forca*\n\n` + - `\`${CMD_PREFIX}forca começar\` — inicia o jogo\n` + - `\`${CMD_PREFIX}forca parar\` — encerra o jogo` - ); - return; - } - - if (sub === "começar") { - forcaAtiva = true; - // Pega uma palavra aleatória - const sorteio = PALAVRAS[Math.floor(Math.random() * PALAVRAS.length)]; - - // Inicializa o jogo - jogosAtivos.set(chatId, { - palavra: sorteio.palavra.toLowerCase(), - tema: sorteio.tema, - vidas: 6, - progresso: gerarProgresso(sorteio.palavra) - }); - - participantesAtivos.set(chatId, new Set()); // reset participantes - - await api.send( - `🎮 *Jogo da Forca iniciado!*\n\n` + - `Tema: *${sorteio.tema}*\n` + - `Palavra: \`${gerarProgresso(sorteio.palavra)}\`\n` + - `Vidas: 6\n\n` + - `Digite uma letra para adivinhar!` - ); - return; - } - - if (sub === "parar") { - jogosAtivos.delete(chatId); - participantesAtivos.delete(chatId); - await api.send("🛑 Jogo da Forca encerrado."); - return; - } - - await api.send( - `❌ Subcomando *${sub}* não existe.\n` + - `Use ${CMD_PREFIX} + \`forca começar\` ou ${CMD_PREFIX} + \`forca parar\`.` - ); - return; - } - - // ── Tentativas durante o jogo - const jogo = jogosAtivos.get(chatId); - if (!jogo) return; // Nenhum jogo ativo - - const tentativa = msg.body.trim().toLowerCase(); - if (!/^[a-z]$/.test(tentativa)) return; // apenas letras simples - - // Se a letra está na palavra - let acerto = false; - let novoProgresso = jogo.progresso.split(""); - for (let i = 0; i < jogo.palavra.length; i++) { - if (jogo.palavra[i] === tentativa) { - novoProgresso[i] = tentativa; - acerto = true; - } - } - jogo.progresso = novoProgresso.join(""); - - if (!acerto) jogo.vidas--; - - // Feedback para o grupo - if (jogo.progresso === jogo.palavra) { - await msg.reply(`🎉 Parabéns! Palavra completa: \`${jogo.palavra}\``); - jogosAtivos.delete(chatId); - participantesAtivos.delete(chatId); - return; - } - - if (jogo.vidas <= 0) { - await msg.reply(`💀 Fim de jogo! Palavra era: \`${jogo.palavra}\``); - jogosAtivos.delete(chatId); - participantesAtivos.delete(chatId); - return; - } - - await msg.reply( - `Palavra: \`${jogo.progresso}\`\n` + - `Vidas: ${jogo.vidas}\n` + - (acerto ? "✅ Acertou a letra!" : "❌ Errou a letra!") - ); -} \ No newline at end of file diff --git a/src/plugins/many/index.js b/src/plugins/many/index.js deleted file mode 100644 index d70f9e7..0000000 --- a/src/plugins/many/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import { CMD_PREFIX } from "../../config.js" - -export default async function ({ msg, api }) { - if (!msg.is(CMD_PREFIX + "many")) return; - - await api.send( - `🤖 *ManyBot — Comandos disponíveis:*\n\n` + - `🎬 \`${CMD_PREFIX}video \` — baixa um vídeo\n` + - `🎵 \`${CMD_PREFIX}audio \` — baixa um áudio\n` + - `🖼️ \`${CMD_PREFIX}figurinha\` — cria figurinhas\n` + - `🎮 \`${CMD_PREFIX}adivinhação começar|parar\` — jogo de adivinhar número\n` + - `🎮 \`${CMD_PREFIX}forca começar|parar\` — jogo da forca\n` - ); -} \ No newline at end of file diff --git a/src/plugins/obrigado/index.js b/src/plugins/obrigado/index.js deleted file mode 100644 index eb4dfaf..0000000 --- a/src/plugins/obrigado/index.js +++ /dev/null @@ -1,8 +0,0 @@ -import { CMD_PREFIX } from "../../config.js"; -const gatilhos = ["obrigado", "valeu", "brigado"]; - -export default async function ({ msg }) { - if (!gatilhos.some(g => msg.is(CMD_PREFIX + g))) return; - - await msg.reply("😊 Por nada!"); -} \ No newline at end of file diff --git a/src/plugins/video/index.js b/src/plugins/video/index.js deleted file mode 100644 index 1a302eb..0000000 --- a/src/plugins/video/index.js +++ /dev/null @@ -1,98 +0,0 @@ -/** - * plugins/video/index.js - * - * Baixa vídeo via yt-dlp e envia no chat. - * Todo o processo (download + envio + limpeza) fica aqui. - */ - -import { spawn } from "child_process"; -import fs from "fs"; -import path from "path"; -import os from "os"; -import { enqueue } from "../../download/queue.js"; -import { emptyFolder } from "../../utils/file.js"; -import { CMD_PREFIX } from "../../config.js"; - -const logStream = fs.createWriteStream("logs/video-error.log", { flags: "a" }); - -const DOWNLOADS_DIR = path.resolve("downloads"); -const YT_DLP = os.platform() === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp"; - -const ARGS_BASE = [ - "--extractor-args", "youtube:player_client=android", - "--print", "after_move:filepath", - "--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", - "-f", "bv+ba/best", -]; - -function downloadVideo(url, id) { - return new Promise((resolve, reject) => { - fs.mkdirSync(DOWNLOADS_DIR, { recursive: true }); - - const output = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`); - const proc = spawn(YT_DLP, [...ARGS_BASE, "--output", output, url]); - let stdout = ""; - - proc.on("error", err => reject(new Error( - err.code === "EACCES" - ? "Sem permissão para executar o yt-dlp. Rode: chmod +x ./bin/yt-dlp" - : err.code === "ENOENT" - ? "yt-dlp não encontrado em ./bin/yt-dlp" - : `Erro ao iniciar o yt-dlp: ${err.message}` - ))); - - proc.stdout.on("data", d => { stdout += d.toString(); }); - proc.stderr.on("data", d => logStream.write(d)); - - 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." - )); - - const filePath = stdout.trim().split("\n").filter(Boolean).at(-1); - if (!filePath || !fs.existsSync(filePath)) return reject(new Error( - "Download concluído mas arquivo não encontrado. Tente novamente." - )); - - resolve(filePath); - }); - }); -} - -export default async function ({ msg, api }) { - if (!msg.is(CMD_PREFIX + "video")) return; - - const url = msg.args[1]; - - if (!url) { - await msg.reply(`❌ Você precisa informar um link.\n\nExemplo: \`${CMD_PREFIX}video https://youtube.com/...\``); - return; - } - - await msg.reply("⏳ Baixando o vídeo, aguarde..."); - - enqueue( - async () => { - const filePath = await downloadVideo(url, `video-${Date.now()}`); - await api.sendVideo(filePath); - fs.unlinkSync(filePath); - emptyFolder(DOWNLOADS_DIR); - api.log.info(`${CMD_PREFIX}video concluído → ${url}`); - }, - async () => { - await msg.reply( - "❌ Não consegui baixar o vídeo.\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." - ); - } - ); -} \ No newline at end of file diff --git a/src/plugins/xp/index.js b/src/plugins/xp/index.js deleted file mode 100644 index 4d398cd..0000000 --- a/src/plugins/xp/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Ideia: - * - * Quando esse plugin for chamado, vai salvar o id de quem mandou no banco de dados. - * Quando esse id mandar mensagem de novo, o plugin vai "se lembrar" dessa pessoa e contar xp com: - * - * - Número de mensagens a cada 30s > conta 1 ponto cada mensagem sendo de texto ou de audio. Durante o intervalo de 30s ele não conta nada. - * - Tipo da mensagem: - * - Texto/Audio: multiplicar por 1 - * - Vídeo/Foto: multiplcar por 2 - * - * - Aculma karma dependendo da mensagem. Se suas mensagens conterem palavrões frequentes ou muito spam (ex. 5 mensagens/s), seu karma diminui: - * - Karma negativo (abaixo de 0): divide pontos de xp por 2 - * - Karma baixo (10-20): multplica pontos por 1 - * - Karma médio (30-40): multiplica pontos por 2 - * - Karma alto (50-80): multiplca pontos por 3 - * - * No final de cada mês, esse plugin organiza uma lista com o ranking dos top 10 com maiores XP do mês e manda em ID (chat). - * - * Esse plugin é a base para fazer um sistema de economia daqui um tempo. -*/ \ No newline at end of file