← back to browse
goat command

meta

No description

0
views
0
likes
0
installs

Aliases: metaai, metagen, metaedit

raw source
const axios = require('axios');
const fs = require('fs-extra');
const path = require('path');
const { createCanvas, loadImage } = require('canvas');

const BASE_API = "https://metabyneokex.vercel.app/photos";

async function downloadImage(url, tempDir, filename) {
    const tempFilePath = path.join(tempDir, filename);
    try {
        const response = await axios({
            method: 'get',
            url: url,
            responseType: 'arraybuffer',
            timeout: 60000
        });
        await fs.writeFile(tempFilePath, response.data);
        return tempFilePath;
    } catch (e) {
        if (fs.existsSync(tempFilePath)) fs.unlinkSync(tempFilePath);
        throw new Error(`Failed to download image: ${e.message}`);
    }
}

async function createGridImage(imagePaths, outputPath) {
    const images = await Promise.all(imagePaths.map(p => loadImage(p)));
    const imgWidth = images[0].width;
    const imgHeight = images[0].height;
    const padding = 10;
    const numberSize = 40;

    const canvasWidth = (imgWidth * 2) + (padding * 3);
    const canvasHeight = (imgHeight * 2) + (padding * 3);

    const canvas = createCanvas(canvasWidth, canvasHeight);
    const ctx = canvas.getContext('2d');

    ctx.fillStyle = '#1a1a2e';
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    const positions = [
        { x: padding, y: padding },
        { x: imgWidth + (padding * 2), y: padding },
        { x: padding, y: imgHeight + (padding * 2) },
        { x: imgWidth + (padding * 2), y: imgHeight + (padding * 2) }
    ];

    for (let i = 0; i < images.length && i < 4; i++) {
        const { x, y } = positions[i];
        ctx.drawImage(images[i], x, y, imgWidth, imgHeight);
        ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
        ctx.beginPath();
        ctx.arc(x + numberSize, y + numberSize, numberSize - 5, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#ffffff';
        ctx.font = 'bold 28px Arial';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText((i + 1).toString(), x + numberSize, y + numberSize);
    }

    const buffer = canvas.toBuffer('image/png');
    await fs.writeFile(outputPath, buffer);
    return outputPath;
}

module.exports = {
    config: {
        name: "meta",
        aliases: ["metaai", "metagen", "metaedit"],
        version: "3.0",
        author: "Neoaz ゐ",
        countDown: 20,
        role: 0,
        longDescription: "Generate or edit images using Meta AI with grid preview and selection.",
        category: "ai-image",
        guide: {
            en: "To generate: {pn} <prompt> [--ar 16:9 | 9:16]\nTo edit: Reply to an image with {pn} <edit instructions>"
        }
    },

    onStart: async function({ message, args, event, api, commandName }) {
        let fullPrompt = args.join(" ");
        const cacheDir = path.join(__dirname, 'cache');
        if (!fs.existsSync(cacheDir)) await fs.mkdirp(cacheDir);

        const isEdit = event.type === "message_reply" && event.messageReply.attachments && event.messageReply.attachments[0].type === "photo";

        if (isEdit) {
            if (!fullPrompt) return message.reply("Please provide instructions on how to edit the image.");
            const imgUrl = event.messageReply.attachments[0].url;
            message.reaction("⏳", event.messageID);

            try {
                const response = await axios.get(`${BASE_API}/edit`, {
                    params: { img_url: imgUrl, prompt: fullPrompt },
                    timeout: 180000
                });

                const data = response.data;
                if (!data.success || !data.image_urls) throw new Error("Edit failed.");

                const editedImgPath = await downloadImage(data.image_urls[0], cacheDir, `meta_edit_${Date.now()}.png`);

                await message.reply({
                    body: `✅ Image edited`,
                    attachment: fs.createReadStream(editedImgPath)
                });
                
                message.reaction("✅", event.messageID);
                setTimeout(() => fs.unlinkSync(editedImgPath), 10000);
            } catch (error) {
                message.reaction("❌", event.messageID);
                message.reply("Error: " + error.message);
            }
            return;
        }

        if (!fullPrompt) return message.reply("Please provide a prompt.");

        let orientation = "SQUARE";
        if (fullPrompt.includes("--ar 16:9")) {
            orientation = "LANDSCAPE";
            fullPrompt = fullPrompt.replace("--ar 16:9", "");
        } else if (fullPrompt.includes("--ar 9:16")) {
            orientation = "VERTICAL";
            fullPrompt = fullPrompt.replace("--ar 9:16", "");
        }

        const prompt = fullPrompt.trim();
        message.reaction("⏳", event.messageID);

        try {
            const response = await axios.get(`${BASE_API}/generate`, {
                params: { prompt, orientation, num_images: 4 },
                timeout: 180000
            });

            const data = response.data;
            if (!data.success || !data.image_urls) throw new Error("Generation failed.");

            const imageUrls = data.image_urls.slice(0, 4);
            const tempPaths = [];

            for (let i = 0; i < imageUrls.length; i++) {
                const imgPath = await downloadImage(imageUrls[i], cacheDir, `meta_${Date.now()}_${i + 1}.png`);
                tempPaths.push(imgPath);
            }

            const gridPath = path.join(cacheDir, `meta_grid_${Date.now()}.png`);
            await createGridImage(tempPaths, gridPath);

            message.reply({
                body: `📐 Ratio: ${orientation}\n\n📷 Reply with 1-4 to select, or "all" to get all.`,
                attachment: fs.createReadStream(gridPath)
            }, (err, info) => {
                if (!err) {
                    global.GoatBot.onReply.set(info.messageID, {
                        commandName,
                        messageID: info.messageID,
                        author: event.senderID,
                        imageUrls,
                        tempPaths,
                        gridPath
                    });
                }
            });

            message.reaction("✅", event.messageID);
        } catch (error) {
            message.reaction("❌", event.messageID);
            message.reply("Error: " + error.message);
        }
    },

    onReply: async function({ message, event, Reply }) {
        if (event.senderID !== Reply.author) return;

        const userReply = event.body.trim().toLowerCase();
        const cacheDir = path.join(__dirname, 'cache');
        const selectedPaths = [];

        try {
            message.reaction("⏳", event.messageID);

            if (userReply === 'all') {
                for (let i = 0; i < Reply.imageUrls.length; i++) {
                    const imgPath = await downloadImage(Reply.imageUrls[i], cacheDir, `meta_all_${Date.now()}_${i}.png`);
                    selectedPaths.push(imgPath);
                }
                await message.reply({ body: "All images:", attachment: selectedPaths.map(p => fs.createReadStream(p)) });
            } else {
                const i = parseInt(userReply) - 1;
                if (i >= 0 && i < Reply.imageUrls.length) {
                    const imgPath = await downloadImage(Reply.imageUrls[i], cacheDir, `meta_one_${Date.now()}.png`);
                    selectedPaths.push(imgPath);
                    await message.reply({ body: `Image ${i + 1}:`, attachment: fs.createReadStream(imgPath) });
                } else {
                    return message.reaction("❓", event.messageID);
                }
            }
            message.reaction("✅", event.messageID);
        } catch (e) {
            message.reply("Error: " + e.message);
        } finally {
            setTimeout(async () => {
                for (const p of selectedPaths) {
                    if (fs.existsSync(p)) await fs.unlink(p);
                }
                if (Reply.tempPaths) {
                    for (const p of Reply.tempPaths) {
                        if (fs.existsSync(p)) await fs.unlink(p);
                    }
                }
                if (Reply.gridPath && fs.existsSync(Reply.gridPath)) await fs.unlink(Reply.gridPath);
            }, 5000);
        }
    }
};

View raw file