first commit
This commit is contained in:
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": "*"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user