remove embedded plugins - now managed via manyplug
This commit is contained in:
@@ -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!");
|
|
||||||
}
|
|
||||||
@@ -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*!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
@@ -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!")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 <link>\` — baixa um vídeo\n` +
|
|
||||||
`🎵 \`${CMD_PREFIX}audio <link>\` — 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`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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!");
|
|
||||||
}
|
|
||||||
@@ -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."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
*/
|
|
||||||
Reference in New Issue
Block a user