← back to browse
goat command

midjourney

No description

0
views
0
likes
0
installs

Aliases: mj, imagine

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

const pipeline = promisify(stream.pipeline);
const API_ENDPOINT = "https://dev.oculux.xyz/api/mj-proxy-pub"; 

async function downloadSingleImage(url, tempDir, index) {
    let tempFilePath = '';
    try {
        const imageStreamResponse = await axios({
            method: 'get',
            url: url,
            responseType: 'arraybuffer',
            timeout: 120000 
        });

        tempFilePath = path.join(tempDir, `mj_single_${Date.now()}_${index}.jpg`);
        await fs.writeFile(tempFilePath, imageStreamResponse.data);

        return { path: tempFilePath };

    } catch (e) {
        if (tempFilePath && fs.existsSync(tempFilePath)) fs.unlinkSync(tempFilePath);
        throw new Error("Failed to download the image.");
    }
}

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: "midjourney",
    aliases: ["mj", "imagine"],
    version: "20.0",
    author: "NeoKEX",
    countDown: 20,
    role: 0,
    longDescription: "Generate 4 Midjourney images in a grid and select one or all by replying.",
    category: "ai-image",
    guide: {
      en: "{pn} <prompt>\n\nExample: {pn} a futuristic city at sunset\n\nAfter receiving the grid, reply with 1, 2, 3, 4 to select one image, or 'all' to get all images."
    }
  },

  onStart: async function({ message, args, event, commandName }) {
    let prompt = args.join(" ");
    const cacheDir = path.join(__dirname, 'cache');

    if (!fs.existsSync(cacheDir)) await fs.mkdirp(cacheDir);

    if (!prompt) {
      return message.reply("❌ Please provide a prompt to generate an image.");
    }

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

    const tempPaths = [];
    let gridPath = '';
    
    try {
      const cleanedPrompt = prompt.trim();
      
      const apiResponse = await axios.get(`${API_ENDPOINT}?prompt=${encodeURIComponent(cleanedPrompt)}&usepolling=false`, { timeout: 300000 }); 
      const data = apiResponse.data;

      if (!data.status || data.status === "failed" || !data.results || data.results.length < 4) {
        const errorDetail = data.message || "API did not return a successful status or enough images (expected 4).";
        throw new Error(`Generation failed: ${errorDetail}`);
      }
      
      const finalUrls = data.results.slice(0, 4); 

      for (let i = 0; i < finalUrls.length; i++) {
          const result = await downloadSingleImage(finalUrls[i], cacheDir, i + 1);
          tempPaths.push(result.path);
      }

      gridPath = path.join(cacheDir, `mj_grid_${Date.now()}.png`);
      await createGridImage(tempPaths, gridPath);
      
      message.reply({
        body: `✨ Midjourney generated 4 images\n\n📷 Reply with 1, 2, 3, 4 to select one image, or "all" to get all images.`,
        attachment: fs.createReadStream(gridPath)
      }, (err, info) => {
        if (!err) {
            global.GoatBot.onReply.set(info.messageID, {
                commandName,
                messageID: info.messageID,
                author: event.senderID,
                imageUrls: finalUrls,
                tempPaths: tempPaths,
                gridPath: gridPath,
                prompt: cleanedPrompt
            });
        } else {
            for (const p of tempPaths) {
                if (fs.existsSync(p)) fs.unlinkSync(p);
            }
            if (gridPath && fs.existsSync(gridPath)) fs.unlinkSync(gridPath);
        }
      });
      
      message.reaction("✅", event.messageID);

    } catch (error) {
      message.reaction("❌", event.messageID);

      for (const p of tempPaths) {
          if (fs.existsSync(p)) fs.unlinkSync(p);
      }
      if (gridPath && fs.existsSync(gridPath)) fs.unlinkSync(gridPath);

      const errorMessage = error.response ? error.response.data.error || error.response.data.message || `HTTP Error: ${error.response.status}` : error.message;
      console.error("Midjourney Command Error:", error);
      message.reply(`❌ Image generation failed: ${errorMessage}`);
    }
  },

  onReply: async function({ message, event, Reply }) { 
    const { imageUrls, tempPaths, gridPath, author } = Reply;
    const cacheDir = path.join(__dirname, 'cache');

    if (event.senderID !== author) {
        return;
    }
    
    const userReply = event.body.trim().toLowerCase();
    const selectedImagePaths = [];
    
    try {
        message.reaction("⏳", event.messageID);

        if (userReply === 'all') {
            for (let i = 0; i < imageUrls.length; i++) {
                const result = await downloadSingleImage(imageUrls[i], cacheDir, `final_all_${i + 1}`);
                selectedImagePaths.push(result.path);
            }
            
            await message.reply({
                body: `✨ Here are all your images`,
                attachment: selectedImagePaths.map(p => fs.createReadStream(p))
            });
        } else {
            const selection = parseInt(userReply);
            
            if (isNaN(selection) || selection < 1 || selection > 4) {
                message.reaction("", event.messageID);
                return;
            }

            const selectedUrl = imageUrls[selection - 1];

            if (!selectedUrl) {
                return message.reply("❌ Invalid selection. Please reply with 1, 2, 3, 4, or 'all'.");
            }

            const result = await downloadSingleImage(selectedUrl, cacheDir, `final_${selection}`);
            selectedImagePaths.push(result.path);
            
            await message.reply({
                body: `✨ Here is your image`,
                attachment: fs.createReadStream(selectedImagePaths[0])
            });
        }

        message.reaction("✅", event.messageID);

    } catch (error) {
        message.reaction("❌", event.messageID);
        console.error("Selection Download Error:", error);
        message.reply(`❌ Failed to retrieve selected image: ${error.message}`);
    } finally {
        const cleanup = async () => {
            for (const p of selectedImagePaths) {
                if (p && fs.existsSync(p)) {
                    await fs.unlink(p).catch(console.error);
                }
            }
            if (tempPaths) {
                await Promise.all(tempPaths.map(p => 
                    fs.existsSync(p) ? fs.unlink(p).catch(console.error) : Promise.resolve()
                ));
            }
            if (gridPath && fs.existsSync(gridPath)) {
                await fs.unlink(gridPath).catch(console.error);
            }
        };
        cleanup().catch(console.error);

        global.GoatBot.onReply.delete(Reply.messageID);
    }
  }
};

View raw file