← back to browse
goat command

pair

No description

0
views
0
likes
0
installs
raw source
const axios = require("axios");
const fs = require("fs-extra");
const path = require("path");

let createCanvas, loadImage;
try {
  const canvas = require("canvas");
  createCanvas = canvas.createCanvas;
  loadImage = canvas.loadImage;
} catch (e) {}

const BEST_FILE = path.join(__dirname, "cache", "best_match.json");
const CACHE_DIR = path.join(__dirname, "cache");

const TEMPLATES = [
  { url: "https://files.catbox.moe/xmsmrt.jpeg", maleSide: "left",  left: { x: 0.22, y: 0.48, size: 0.26 }, right: { x: 0.72, y: 0.48, size: 0.26 } },
  { url: "https://files.catbox.moe/dkbxze.jpeg", maleSide: "left",  left: { x: 0.28, y: 0.28, size: 0.24 }, right: { x: 0.68, y: 0.30, size: 0.24 } },
  { url: "https://files.catbox.moe/c5syq8.jpeg", maleSide: "right", left: { x: 0.25, y: 0.18, size: 0.22 }, right: { x: 0.68, y: 0.16, size: 0.22 } },
  { url: "https://files.catbox.moe/awasn6.jpeg", maleSide: "right", left: { x: 0.28, y: 0.35, size: 0.24 }, right: { x: 0.65, y: 0.38, size: 0.24 } },
  { url: "https://files.catbox.moe/voo30r.jpeg", maleSide: "right", left: { x: 0.22, y: 0.32, size: 0.22 }, right: { x: 0.68, y: 0.30, size: 0.22 } }
];

const RANDOM_GIFS = [
  "https://files.catbox.moe/i9yxez.gif",
  "https://files.catbox.moe/a3r48n.gif",
  "https://files.catbox.moe/6lnz86.gif",
  "https://files.catbox.moe/u3ip3u.gif",
  "https://files.catbox.moe/ydl2aq.gif",
  "https://files.catbox.moe/gp5ulj.gif"
];

const TITLES = [
  "💘 Perfect Match",
  "💞 Soulmates",
  "✨ Destiny Pair",
  "🌌 Cosmic Love",
  "💓 Heart Sync",
  "👑 Ultimate Couple",
  "🔒 Love Locked",
  "🌹 Made For Each Other"
];

function getBadge(p) {
  if (p >= 95) return "🏆 LEGENDARY";
  if (p >= 88) return "💯 PERFECT";
  if (p >= 78) return "🔥 GREAT";
  if (p >= 68) return "😊 GOOD";
  return "🙂 NICE";
}

function makeCaption(percent, title) {
  const list = [
    `${title}\n\n💓 দুজনের মিল: ${percent}%`,
    `${title}\n\n💘 Love Score: ${percent}%`,
    `${title}\n\n✨ Compatibility: ${percent}%`
  ];
  return list[Math.floor(Math.random() * list.length)];
}

async function loadAvatar(uid) {
  if (!loadImage) return null;
  const urls = [
    `https://graph.facebook.com/${uid}/picture?width=512&height=512&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
    `https://graph.facebook.com/${uid}/picture?type=large&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`,
    `https://graph.facebook.com/${uid}/picture?width=512&height=512`,
    `https://graph.facebook.com/${uid}/picture?type=large`
  ];
  for (const url of urls) {
    try {
      const img = await loadImage(url);
      if (img && img.width >= 80) return img;
    } catch (e) {}
  }
  return null;
}

async function downloadBuffer(url) {
  try {
    const res = await axios.get(url, { responseType: "arraybuffer", timeout: 15000 });
    return Buffer.from(res.data);
  } catch (e) {
    return null;
  }
}

async function createTemplatePair(senderID, targetID, senderName, targetName, senderGender, percent) {
  if (!createCanvas || !loadImage) return null;
  try {
    const tpl = TEMPLATES[Math.floor(Math.random() * TEMPLATES.length)];
    const bg = await loadImage(tpl.url);
    const W = bg.width;
    const H = bg.height;
    const canvas = createCanvas(W, H);
    const ctx = canvas.getContext("2d");
    ctx.drawImage(bg, 0, 0, W, H);

    let leftID, rightID, leftName, rightName;
    if (tpl.maleSide === "left") {
      if (senderGender === "male") {
        leftID = senderID; leftName = senderName;
        rightID = targetID; rightName = targetName;
      } else {
        leftID = targetID; leftName = targetName;
        rightID = senderID; rightName = senderName;
      }
    } else {
      if (senderGender === "male") {
        rightID = senderID; rightName = senderName;
        leftID = targetID; leftName = targetName;
      } else {
        rightID = targetID; rightName = targetName;
        leftID = senderID; leftName = senderName;
      }
    }

    const leftImg = await loadAvatar(leftID);
    const rightImg = await loadAvatar(rightID);

    function drawCircle(img, x, y, size) {
      if (!img) return;
      const r = size / 2;
      ctx.save();
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.closePath();
      ctx.clip();
      ctx.drawImage(img, x - r, y - r, size, size);
      ctx.restore();
      ctx.beginPath();
      ctx.arc(x, y, r, 0, Math.PI * 2);
      ctx.strokeStyle = "#fff";
      ctx.lineWidth = Math.max(4, size * 0.04);
      ctx.stroke();
    }

    const ls = tpl.left.size * Math.min(W, H);
    const rs = tpl.right.size * Math.min(W, H);
    drawCircle(leftImg, tpl.left.x * W, tpl.left.y * H, ls);
    drawCircle(rightImg, tpl.right.x * W, tpl.right.y * H, rs);

    return canvas.toBuffer("image/png");
  } catch (e) {
    console.error("createTemplatePair:", e.message);
    return null;
  }
}

/**
 * SAAN স্টাইল — getThreadInfo().userInfo থেকে এক কলেই gender
 * ছেলে → মেয়ে, মেয়ে → ছেলে
 */
async function pickOppositePartner(api, threadID, senderID, botID) {
  const thread = await api.getThreadInfo(threadID);
  const users = thread.userInfo || [];

  const me = users.find(u => String(u.id) === String(senderID));
  let myGender = (me?.gender || "").toUpperCase(); // MALE / FEMALE

  // userInfo তে gender না থাকলে fallback
  if (!myGender || (myGender !== "MALE" && myGender !== "FEMALE")) {
    try {
      const info = await api.getUserInfo(senderID);
      const g = info[senderID]?.gender;
      if (g === 1 || g === "female" || g === "FEMALE") myGender = "FEMALE";
      else if (g === 2 || g === "male" || g === "MALE") myGender = "MALE";
      else myGender = "MALE";
    } catch (e) {
      myGender = "MALE";
    }
  }

  let candidates = [];
  if (myGender === "MALE") {
    candidates = users.filter(
      u => u.gender === "FEMALE" && String(u.id) !== String(senderID) && String(u.id) !== String(botID)
    );
  } else {
    candidates = users.filter(
      u => u.gender === "MALE" && String(u.id) !== String(senderID) && String(u.id) !== String(botID)
    );
  }

  // অপর জেন্ডার না থাকলে যেকোনো মেম্বার
  if (!candidates.length) {
    candidates = users.filter(
      u => String(u.id) !== String(senderID) && String(u.id) !== String(botID)
    );
  }

  if (!candidates.length) return null;

  const selected = candidates[Math.floor(Math.random() * candidates.length)];
  return {
    targetID: selected.id,
    targetName: selected.name || "Partner",
    senderGender: myGender === "FEMALE" ? "female" : "male",
    users
  };
}

module.exports = {
  config: {
    name: "pair",
    version: "3.0.0",
    author: "Banu_suna + SAAN logic",
    countDown: 5,
    role: 0,
    description: {
      en: "Find random couple with opposite gender (boy↔girl)",
      bn: "ছেলে-মেয়ে মিলিয়ে random জুটি খোঁজো"
    },
    category: "fun",
    guide: {
      en: "{pn}\n{pn} @tag\n{pn} (reply)\n{pn} best\n{pn} gif",
      bn: "{pn}\n{pn} @ট্যাগ\n{pn} (রিপ্লাই)\n{pn} best\n{pn} gif"
    }
  },

  langs: {
    en: {
      waiting: "💘 Finding your perfect match...",
      noMembers: "❌ | No suitable match found in this group!",
      selfPair: "❌ | You cannot pair with yourself!",
      noBest: "❌ | No best match recorded today yet.",
      bestTitle: "🏆 Today's Best Match",
      error: "❌ | Something went wrong. Try again."
    },
    bn: {
      waiting: "💘 তোমার পারফেক্ট ম্যাচ খোঁজা হচ্ছে...",
      noMembers: "❌ | গ্রুপে উপযুক্ত ম্যাচ পাওয়া যায়নি!",
      selfPair: "❌ | নিজের সাথে পেয়ার করা যাবে না!",
      noBest: "❌ | আজকের বেস্ট ম্যাচ এখনো হয়নি।",
      bestTitle: "🏆 আজকের বেস্ট ম্যাচ",
      error: "❌ | কিছু সমস্যা হয়েছে। আবার চেষ্টা করো।"
    }
  },

  onStart: async function ({ api, event, args, message, usersData, getLang }) {
    const { threadID, messageID, senderID, messageReply, mentions } = event;
    const botID = api.getCurrentUserID();
    const firstArg = (args[0] || "").toLowerCase();
    const wantGif = firstArg === "gif";

    try {
      try {
        await api.setMessageReaction("😍", messageID, () => {}, true);
      } catch (e) {}

      // ===== .pair best =====
      if (firstArg === "best") {
        const best = await fs.readJson(BEST_FILE).catch(() => null);
        const today = new Date().toLocaleDateString("en-CA", { timeZone: "Asia/Dhaka" });
        if (!best || best.date !== today) {
          return message.reply(getLang("noBest"));
        }
        return message.reply(
          getLang("bestTitle") +
          "\n\n💞 " + best.senderName + " + " + best.targetName +
          "\n⭐ Score: " + best.percent + "%\n" + best.badge
        );
      }

      let waiting = null;
      try {
        waiting = await message.reply(getLang("waiting"));
      } catch (e) {}

      let targetID = null;
      let targetName = "Partner";
      let senderGender = "male";
      let senderName = "You";

      // রিপ্লাই / ট্যাগ
      if (messageReply && messageReply.senderID) {
        targetID = messageReply.senderID;
      } else if (mentions && Object.keys(mentions).length) {
        targetID = Object.keys(mentions)[0];
      }

      if (targetID && String(targetID) === String(senderID)) {
        if (waiting) try { await api.unsendMessage(waiting.messageID); } catch (e) {}
        return message.reply(getLang("selfPair"));
      }

      // Random opposite gender (SAAN style — ১টা getThreadInfo)
      if (!targetID) {
        const picked = await pickOppositePartner(api, threadID, senderID, botID);
        if (!picked) {
          if (waiting) try { await api.unsendMessage(waiting.messageID); } catch (e) {}
          return message.reply(getLang("noMembers"));
        }
        targetID = picked.targetID;
        targetName = picked.targetName;
        senderGender = picked.senderGender;

        const me = (picked.users || []).find(u => String(u.id) === String(senderID));
        if (me?.name) senderName = me.name;
      } else {
        // ট্যাগ/রিপ্লাই মোডে gender + নাম
        try {
          const thread = await api.getThreadInfo(threadID);
          const users = thread.userInfo || [];
          const me = users.find(u => String(u.id) === String(senderID));
          const partner = users.find(u => String(u.id) === String(targetID));

          if (me?.name) senderName = me.name;
          if (partner?.name) targetName = partner.name;

          const g = (me?.gender || "").toUpperCase();
          if (g === "FEMALE") senderGender = "female";
          else if (g === "MALE") senderGender = "male";
        } catch (e) {}
      }

      // নাম আরও নিশ্চিত করতে
      try {
        const n1 = await usersData.getName(senderID);
        if (n1) senderName = n1;
        const n2 = await usersData.getName(targetID);
        if (n2) targetName = n2;
      } catch (e) {}

      const percent = Math.floor(Math.random() * 36) + 65; // 65–100
      const badge = getBadge(percent);
      const title = TITLES[Math.floor(Math.random() * TITLES.length)];
      const caption = makeCaption(percent, title);

      // Daily best আপডেট
      const today = new Date().toLocaleDateString("en-CA", { timeZone: "Asia/Dhaka" });
      const prev = await fs.readJson(BEST_FILE).catch(() => null);
      if (!prev || prev.date !== today || percent > prev.percent) {
        await fs.ensureDir(path.dirname(BEST_FILE));
        await fs.writeJson(BEST_FILE, {
          date: today,
          senderName,
          targetName,
          percent,
          badge
        });
      }

      const body =
        caption +
        "\n\n👤 " + senderName +
        "\n💑 " + targetName +
        "\n" + badge;

      const senderIdx = body.indexOf(senderName);
      const targetIdx = body.indexOf(targetName, senderIdx + senderName.length);

      const msgMentions = [];
      if (senderIdx !== -1) {
        msgMentions.push({ tag: senderName, id: senderID, fromIndex: senderIdx });
      }
      if (targetIdx !== -1) {
        msgMentions.push({ tag: targetName, id: targetID, fromIndex: targetIdx });
      }
      if (msgMentions.length < 2) {
        msgMentions.length = 0;
        msgMentions.push({ tag: senderName, id: senderID });
        msgMentions.push({ tag: targetName, id: targetID });
      }

      if (waiting) {
        try { await api.unsendMessage(waiting.messageID); } catch (e) {}
      }

      await fs.ensureDir(CACHE_DIR);

      let buffer = null;
      let ext = "png";

      if (wantGif) {
        const gifUrl = RANDOM_GIFS[Math.floor(Math.random() * RANDOM_GIFS.length)];
        buffer = await downloadBuffer(gifUrl);
        ext = "gif";
      } else {
        buffer = await createTemplatePair(senderID, targetID, senderName, targetName, senderGender, percent);
        if (!buffer) {
          const gifUrl = RANDOM_GIFS[Math.floor(Math.random() * RANDOM_GIFS.length)];
          buffer = await downloadBuffer(gifUrl);
          ext = "gif";
        }
      }

      const msgData = {
        body,
        mentions: msgMentions
      };

      if (buffer) {
        const file = path.join(CACHE_DIR, "pair_" + Date.now() + "." + ext);
        await fs.writeFile(file, buffer);
        msgData.attachment = fs.createReadStream(file);
        try {
          await message.reply(msgData);
          try { await fs.unlink(file); } catch (e) {}
          return;
        } catch (e) {
          console.error("Upload failed:", e.message);
        }
      }

      return message.reply(msgData);

    } catch (err) {
      console.error("Pair Error:", err);
      return message.reply(getLang("error"));
    }
  }
};

View raw file