first commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# manyplug-repo
|
||||
|
||||
This is a mirror of Freakkdev ManyBot's plugin repository (ManyPlug).
|
||||
|
||||
If you want to publish a plugin, you can make a pull request or send a Git patch to the email: `manybot@pm.me`.
|
||||
9
a/index.js
Normal file
9
a/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { hangmanActive } from "../forca/index.js";
|
||||
|
||||
export default async function ({ msg }) {
|
||||
if (msg.body.trim().toLowerCase() !== "a") return;
|
||||
if (msg.args.length > 1) return;
|
||||
if (hangmanActive) return;
|
||||
|
||||
await msg.reply("B!");
|
||||
}
|
||||
8
a/manyplug.json
Normal file
8
a/manyplug.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "a",
|
||||
"author": "freakk.dev",
|
||||
"version": "1.0.0",
|
||||
"category": "humor",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
77
adivinhação/index.js
Normal file
77
adivinhação/index.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* plugins/adivinhacao/index.js
|
||||
*
|
||||
* Game state lives here — isolated in the plugin.
|
||||
* Multiple groups can play simultaneously without conflict.
|
||||
*/
|
||||
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
const RANGE = { min: 1, max: 100 };
|
||||
const jogosAtivos = new Map();
|
||||
|
||||
const sorteio = () =>
|
||||
Math.floor(Math.random() * (RANGE.max - RANGE.min + 1)) + RANGE.min;
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
const chatId = api.chat.id;
|
||||
|
||||
// ── !adivinhação ─────────────────────────────────────────
|
||||
if (msg.is(CMD_PREFIX + "adivinhação")) {
|
||||
const sub = msg.args[1];
|
||||
|
||||
if (!sub) {
|
||||
await api.send(
|
||||
`${t("title")}\n\n` +
|
||||
`\`${CMD_PREFIX}adivinhação começar\` — ${t("startCommand")}\n` +
|
||||
`\`${CMD_PREFIX}adivinhação parar\` — ${t("stopCommand")}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === "começar") {
|
||||
jogosAtivos.set(chatId, sorteio());
|
||||
await api.send(t("started"));
|
||||
api.log.info(t("gameLog.started"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === "parar") {
|
||||
jogosAtivos.delete(chatId);
|
||||
await api.send(t("stopped"));
|
||||
api.log.info(t("gameLog.stopped"));
|
||||
return;
|
||||
}
|
||||
|
||||
await api.send(
|
||||
`${t("invalidCommand", { sub })} \`${CMD_PREFIX}adivinhação começar\` ${t("or")} \`${CMD_PREFIX}adivinhação parar\`.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Guesses during active game ────────────────────────────
|
||||
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(t("range", { min: RANGE.min, max: RANGE.max }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (num === numero) {
|
||||
await msg.reply(
|
||||
`${t("correct", { number: numero })} \`${CMD_PREFIX}adivinhação começar\` ${t("playAgain")}`
|
||||
);
|
||||
jogosAtivos.delete(chatId);
|
||||
} else {
|
||||
await api.send(num > numero ? t("lower") : t("higher"));
|
||||
}
|
||||
}
|
||||
18
adivinhação/locale/en.json
Normal file
18
adivinhação/locale/en.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"title": "🎲 Guessing Game",
|
||||
"startCommand": "Start a new game",
|
||||
"stopCommand": "Stop the current game",
|
||||
"started": "Game started! Guess a number between 1 and 100.",
|
||||
"stopped": "Game stopped.",
|
||||
"invalidCommand": "Unknown subcommand: {{sub}}. Use",
|
||||
"or": "or",
|
||||
"range": "The number must be between {{min}} and {{max}}.",
|
||||
"correct": "🎉 Correct! The number was {{number}}. Type",
|
||||
"playAgain": "to play again.",
|
||||
"higher": "📈 Higher!",
|
||||
"lower": "📉 Lower!",
|
||||
"gameLog": {
|
||||
"started": "Guessing game started.",
|
||||
"stopped": "Guessing game stopped."
|
||||
}
|
||||
}
|
||||
18
adivinhação/locale/es.json
Normal file
18
adivinhação/locale/es.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"title": "🎲 Juego de Adivinanza",
|
||||
"startCommand": "Iniciar un nuevo juego",
|
||||
"stopCommand": "Detener el juego actual",
|
||||
"started": "¡Juego iniciado! Adivina un número entre 1 y 100.",
|
||||
"stopped": "Juego detenido.",
|
||||
"invalidCommand": "Subcomando desconocido: {{sub}}. Usa",
|
||||
"or": "o",
|
||||
"range": "El número debe estar entre {{min}} y {{max}}.",
|
||||
"correct": "🎉 ¡Correcto! El número era {{number}}. Escribe",
|
||||
"playAgain": "para jugar de nuevo.",
|
||||
"higher": "📈 ¡Mayor!",
|
||||
"lower": "📉 ¡Menor!",
|
||||
"gameLog": {
|
||||
"started": "Juego de adivinanza iniciado.",
|
||||
"stopped": "Juego de adivinanza detenido."
|
||||
}
|
||||
}
|
||||
18
adivinhação/locale/pt.json
Normal file
18
adivinhação/locale/pt.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"title": "🎲 Jogo de Adivinhação",
|
||||
"startCommand": "Iniciar um novo jogo",
|
||||
"stopCommand": "Parar o jogo atual",
|
||||
"started": "Jogo iniciado! Adivinhe um número entre 1 e 100.",
|
||||
"stopped": "Jogo parado.",
|
||||
"invalidCommand": "Subcomando desconhecido: {{sub}}. Use",
|
||||
"or": "ou",
|
||||
"range": "O número deve estar entre {{min}} e {{max}}.",
|
||||
"correct": "🎉 Correto! O número era {{number}}. Digite",
|
||||
"playAgain": "para jogar novamente.",
|
||||
"higher": "📈 Maior!",
|
||||
"lower": "📉 Menor!",
|
||||
"gameLog": {
|
||||
"started": "Jogo de adivinhação iniciado.",
|
||||
"stopped": "Jogo de adivinhação parado."
|
||||
}
|
||||
}
|
||||
7
adivinhação/manyplug.json
Normal file
7
adivinhação/manyplug.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "adivinhacao",
|
||||
"version": "1.0.0",
|
||||
"category": "games",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
116
audio/index.js
Normal file
116
audio/index.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* plugins/audio/index.js
|
||||
*
|
||||
* Downloads video via yt-dlp, converts to mp3 via ffmpeg and sends to chat.
|
||||
* All processing (download + conversion + send + cleanup) is here.
|
||||
*/
|
||||
|
||||
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";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
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" ? t("error.noPermission")
|
||||
: err.code === "ENOENT" ? t("error.notFound")
|
||||
: `${t("error.startError")} ${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(t("error.downloadFailed")));
|
||||
|
||||
const filePath = stdout.trim().split("\n").filter(Boolean).at(-1);
|
||||
if (!filePath || !fs.existsSync(filePath))
|
||||
return reject(new Error(t("error.fileNotFound")));
|
||||
|
||||
resolve(filePath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function convertToMp3(videoPath, id) {
|
||||
const mp3Path = path.join(DOWNLOADS_DIR, `${id}.mp3`);
|
||||
|
||||
await execFileAsync(FFMPEG, [
|
||||
"-i", videoPath,
|
||||
"-vn", // no video
|
||||
"-ar", "44100", // sample rate
|
||||
"-ac", "2", // stereo
|
||||
"-b:a", "192k", // bitrate
|
||||
"-y", // overwrite if exists
|
||||
mp3Path,
|
||||
]);
|
||||
|
||||
fs.unlinkSync(videoPath); // remove intermediate video
|
||||
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(`${t("noUrl")} \`${CMD_PREFIX}audio https://youtube.com/...\``);
|
||||
return;
|
||||
}
|
||||
|
||||
await msg.reply(t("downloading"));
|
||||
|
||||
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 completed → ${url}`);
|
||||
},
|
||||
async () => {
|
||||
await msg.reply(t("error.generic"));
|
||||
}
|
||||
);
|
||||
}
|
||||
12
audio/locale/en.json
Normal file
12
audio/locale/en.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"noUrl": "Please provide a URL.",
|
||||
"downloading": "Downloading audio...",
|
||||
"error": {
|
||||
"noPermission": "yt-dlp: permission denied",
|
||||
"notFound": "yt-dlp: not found",
|
||||
"startError": "Failed to start download:",
|
||||
"downloadFailed": "Download failed. Check the URL.",
|
||||
"fileNotFound": "Downloaded file not found.",
|
||||
"generic": "Failed to download audio. Try again later."
|
||||
}
|
||||
}
|
||||
12
audio/locale/es.json
Normal file
12
audio/locale/es.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"noUrl": "Por favor, proporciona una URL.",
|
||||
"downloading": "Descargando audio...",
|
||||
"error": {
|
||||
"noPermission": "yt-dlp: permiso denegado",
|
||||
"notFound": "yt-dlp: no encontrado",
|
||||
"startError": "Error al iniciar descarga:",
|
||||
"downloadFailed": "Descarga fallida. Verifica la URL.",
|
||||
"fileNotFound": "Archivo descargado no encontrado.",
|
||||
"generic": "Error al descargar audio. Intenta de nuevo más tarde."
|
||||
}
|
||||
}
|
||||
12
audio/locale/pt.json
Normal file
12
audio/locale/pt.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"noUrl": "Por favor, forneça uma URL.",
|
||||
"downloading": "Baixando áudio...",
|
||||
"error": {
|
||||
"noPermission": "yt-dlp: permissão negada",
|
||||
"notFound": "yt-dlp: não encontrado",
|
||||
"startError": "Falha ao iniciar download:",
|
||||
"downloadFailed": "Download falhou. Verifique a URL.",
|
||||
"fileNotFound": "Arquivo baixado não encontrado.",
|
||||
"generic": "Falha ao baixar áudio. Tente novamente mais tarde."
|
||||
}
|
||||
}
|
||||
7
audio/manyplug.json
Normal file
7
audio/manyplug.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "audio",
|
||||
"version": "1.0.0",
|
||||
"category": "media",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
250
figurinha/index.js
Normal file
250
figurinha/index.js
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* plugins/figurinha/index.js
|
||||
*
|
||||
* Usage modes:
|
||||
* command + attached media → creates 1 sticker directly
|
||||
* command + replying to media → creates 1 sticker directly
|
||||
* command + attached media + replying → creates 2 stickers directly
|
||||
* command (no media) → opens session
|
||||
* command create (with open session) → processes session media
|
||||
*/
|
||||
|
||||
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";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────
|
||||
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 getHelp = () =>
|
||||
`${t("help")} \`${CMD_PREFIX}figurinha\` ${t("helpMedia")} \`${CMD_PREFIX}figurinha\` ${t("helpSession")} \`${CMD_PREFIX}figurinha criar\` ${t("helpCreate")}`;
|
||||
|
||||
// ── Internal state ───────────────────────────────────────────
|
||||
// { chatId → { author, medias[], timeout } }
|
||||
const sessions = new Map();
|
||||
|
||||
// ── Conversion ────────────────────────────────────────────────
|
||||
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: t("pack"),
|
||||
author: t("author"),
|
||||
type: isAnimated ? "FULL" : "STATIC",
|
||||
categories: ["🤖"],
|
||||
quality,
|
||||
});
|
||||
if (buf.length <= MAX_STICKER_SIZE) return buf;
|
||||
}
|
||||
throw new Error(t("error.tooLarge"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(`Sticker generation error: ${err.message}`);
|
||||
await msg.reply(t("error.generic"));
|
||||
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;
|
||||
}
|
||||
|
||||
const sub = msg.args[1];
|
||||
// ── figurinha parar ──────────────────────────────────────
|
||||
if (sub === "parar") {
|
||||
const session = sessions.get(chatId);
|
||||
if (!session) {
|
||||
await msg.reply(`${t("session.noneActive")}\n\n${getHelp()}`);
|
||||
return;
|
||||
}
|
||||
clearTimeout(session.timeout);
|
||||
sessions.delete(chatId);
|
||||
|
||||
await msg.reply(t("session.stopped"));
|
||||
return;
|
||||
}
|
||||
|
||||
// ── figurinha criar ──────────────────────────────────────
|
||||
|
||||
if (sub === "criar") {
|
||||
const session = sessions.get(chatId);
|
||||
|
||||
if (!session) {
|
||||
await msg.reply(`${t("session.noneActive")}\n\n${getHelp()}`);
|
||||
return;
|
||||
}
|
||||
if (!session.medias.length) {
|
||||
await msg.reply(`${t("session.noMedia")}\n\n${getHelp()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(session.timeout);
|
||||
await msg.reply(t("session.generating"));
|
||||
|
||||
for (const { media, isGif } of session.medias) {
|
||||
await processarUmaMedia(media, isGif, api, msg);
|
||||
}
|
||||
|
||||
await msg.reply(t("session.success"));
|
||||
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(t("session.generatingOne"));
|
||||
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(
|
||||
`${t("session.alreadyOpen")} \`${CMD_PREFIX}figurinha criar\`.\n` +
|
||||
t("session.waitExpire")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = setTimeout(async () => {
|
||||
sessions.delete(chatId);
|
||||
try {
|
||||
await msg.reply(
|
||||
`${t("session.expired")} \`${CMD_PREFIX}figurinha\` ${t("session.expiredEnd")}`
|
||||
);
|
||||
} catch { }
|
||||
}, SESSION_TIMEOUT);
|
||||
|
||||
sessions.set(chatId, { author: msg.sender, medias: [], timeout });
|
||||
await msg.reply(`${t("session.started")} *${msg.senderName}*!\n\n${getHelp()}`);
|
||||
}
|
||||
25
figurinha/locale/en.json
Normal file
25
figurinha/locale/en.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"pack": "ManyBot Stickers",
|
||||
"author": "ManyBot",
|
||||
"help": "Convert images/videos to stickers.",
|
||||
"helpMedia": "Reply to or attach an image/video with",
|
||||
"helpSession": "Or start a multi-media session with",
|
||||
"helpCreate": "to process all at once.",
|
||||
"error": {
|
||||
"tooLarge": "Could not reduce sticker below 900KB.",
|
||||
"generic": "Failed to create sticker. Try another file."
|
||||
},
|
||||
"session": {
|
||||
"noneActive": "No active sticker session.",
|
||||
"stopped": "Sticker session ended.",
|
||||
"noMedia": "No media collected yet.",
|
||||
"generating": "Creating stickers...",
|
||||
"generatingOne": "Creating sticker...",
|
||||
"alreadyOpen": "Session already open.",
|
||||
"waitExpire": "Wait for it to expire or use",
|
||||
"expired": "Session expired.",
|
||||
"expiredEnd": "to start a new one.",
|
||||
"started": "Sticker session started by",
|
||||
"success": "All stickers created!"
|
||||
}
|
||||
}
|
||||
25
figurinha/locale/es.json
Normal file
25
figurinha/locale/es.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"pack": "ManyBot Stickers",
|
||||
"author": "ManyBot",
|
||||
"help": "Convierte imágenes/videos en stickers.",
|
||||
"helpMedia": "Responde o adjunta una imagen/video con",
|
||||
"helpSession": "O inicia una sesión multi-media con",
|
||||
"helpCreate": "para procesar todo de una vez.",
|
||||
"error": {
|
||||
"tooLarge": "No se pudo reducir el sticker debajo de 900KB.",
|
||||
"generic": "Error al crear sticker. Intenta con otro archivo."
|
||||
},
|
||||
"session": {
|
||||
"noneActive": "No hay sesión de sticker activa.",
|
||||
"stopped": "Sesión de sticker finalizada.",
|
||||
"noMedia": "No se ha recolectado ningún medio aún.",
|
||||
"generating": "Creando stickers...",
|
||||
"generatingOne": "Creando sticker...",
|
||||
"alreadyOpen": "Sesión ya abierta.",
|
||||
"waitExpire": "Espera a que expire o usa",
|
||||
"expired": "Sesión expirada.",
|
||||
"expiredEnd": "para iniciar una nueva.",
|
||||
"started": "Sesión de sticker iniciada por",
|
||||
"success": "¡Todos los stickers creados!"
|
||||
}
|
||||
}
|
||||
25
figurinha/locale/pt.json
Normal file
25
figurinha/locale/pt.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"pack": "ManyBot Stickers",
|
||||
"author": "ManyBot",
|
||||
"help": "Converte imagens/vídeos em figurinhas.",
|
||||
"helpMedia": "Responda ou anexe uma imagem/vídeo com",
|
||||
"helpSession": "Ou inicie uma sessão multi-mídia com",
|
||||
"helpCreate": "para processar tudo de uma vez.",
|
||||
"error": {
|
||||
"tooLarge": "Não foi possível reduzir figurinha abaixo de 900KB.",
|
||||
"generic": "Falha ao criar figurinha. Tente outro arquivo."
|
||||
},
|
||||
"session": {
|
||||
"noneActive": "Nenhuma sessão de figurinha ativa.",
|
||||
"stopped": "Sessão de figurinha encerrada.",
|
||||
"noMedia": "Nenhuma mídia coletada ainda.",
|
||||
"generating": "Criando figurinhas...",
|
||||
"generatingOne": "Criando figurinha...",
|
||||
"alreadyOpen": "Sessão já aberta.",
|
||||
"waitExpire": "Aguarde expirar ou use",
|
||||
"expired": "Sessão expirada.",
|
||||
"expiredEnd": "para iniciar uma nova.",
|
||||
"started": "Sessão de figurinha iniciada por",
|
||||
"success": "Todas as figurinhas criadas!"
|
||||
}
|
||||
}
|
||||
9
figurinha/manyplug.json
Normal file
9
figurinha/manyplug.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "figurinha",
|
||||
"version": "1.0.0",
|
||||
"category": "media",
|
||||
"service": false,
|
||||
"dependencies": {
|
||||
"wa-sticker-formatter": "*"
|
||||
}
|
||||
}
|
||||
160
forca/index.js
Normal file
160
forca/index.js
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* plugins/forca/index.js
|
||||
*
|
||||
* Hangman game plugin with isolated i18n.
|
||||
* Game state is stored internally per chat.
|
||||
*/
|
||||
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
// Game states
|
||||
const activeGames = new Map(); // chatId -> { word, theme, lives, progress }
|
||||
const activeParticipants = new Map(); // chatId -> Set of users who reacted
|
||||
export let hangmanActive = false;
|
||||
|
||||
|
||||
// Sample words
|
||||
const WORDS = [
|
||||
{ word: "python", theme: "Programming Language" },
|
||||
{ word: "javascript", theme: "Programming Language" },
|
||||
{ word: "java", theme: "Programming Language" },
|
||||
{ word: "dog", theme: "Animal" },
|
||||
{ word: "cat", theme: "Animal" },
|
||||
{ word: "elephant", theme: "Animal" },
|
||||
{ word: "giraffe", theme: "Animal" },
|
||||
{ word: "guitar", theme: "Musical Instrument" },
|
||||
{ word: "piano", theme: "Musical Instrument" },
|
||||
{ word: "drums", theme: "Musical Instrument" },
|
||||
{ word: "violin", theme: "Musical Instrument" },
|
||||
{ word: "soccer", theme: "Sport" },
|
||||
{ word: "basketball", theme: "Sport" },
|
||||
{ word: "swimming", theme: "Sport" },
|
||||
{ word: "tennis", theme: "Sport" },
|
||||
{ word: "brazil", theme: "Country" },
|
||||
{ word: "japan", theme: "Country" },
|
||||
{ word: "canada", theme: "Country" },
|
||||
{ word: "france", theme: "Country" },
|
||||
{ word: "mars", theme: "Planet" },
|
||||
{ word: "venus", theme: "Planet" },
|
||||
{ word: "jupiter", theme: "Planet" },
|
||||
{ word: "saturn", theme: "Planet" },
|
||||
{ word: "minecraft", theme: "Game" },
|
||||
{ word: "fortnite", theme: "Game" },
|
||||
{ word: "roblox", theme: "Game" },
|
||||
{ word: "amongus", theme: "Game" },
|
||||
{ word: "rose", theme: "Flower" },
|
||||
{ word: "sunflower", theme: "Flower" },
|
||||
{ word: "tulip", theme: "Flower" },
|
||||
{ word: "orchid", theme: "Flower" },
|
||||
{ word: "scissors", theme: "Object" },
|
||||
{ word: "notebook", theme: "Object" },
|
||||
{ word: "computer", theme: "Object" },
|
||||
{ word: "phone", theme: "Object" },
|
||||
{ word: "moon", theme: "Celestial Body" },
|
||||
{ word: "sun", theme: "Celestial Body" },
|
||||
{ word: "star", theme: "Celestial Body" },
|
||||
{ word: "comet", theme: "Celestial Body" },
|
||||
{ word: "ocean", theme: "Nature" },
|
||||
{ word: "mountain", theme: "Nature" },
|
||||
];
|
||||
|
||||
// Generate word with underscores
|
||||
const generateProgress = word =>
|
||||
word.replace(/[a-zA-Z]/g, "_");
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
const chatId = api.chat.id;
|
||||
const sub = msg.args[1];
|
||||
|
||||
// ── Main game command
|
||||
if (msg.is(CMD_PREFIX + "forca")) {
|
||||
if (!sub) {
|
||||
await api.send(
|
||||
`${t("title")}\n\n` +
|
||||
`\`${CMD_PREFIX}forca start\` — ${t("startCommand")}\n` +
|
||||
`\`${CMD_PREFIX}forca stop\` — ${t("stopCommand")}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === "start") {
|
||||
hangmanActive = true;
|
||||
// Get random word
|
||||
const random = WORDS[Math.floor(Math.random() * WORDS.length)];
|
||||
|
||||
// Initialize game
|
||||
activeGames.set(chatId, {
|
||||
word: random.word.toLowerCase(),
|
||||
theme: random.theme,
|
||||
lives: 6,
|
||||
progress: generateProgress(random.word)
|
||||
});
|
||||
|
||||
activeParticipants.set(chatId, new Set()); // reset participants
|
||||
|
||||
await api.send(
|
||||
t("started", {
|
||||
theme: random.theme,
|
||||
word: generateProgress(random.word),
|
||||
lives: 6
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sub === "stop") {
|
||||
activeGames.delete(chatId);
|
||||
activeParticipants.delete(chatId);
|
||||
await api.send(t("stopped"));
|
||||
return;
|
||||
}
|
||||
|
||||
await api.send(
|
||||
`${t("invalidCommand", { sub })} \`${CMD_PREFIX}forca start\` ${t("or")} \`${CMD_PREFIX}forca stop\`.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Game attempts
|
||||
const game = activeGames.get(chatId);
|
||||
if (!game) return; // No active game
|
||||
|
||||
const attempt = msg.body.trim().toLowerCase();
|
||||
if (!/^[a-z]$/.test(attempt)) return; // single letters only
|
||||
|
||||
// Check if letter is in word
|
||||
let hit = false;
|
||||
let newProgress = game.progress.split("");
|
||||
for (let i = 0; i < game.word.length; i++) {
|
||||
if (game.word[i] === attempt) {
|
||||
newProgress[i] = attempt;
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
game.progress = newProgress.join("");
|
||||
|
||||
if (!hit) game.lives--;
|
||||
|
||||
// Feedback for group
|
||||
if (game.progress === game.word) {
|
||||
await msg.reply(t("won", { word: game.word }));
|
||||
activeGames.delete(chatId);
|
||||
activeParticipants.delete(chatId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.lives <= 0) {
|
||||
await msg.reply(t("lost", { word: game.word }));
|
||||
activeGames.delete(chatId);
|
||||
activeParticipants.delete(chatId);
|
||||
return;
|
||||
}
|
||||
|
||||
await msg.reply(
|
||||
`${t("status", { word: game.progress, lives: game.lives })}\n` +
|
||||
(hit ? t("correct") : t("wrong"))
|
||||
);
|
||||
}
|
||||
14
forca/locale/en.json
Normal file
14
forca/locale/en.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"title": "Hangman Game",
|
||||
"startCommand": "starts the game",
|
||||
"stopCommand": "stops the game",
|
||||
"started": "Hangman game started!\n\nTheme: *{{theme}}*\nWord: `{{word}}`\nLives: {{lives}}\n\nType a letter to guess!",
|
||||
"stopped": "Hangman game ended.",
|
||||
"invalidCommand": "Subcommand *{{sub}}* does not exist.\nUse",
|
||||
"or": "or",
|
||||
"won": "Congratulations! Complete word: `{{word}}`",
|
||||
"lost": "Game over! Word was: `{{word}}`",
|
||||
"status": "Word: `{{word}}`\nLives: {{lives}}",
|
||||
"correct": "Got the letter!",
|
||||
"wrong": "Wrong letter!"
|
||||
}
|
||||
14
forca/locale/es.json
Normal file
14
forca/locale/es.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"title": "Juego del Ahorcado",
|
||||
"startCommand": "inicia el juego",
|
||||
"stopCommand": "detiene el juego",
|
||||
"started": "¡Juego del Ahorcado iniciado!\n\nTema: *{{theme}}*\nPalabra: `{{word}}`\nVidas: {{lives}}\n\nEscribe una letra para adivinar!",
|
||||
"stopped": "Juego del Ahorcado terminado.",
|
||||
"invalidCommand": "El subcomando *{{sub}}* no existe.\nUsa",
|
||||
"or": "o",
|
||||
"won": "¡Felicitaciones! Palabra completa: `{{word}}`",
|
||||
"lost": "¡Fin del juego! La palabra era: `{{word}}`",
|
||||
"status": "Palabra: `{{word}}`\nVidas: {{lives}}",
|
||||
"correct": "¡Letra correcta!",
|
||||
"wrong": "¡Letra incorrecta!"
|
||||
}
|
||||
14
forca/locale/pt.json
Normal file
14
forca/locale/pt.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"title": "Jogo da Forca",
|
||||
"startCommand": "inicia o jogo",
|
||||
"stopCommand": "encerra o jogo",
|
||||
"started": "Jogo da Forca iniciado!\n\nTema: *{{theme}}*\nPalavra: `{{word}}`\nVidas: {{lives}}\n\nDigite uma letra para adivinhar!",
|
||||
"stopped": "Jogo da Forca encerrado.",
|
||||
"invalidCommand": "Subcomando *{{sub}}* não existe.\nUse",
|
||||
"or": "ou",
|
||||
"won": "Parabéns! Palavra completa: `{{word}}`",
|
||||
"lost": "Fim de jogo! Palavra era: `{{word}}`",
|
||||
"status": "Palavra: `{{word}}`\nVidas: {{lives}}",
|
||||
"correct": "Acertou a letra!",
|
||||
"wrong": "Errou a letra!"
|
||||
}
|
||||
7
forca/manyplug.json
Normal file
7
forca/manyplug.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "forca",
|
||||
"version": "1.0.0",
|
||||
"category": "games",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
74
manager.js
Normal file
74
manager.js
Normal file
@@ -0,0 +1,74 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
let regPath = path.resolve('registry.json');
|
||||
|
||||
const entries = await fs.readdir(".", { withFileTypes: true }); // all files and directories of plugins dir
|
||||
|
||||
let update = [];
|
||||
let added = [];
|
||||
|
||||
// Load existing registry or create new one
|
||||
let registry;
|
||||
if (fs.existsSync(regPath)) {
|
||||
try {
|
||||
registry = JSON.parse(fs.readFileSync(regPath, "utf-8"));
|
||||
} catch (err) {
|
||||
registry = {
|
||||
lastUpdated: new Date().toISOString(),
|
||||
plugins: {}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
registry = {
|
||||
lastUpdated: new Date().toISOString(),
|
||||
plugins: {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue; // if it is not a directory, skip and continue the next loop
|
||||
|
||||
const manifestPath = path.join(".", entry.name, 'manyplug.json');
|
||||
if (!await fs.pathExists(manifestPath)) continue;
|
||||
|
||||
try {
|
||||
const manifest = await fs.readJson(manifestPath); // manifest = data from manyplug.json
|
||||
const pluginName = manifest.name || entry.name;
|
||||
|
||||
// Check if plugin exists in registry and version changed
|
||||
const existing = registry.plugins[pluginName]; // existing = plugin in registry.json
|
||||
if (!existing) {
|
||||
added.push({
|
||||
name: pluginName,
|
||||
version: manifest.version
|
||||
});
|
||||
registry.plugins[pluginName] = manifest;
|
||||
} else if (existing.version !== manifest.version) {
|
||||
const oldVersion = existing.version;
|
||||
existing.version = manifest.version;
|
||||
|
||||
update.push({
|
||||
name: pluginName,
|
||||
oldVersion: oldVersion,
|
||||
newVersion: existing.version
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(chalk.yellow(`⚠️ Failed to read ${entry.name}: ${err.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
// Update timestamp
|
||||
registry.lastUpdated = new Date().toISOString();
|
||||
|
||||
await fs.writeJson(regPath, registry, { spaces: 2 });
|
||||
|
||||
console.log(chalk.green(`Registry synced\n`));
|
||||
console.log(chalk.blue(` New plugins registred (${added.length}):`));
|
||||
console.log(chalk.blue(added.map(a => ` + ${a.name} (${a.version})`).join('\n')));
|
||||
|
||||
console.log(chalk.yellow(` Plugins updated (${update.length}):`));
|
||||
console.log(chalk.yellow(update.map(u => ` * ${u.name} (${u.oldVersion}) -> (${u.newVersion})`).join('\n')));
|
||||
17
many/index.js
Normal file
17
many/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "many")) return;
|
||||
|
||||
await api.send(
|
||||
`${t("title")}\n\n` +
|
||||
`🎬 \`${CMD_PREFIX}video <link>\` — ${t("video")}\n` +
|
||||
`🎵 \`${CMD_PREFIX}audio <link>\` — ${t("audio")}\n` +
|
||||
`🖼️ \`${CMD_PREFIX}figurinha\` — ${t("sticker")}\n` +
|
||||
`🎮 \`${CMD_PREFIX}adivinhação começar|parar\` — ${t("guess")}\n` +
|
||||
`🎮 \`${CMD_PREFIX}forca começar|parar\` — ${t("hangman")}\n`
|
||||
);
|
||||
}
|
||||
8
many/locale/en.json
Normal file
8
many/locale/en.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "🤖 ManyBot — Available Commands",
|
||||
"video": "Download video from URL",
|
||||
"audio": "Download audio (mp3) from URL",
|
||||
"sticker": "Convert image/video to sticker",
|
||||
"guess": "Guessing game (number 1-100)",
|
||||
"hangman": "Hangman game"
|
||||
}
|
||||
8
many/locale/es.json
Normal file
8
many/locale/es.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "🤖 ManyBot — Comandos disponibles",
|
||||
"video": "Descarga video de una URL",
|
||||
"audio": "Descarga audio (mp3) de una URL",
|
||||
"sticker": "Convierte imagen/video en sticker",
|
||||
"guess": "Juego de adivinanza (número 1-100)",
|
||||
"hangman": "Juego del ahorcado"
|
||||
}
|
||||
8
many/locale/pt.json
Normal file
8
many/locale/pt.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"title": "🤖 ManyBot — Comandos disponíveis",
|
||||
"video": "Baixa vídeo de uma URL",
|
||||
"audio": "Baixa áudio (mp3) de uma URL",
|
||||
"sticker": "Converte imagem/vídeo em figurinha",
|
||||
"guess": "Jogo de adivinhação (número 1-100)",
|
||||
"hangman": "Jogo da forca"
|
||||
}
|
||||
7
many/manyplug.json
Normal file
7
many/manyplug.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "many",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
12
obrigado/index.js
Normal file
12
obrigado/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
const triggers = ["obrigado", "valeu", "brigado", "obrigada", "thx", "thanks"];
|
||||
|
||||
export default async function ({ msg }) {
|
||||
if (!triggers.some(g => msg.is(CMD_PREFIX + g))) return;
|
||||
|
||||
await msg.reply(t("reply"));
|
||||
}
|
||||
3
obrigado/locale/en.json
Normal file
3
obrigado/locale/en.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reply": "You're welcome! 🤗"
|
||||
}
|
||||
3
obrigado/locale/es.json
Normal file
3
obrigado/locale/es.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reply": "¡De nada! 🤗"
|
||||
}
|
||||
3
obrigado/locale/pt.json
Normal file
3
obrigado/locale/pt.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"reply": "Por nada! Disponha! 🤗"
|
||||
}
|
||||
105
package-lock.json
generated
Normal file
105
package-lock.json
generated
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "manyplug-repo",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manyplug-repo",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"fs-extra": "^11.3.4",
|
||||
"path": "^0.12.7"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.3.4",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
|
||||
"integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/path": {
|
||||
"version": "0.12.7",
|
||||
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||
"integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"process": "^0.11.1",
|
||||
"util": "^0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "2.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "manyplug-repo",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a mirror of Freakkdev ManyBot's plugin repository (ManyPlug).",
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"main": "manager.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^5.6.2",
|
||||
"fs-extra": "^11.3.4",
|
||||
"path": "^0.12.7"
|
||||
}
|
||||
}
|
||||
64
registry.json
Normal file
64
registry.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"lastUpdated": "2026-04-15T00:07:30.616Z",
|
||||
"plugins": {
|
||||
"a": {
|
||||
"name": "a",
|
||||
"author": "freakk.dev",
|
||||
"version": "1.0.0",
|
||||
"category": "humor",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
},
|
||||
"adivinhacao": {
|
||||
"name": "adivinhacao",
|
||||
"version": "1.0.0",
|
||||
"category": "games",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
},
|
||||
"audio": {
|
||||
"name": "audio",
|
||||
"version": "1.0.0",
|
||||
"category": "media",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
},
|
||||
"figurinha": {
|
||||
"name": "figurinha",
|
||||
"version": "1.0.0",
|
||||
"category": "media",
|
||||
"service": false,
|
||||
"dependencies": {
|
||||
"wa-sticker-formatter": "*"
|
||||
}
|
||||
},
|
||||
"forca": {
|
||||
"name": "forca",
|
||||
"version": "1.0.0",
|
||||
"category": "games",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
},
|
||||
"many": {
|
||||
"name": "many",
|
||||
"version": "1.0.0",
|
||||
"category": "utility",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
},
|
||||
"video": {
|
||||
"name": "video",
|
||||
"version": "1.0.0",
|
||||
"category": "media",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
},
|
||||
"xp": {
|
||||
"name": "xp",
|
||||
"version": "0.1.0",
|
||||
"category": "social",
|
||||
"service": true,
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
video/index.js
Normal file
109
video/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* plugins/video/index.js
|
||||
*
|
||||
* Downloads video via yt-dlp and sends to chat.
|
||||
* All processing (download + send + cleanup) is here.
|
||||
*/
|
||||
|
||||
import { spawn } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import { enqueue } from "../../download/queue.js";
|
||||
import { CMD_PREFIX } from "../../config.js";
|
||||
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||
|
||||
const { t } = createPluginI18n(import.meta.url);
|
||||
|
||||
fs.mkdirSync("logs", { recursive: true });
|
||||
const logStream = fs.createWriteStream("logs/video-error.log", { flags: "a" });
|
||||
logStream.on("error", err => console.error("[logStream]", err));
|
||||
|
||||
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) => {
|
||||
// Isolated folder just for this download
|
||||
const tmpDir = path.join(DOWNLOADS_DIR, id);
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
|
||||
const output = path.join(tmpDir, "%(title).80s.%(ext)s");
|
||||
const proc = spawn(YT_DLP, [...ARGS_BASE, "--output", output, url]);
|
||||
let stdout = "";
|
||||
|
||||
proc.on("error", err => reject(new Error(
|
||||
err.code === "EACCES" ? t("error.noPermission")
|
||||
: err.code === "ENOENT" ? t("error.notFound")
|
||||
: `${t("error.startError")} ${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) {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
return reject(new Error(t("error.downloadFailed")));
|
||||
}
|
||||
|
||||
// Try stdout path first
|
||||
let filePath = stdout.trim().split("\n").filter(Boolean).at(-1);
|
||||
|
||||
// Fallback: get the single file inside the isolated folder
|
||||
if (!filePath || !fs.existsSync(filePath)) {
|
||||
const files = fs.readdirSync(tmpDir).filter(f => !f.endsWith(".part"));
|
||||
filePath = files.length === 1 ? path.join(tmpDir, files[0]) : null;
|
||||
}
|
||||
|
||||
if (!filePath) {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
return reject(new Error(t("error.fileNotFound")));
|
||||
}
|
||||
|
||||
resolve({ filePath, tmpDir });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default async function ({ msg, api }) {
|
||||
if (!msg.is(CMD_PREFIX + "video")) return;
|
||||
|
||||
const url = msg.args[1];
|
||||
|
||||
if (!url) {
|
||||
await msg.reply(`${t("noUrl")} \`${CMD_PREFIX}video https://youtube.com/...\``);
|
||||
return;
|
||||
}
|
||||
|
||||
await msg.reply(t("downloading"));
|
||||
|
||||
const id = `video-${Date.now()}`;
|
||||
|
||||
enqueue(
|
||||
async () => {
|
||||
const { filePath, tmpDir } = await downloadVideo(url, id);
|
||||
await api.sendVideo(filePath);
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
api.log.info(`${CMD_PREFIX}video completed → ${url}`);
|
||||
},
|
||||
async () => {
|
||||
await msg.reply(t("error.generic"));
|
||||
}
|
||||
);
|
||||
}
|
||||
12
video/locale/en.json
Normal file
12
video/locale/en.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"noUrl": "Please provide a URL.",
|
||||
"downloading": "Downloading video...",
|
||||
"error": {
|
||||
"noPermission": "yt-dlp: permission denied",
|
||||
"notFound": "yt-dlp: not found",
|
||||
"startError": "Failed to start download:",
|
||||
"downloadFailed": "Download failed. Check the URL.",
|
||||
"fileNotFound": "Downloaded file not found.",
|
||||
"generic": "Failed to download video. Try again later."
|
||||
}
|
||||
}
|
||||
12
video/locale/es.json
Normal file
12
video/locale/es.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"noUrl": "Por favor, proporciona una URL.",
|
||||
"downloading": "Descargando video...",
|
||||
"error": {
|
||||
"noPermission": "yt-dlp: permiso denegado",
|
||||
"notFound": "yt-dlp: no encontrado",
|
||||
"startError": "Error al iniciar descarga:",
|
||||
"downloadFailed": "Descarga fallida. Verifica la URL.",
|
||||
"fileNotFound": "Archivo descargado no encontrado.",
|
||||
"generic": "Error al descargar video. Intenta de nuevo más tarde."
|
||||
}
|
||||
}
|
||||
12
video/locale/pt.json
Normal file
12
video/locale/pt.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"noUrl": "Por favor, forneça uma URL.",
|
||||
"downloading": "Baixando vídeo...",
|
||||
"error": {
|
||||
"noPermission": "yt-dlp: permissão negada",
|
||||
"notFound": "yt-dlp: não encontrado",
|
||||
"startError": "Falha ao iniciar download:",
|
||||
"downloadFailed": "Download falhou. Verifique a URL.",
|
||||
"fileNotFound": "Arquivo baixado não encontrado.",
|
||||
"generic": "Falha ao baixar vídeo. Tente novamente mais tarde."
|
||||
}
|
||||
}
|
||||
7
video/manyplug.json
Normal file
7
video/manyplug.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "video",
|
||||
"version": "1.0.0",
|
||||
"category": "media",
|
||||
"service": false,
|
||||
"dependencies": {}
|
||||
}
|
||||
21
xp/index.js
Normal file
21
xp/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
7
xp/manyplug.json
Normal file
7
xp/manyplug.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "xp",
|
||||
"version": "0.1.0",
|
||||
"category": "social",
|
||||
"service": true,
|
||||
"dependencies": {}
|
||||
}
|
||||
Reference in New Issue
Block a user