← back to browse
goat command

edit

Edit image with AI (Rotating APIs)

0
views
0
likes
0
installs

Aliases: imgedit, aiedit

raw source
const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs-extra");
const path = require("path");

/* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• API ROTATION COUNTER โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
let apiIndex = 0;

module.exports = {
  config: {
    name: "edit",
    aliases: ["imgedit", "aiedit"],
    version: "3.1.0",
    author: "๐Ÿ”ฐ๐‘๐€๐‡๐€๐“ ๐ˆ๐’๐‹๐€๐Œ๐Ÿ”ฐ (Fixed by AI)",
    countDown: 25,
    role: 0,
    shortDescription: "Edit image with AI (Rotating APIs)",
    longDescription: "Reply to any image with a prompt to edit it using AI. Rotates between 4 APIs.",
    category: "image",
    guide: "{pn} [prompt]  |  Reply to an image"
  },

  onStart: async function ({ api, event, args, message, usersData }) {
    const { senderID, messageReply, threadID, messageID } = event;

    /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• VIP & PERMISSION CHECK โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
    const ADMINS = global.GoatBot?.config?.adminBot || [];
    const isBotAdmin = ADMINS.map(String).includes(String(senderID));

    if (!isBotAdmin) {
      try {
        const userData = await usersData.get(senderID);
        const vip = userData?.data?.vip;

        if (!vip || !vip.expires || vip.expires < Date.now()) {
          return api.sendMessage(
            "โŒ ๐•๐ˆ๐ ๐Ž๐๐‹๐˜ ๐‚๐Ž๐Œ๐Œ๐€๐๐ƒ\n" +
            "โ€ข ๐Ž๐ง๐ฅ๐ฒ ๐•๐ˆ๐ ๐ฎ๐ฌ๐ž๐ซ๐ฌ ๐œ๐š๐ง ๐ฎ๐ฌ๐ž ๐ž๐๐ข๐ญ\n" +
            "โ€ข ๐“๐ฒ๐ฉ๐ž: vip buy",
            threadID,
            messageID
          );
        }
      } catch (err) {
        console.error("[edit] VIP check failed:", err.message);
      }
    }

    /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• REACTION HELPER โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
    const react = (emoji) => {
      try {
        api.setMessageReaction(emoji, messageID, () => {}, true);
      } catch (e) {}
    };

    /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• GET IMAGE URL โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
    let imageUrl = null;

    if (messageReply?.attachments?.length > 0) {
      const att = messageReply.attachments.find(
        item => item.type === "photo" || item.type === "image"
      );
      if (att) imageUrl = att.url || att.image_data?.url;
    }

    if (!imageUrl && event.attachments?.length > 0) {
      const att = event.attachments.find(
        item => item.type === "photo" || item.type === "image"
      );
      if (att) imageUrl = att.url || att.image_data?.url;
    }

    if (!imageUrl) {
      react("๐Ÿ–ผ๏ธ");
      return api.sendMessage(
        "โŒ เฆเฆ•เฆŸเฆฟ เฆ›เฆฌเฆฟเฆฐ เฆฎเง‡เฆธเง‡เฆœเง‡ reply เฆ•เฆฐเง‡ command เฆฆเฆฟเฆจเฅค\n\n" +
        "เฆ‰เฆฆเฆพเฆนเฆฐเฆฃ:\n.edit make the background beautiful",
        threadID,
        messageID
      );
    }

    /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• GET PROMPT โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
    const prompt = args.join(" ").trim();

    if (!prompt) {
      react("โš ๏ธ");
      return api.sendMessage(
        "โŒ Prompt เฆฆเฆฟเฆจเฅค\n\n" +
        "เฆ‰เฆฆเฆพเฆนเฆฐเฆฃ:\n.edit make the sky sunset",
        threadID,
        messageID
      );
    }

    /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• START PROCESSING โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
    react("โณ");

    const cacheDir = path.join(__dirname, "cache");
    await fs.ensureDir(cacheDir);

    const inputPath = path.join(cacheDir, `edit_in_${Date.now()}.jpg`);
    const outputPath = path.join(cacheDir, `edit_out_${Date.now()}_${senderID}.png`);

    /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• SEND WAITING MESSAGE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
    let waitMsgID = null;
    try {
      const waitMsg = await api.sendMessage(
        "๐Ÿช’please wait bara...",
        threadID,
        messageID
      );
      waitMsgID = waitMsg?.messageID;
    } catch (e) {}

    try {
      /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• API LIST (Rotation) โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
      const apiFunctions = [
        // โ•โ•โ•โ• API 1: Oculux Flux Kontext (Stream) โ•โ•โ•โ•
        async () => {
          const url = `https://dev.oculux.xyz/api/fluxkontext?prompt=${encodeURIComponent(prompt)}&ref=${encodeURIComponent(imageUrl)}`;
          const response = await axios.get(url, {
            responseType: "arraybuffer",
            timeout: 20000,
            headers: { "User-Agent": "Mozilla/5.0" }
          });
          if (!response.data || response.data.byteLength < 1000) {
            throw new Error("Invalid image data from Oculux");
          }
          await fs.writeFile(outputPath, Buffer.from(response.data));
        },

        // โ•โ•โ•โ• API 2: Xrahat NanoBanana Edit (Multipart) ๐ŸŒ โ•โ•โ•โ•
        async () => {
          // Download input image
          const imgRes = await axios.get(imageUrl, {
            responseType: "arraybuffer",
            timeout: 15000,
            headers: { "User-Agent": "Mozilla/5.0" }
          });
          await fs.writeFile(inputPath, Buffer.from(imgRes.data));

          // Build form data (Xrahat format)
          const form = new FormData();
          form.append("image", fs.createReadStream(inputPath), {
            filename: "image.jpg",
            contentType: "image/jpeg"
          });
          form.append("prompt", prompt);
          form.append("resolution", "2K");
          form.append("ratio", "match_input_image");

          // POST to Xrahat API
          const apiResponse = await axios.post(
            "https://xrahat-image-edit.vercel.app/api/edit",
            form,
            {
              headers: { ...form.getHeaders() },
              timeout: 20000,
              maxContentLength: Infinity,
              maxBodyLength: Infinity
            }
          );

          const data = apiResponse.data || {};
          if (!data.success || !data.imageUrl) {
            throw new Error(data.error || "Xrahat edit failed");
          }

          // Download generated image
          const outRes = await axios.get(data.imageUrl, {
            responseType: "arraybuffer",
            timeout: 15000,
            headers: { "User-Agent": "Mozilla/5.0" }
          });
          if (!outRes.data || outRes.data.byteLength < 1000) {
            throw new Error("Invalid image from Xrahat");
          }
          await fs.writeFile(outputPath, Buffer.from(outRes.data));
        },

        // โ•โ•โ•โ• API 3: FluxCDI Seedream V4 Edit (JSON) โ•โ•โ•โ•
        async () => {
          const url = `https://fluxcdibai-1.onrender.com/generate?prompt=${encodeURIComponent(prompt)}&model=seedream v4 edit&imageUrl=${encodeURIComponent(imageUrl)}`;
          const response = await axios.get(url, {
            timeout: 20000,
            headers: { "User-Agent": "Mozilla/5.0" }
          });

          const resultUrl =
            response.data?.data?.imageResponseVo?.url ||
            response.data?.imageUrl ||
            response.data?.url ||
            response.data?.result;

          if (!resultUrl) throw new Error("No image URL from FluxCDI");

          const imgRes = await axios.get(resultUrl, {
            responseType: "arraybuffer",
            timeout: 15000,
            headers: { "User-Agent": "Mozilla/5.0" }
          });
          if (!imgRes.data || imgRes.data.byteLength < 1000) {
            throw new Error("Invalid image from FluxCDI");
          }
          await fs.writeFile(outputPath, Buffer.from(imgRes.data));
        },

        // โ•โ•โ•โ• API 4: Azadx Editor (Stream) โ•โ•โ•โ•
        async () => {
          const url = `https://azadx69x.is-a.dev/api/editor?url=${encodeURIComponent(imageUrl)}&prompt=${encodeURIComponent(prompt)}`;
          const response = await axios.get(url, {
            responseType: "arraybuffer",
            timeout: 20000,
            headers: { "User-Agent": "Mozilla/5.0" }
          });
          if (!response.data || response.data.byteLength < 1000) {
            throw new Error("Invalid image from Azadx");
          }
          await fs.writeFile(outputPath, Buffer.from(response.data));
        }
      ];

      /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ROTATION LOGIC โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
      const totalAPIs = apiFunctions.length;
      const currentAPI = apiIndex % totalAPIs;
      apiIndex = (apiIndex + 1) % totalAPIs;

      console.log(`[edit] ๐ŸŽฏ Using API #${currentAPI + 1} of ${totalAPIs}`);

      let success = false;
      let lastError = null;
      let usedAPIName = "";

      const apiNames = ["Oculux", "Xrahat ๐ŸŒ", "FluxCDI", "Azadx"];

      // Try current API first
      try {
        await apiFunctions[currentAPI]();
        success = true;
        usedAPIName = apiNames[currentAPI];
        console.log(`[edit] โœ… Success with API #${currentAPI + 1} (${usedAPIName})`);
      } catch (err) {
        lastError = err;
        console.log(`[edit] โŒ API #${currentAPI + 1} (${apiNames[currentAPI]}) failed: ${err.message}`);

        // If failed, try backup APIs
        for (let i = 1; i < totalAPIs; i++) {
          const backupIndex = (currentAPI + i) % totalAPIs;
          try {
            console.log(`[edit] ๐Ÿ”„ Trying backup API #${backupIndex + 1} (${apiNames[backupIndex]})...`);
            await apiFunctions[backupIndex]();
            success = true;
            usedAPIName = apiNames[backupIndex];
            apiIndex = (backupIndex + 1) % totalAPIs;
            console.log(`[edit] โœ… Success with backup API #${backupIndex + 1} (${usedAPIName})`);
            break;
          } catch (backupErr) {
            lastError = backupErr;
            console.log(`[edit] โŒ Backup API #${backupIndex + 1} failed: ${backupErr.message}`);
            continue;
          }
        }
      }

      if (!success) {
        throw lastError || new Error("All API endpoints failed");
      }

      /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• FILE VALIDATION โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
      const stats = await fs.stat(outputPath);
      if (stats.size < 1000) {
        throw new Error("Generated image file is invalid");
      }

      /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• DELETE WAITING MESSAGE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
      if (waitMsgID) {
        try { await api.unsendMessage(waitMsgID); } catch (e) {}
      }

      /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• SEND SUCCESS โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
      react("โœ…");

      return api.sendMessage(
        {
          body:
            `โœ… Image Edit Complete!\n` +
            `๐Ÿ“ Prompt: ${prompt}\n` +
            `๐ŸŽฏ API: ${usedAPIName}`,
          attachment: fs.createReadStream(outputPath)
        },
        threadID,
        () => {
          if (fs.existsSync(outputPath)) {
            try { fs.unlinkSync(outputPath); } catch (e) {}
          }
          if (fs.existsSync(inputPath)) {
            try { fs.unlinkSync(inputPath); } catch (e) {}
          }
        },
        messageReply?.messageID || messageID
      );

    } catch (error) {
      /* โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ERROR HANDLING โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• */
      console.error("[edit] Final Error:", error.message);

      // Delete waiting message
      if (waitMsgID) {
        try { await api.unsendMessage(waitMsgID); } catch (e) {}
      }

      if (fs.existsSync(outputPath)) {
        try { fs.unlinkSync(outputPath); } catch (e) {}
      }
      if (fs.existsSync(inputPath)) {
        try { fs.unlinkSync(inputPath); } catch (e) {}
      }

      react("โŒ");

      let errorMsg = "โŒ Image edit failed.";

      if (error.code === "ECONNABORTED" || error.message.includes("timeout")) {
        errorMsg += "\n\nโฑ๏ธ Request timed out. Please try again.";
      } else if (error.response?.data?.error) {
        errorMsg += `\n\n${error.response.data.error}`;
      } else if (error.response) {
        errorMsg += `\n\n๐ŸŒ API Error: ${error.response.status}`;
      } else if (error.message.includes("too small") || error.message.includes("Invalid")) {
        errorMsg += "\n\n๐Ÿ“ฆ Received invalid image data.";
      } else if (error.message.includes("All API endpoints failed")) {
        errorMsg += "\n\n๐Ÿ”Œ All servers are down. Please try again later.";
      } else if (error.message) {
        errorMsg += `\n\n${error.message}`;
      }

      return api.sendMessage(errorMsg, threadID, messageID);
    }
  }
};

View raw file