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