← 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 getBadgeTier(p) {
  if (p >= 95) return { emoji: "🏆", label: "LEGENDARY", c1: "#FFE066", c2: "#FFA500", glow: "rgba(255,190,60,0.9)" };
  if (p >= 88) return { emoji: "💯", label: "PERFECT",   c1: "#FF9AD5", c2: "#C86DD7", glow: "rgba(255,107,157,0.9)" };
  if (p >= 78) return { emoji: "🔥", label: "GREAT",     c1: "#FF9A5A", c2: "#FF3D3D", glow: "rgba(255,90,60,0.85)" };
  if (p >= 68) return { emoji: "😊", label: "GOOD",      c1: "#6FE3FF", c2: "#4F8CFF", glow: "rgba(80,150,255,0.85)" };
  return          { emoji: "🙂", label: "NICE",      c1: "#B6FFCB", c2: "#43CBFF", glow: "rgba(80,220,180,0.85)" };
}

function makeCaption(percent, title) {
  const list = [
    title + "\n\n💓 দুটো হৃদয় এক ছন্দে বাজছে...\nLove Match: " + percent + "%",
    title + "\n\n🌙✨ চাঁদ আর তারার মতো মানানসই\nChemistry: " + percent + "%",
    title + "\n\n👀 এক নজরেই সব বলে দিলো\nSoul Rate: " + percent + "%",
    title + "\n\n💘 পারফেক্ট ম্যাচ পাওয়া গেছে\nLove: " + percent + "%",
    title + "\n\n🌸 " + percent + "% Pure Connection\nতোমরা একে অপরের জন্যই 💞",
    title + "\n\n🥰 ভালোবাসা কোনো শব্দ নয়...\nMatch: " + percent + "%"
  ];
  return list[Math.floor(Math.random() * list.length)];
}

async function getGender(uid, api) {
  try {
    const info = await api.getUserInfo(uid);
    const u = info[uid];
    if (!u) return null;
    if (u.gender === 1 || u.gender === "female") return "female";
    if (u.gender === 2 || u.gender === "male") return "male";
  } catch (e) {}
  return null;
}

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: 20000 });
    if (res.data && res.data.byteLength > 2000) 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 templateImg = await loadImage(tpl.url);
    const width = templateImg.width;
    const height = templateImg.height;
    const canvas = createCanvas(width, height);
    const ctx = canvas.getContext("2d");

    ctx.drawImage(templateImg, 0, 0, width, height);

    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 leftAv = await loadAvatar(leftID);
    const rightAv = await loadAvatar(rightID);

    function drawCircleAvatar(img, pos) {
      if (!img) return;
      const size = Math.floor(width * pos.size);
      const x = Math.floor(width * pos.x) - size / 2;
      const y = Math.floor(height * pos.y) - size / 2;
      ctx.save();
      ctx.beginPath();
      ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2);
      ctx.closePath();
      ctx.clip();
      ctx.drawImage(img, x, y, size, size);
      ctx.restore();
      ctx.beginPath();
      ctx.arc(x + size / 2, y + size / 2, size / 2 + 3, 0, Math.PI * 2);
      ctx.strokeStyle = "#ffffff";
      ctx.lineWidth = Math.max(4, Math.floor(width * 0.008));
      ctx.stroke();
    }

    function roundRect(x, y, w, h, r) {
      ctx.beginPath();
      ctx.moveTo(x + r, y);
      ctx.lineTo(x + w - r, y);
      ctx.quadraticCurveTo(x + w, y, x + w, y + r);
      ctx.lineTo(x + w, y + h - r);
      ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
      ctx.lineTo(x + r, y + h);
      ctx.quadraticCurveTo(x, y + h, x, y + h - r);
      ctx.lineTo(x, y + r);
      ctx.quadraticCurveTo(x, y, x + r, y);
      ctx.closePath();
    }

    function drawSparkle(cx, cy, r, color) {
      ctx.save();
      ctx.fillStyle = color;
      ctx.beginPath();
      ctx.moveTo(cx, cy - r);
      ctx.lineTo(cx + r * 0.28, cy - r * 0.28);
      ctx.lineTo(cx + r, cy);
      ctx.lineTo(cx + r * 0.28, cy + r * 0.28);
      ctx.lineTo(cx, cy + r);
      ctx.lineTo(cx - r * 0.28, cy + r * 0.28);
      ctx.lineTo(cx - r, cy);
      ctx.lineTo(cx - r * 0.28, cy - r * 0.28);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    }

    const tier = getBadgeTier(percent);

    // Frame glow
    const frameW = Math.max(4, Math.floor(width * 0.006));
    ctx.save();
    ctx.strokeStyle = tier.c2;
    ctx.lineWidth = frameW;
    ctx.shadowColor = tier.glow;
    ctx.shadowBlur = Math.floor(width * 0.015);
    ctx.strokeRect(frameW / 2, frameW / 2, width - frameW, height - frameW);
    ctx.restore();

    // Top ribbon
    const ribbonW = Math.floor(width * 0.5);
    const ribbonH = Math.floor(height * 0.055);
    const ribbonX = (width - ribbonW) / 2;
    const ribbonY = Math.floor(height * 0.025);
    const ribbonGrad = ctx.createLinearGradient(ribbonX, 0, ribbonX + ribbonW, 0);
    ribbonGrad.addColorStop(0, tier.c1);
    ribbonGrad.addColorStop(1, tier.c2);
    ctx.save();
    ctx.globalAlpha = 0.92;
    ctx.fillStyle = ribbonGrad;
    roundRect(ribbonX, ribbonY, ribbonW, ribbonH, ribbonH / 2);
    ctx.fill();
    ctx.restore();

    ctx.textAlign = "center";
    ctx.fillStyle = "#1a1020";
    ctx.font = "bold " + Math.floor(ribbonH * 0.5) + "px Arial";
    ctx.fillText("💘 LOVE MATCH 💘", width / 2, ribbonY + ribbonH * 0.66);

    // Sparkles
    drawSparkle(width * 0.08, height * 0.06, width * 0.012, "#FFFFFF");
    drawSparkle(width * 0.92, height * 0.06, width * 0.012, "#FFFFFF");
    drawSparkle(width * 0.06, height * 0.10, width * 0.008, tier.c1);
    drawSparkle(width * 0.94, height * 0.10, width * 0.008, tier.c1);

    drawCircleAvatar(leftAv, tpl.left);
    drawCircleAvatar(rightAv, tpl.right);

    // Bottom caption box
    const boxH = Math.floor(height * 0.20);
    const boxY = height - boxH;
    const radius = Math.floor(height * 0.02);

    const bgGrad = ctx.createLinearGradient(0, boxY, 0, height);
    bgGrad.addColorStop(0, "rgba(20,10,25,0.25)");
    bgGrad.addColorStop(0.3, "rgba(15,8,20,0.75)");
    bgGrad.addColorStop(1, "rgba(8,4,12,0.92)");
    ctx.fillStyle = bgGrad;
    roundRect(0, boxY, width, boxH, radius);
    ctx.fill();

    const lineGrad = ctx.createLinearGradient(0, 0, width, 0);
    lineGrad.addColorStop(0, tier.c2);
    lineGrad.addColorStop(0.5, tier.c1);
    lineGrad.addColorStop(1, tier.c2);
    ctx.fillStyle = lineGrad;
    ctx.fillRect(0, boxY, width, Math.max(3, Math.floor(height * 0.007)));

    ctx.textAlign = "center";
    ctx.font = "bold " + Math.floor(height * 0.034) + "px Arial";
    ctx.shadowColor = "rgba(255,107,157,0.85)";
    ctx.shadowBlur = Math.floor(height * 0.01);
    ctx.fillStyle = "#FFFFFF";
    const nameLine = (leftName || "User").substring(0, 13) + "   💗   " + (rightName || "User").substring(0, 13);
    ctx.fillText(nameLine, width / 2, boxY + boxH * 0.30);
    ctx.shadowColor = "transparent";
    ctx.shadowBlur = 0;

    // Percentage pill
    const pillW = Math.floor(width * 0.46);
    const pillH = Math.floor(height * 0.075);
    const pillX = (width - pillW) / 2;
    const pillY = boxY + boxH * 0.42;

    ctx.save();
    ctx.shadowColor = tier.glow;
    ctx.shadowBlur = Math.floor(height * 0.02);
    const pillGrad = ctx.createLinearGradient(pillX, 0, pillX + pillW, 0);
    pillGrad.addColorStop(0, tier.c1);
    pillGrad.addColorStop(1, tier.c2);
    ctx.fillStyle = pillGrad;
    roundRect(pillX, pillY, pillW, pillH, pillH / 2);
    ctx.fill();
    ctx.restore();

    ctx.fillStyle = "#1a1020";
    ctx.font = "bold " + Math.floor(pillH * 0.46) + "px Arial";
    ctx.fillText(tier.emoji + "  " + percent + "%  " + tier.label, width / 2, pillY + pillH * 0.65);

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

module.exports = {
  config: {
    name: "pair",
    version: "2.0.0",
    author: "EryXenX + Anime Pair",
    countDown: 5,
    role: 0,
    description: {
      en: "Find today's random couple / pair with anime template",
      bn: "আজকের random জুটি খোঁজো (অ্যানিমে টেমপ্লেট সহ)",
      hi: "Aaj ka random pair dhundho",
      tl: "Hanapin ang random na pares ngayon",
      ar: "ابحث عن زوج اليوم العشوائي"
    },
    category: "fun",
    guide: {
      en: "{pn}\n{pn} @mention\n{pn} gif\n{pn} best",
      bn: "{pn}\n{pn} @mention\n{pn} gif\n{pn} best"
    }
  },

  langs: {
    en: {
      noMembers: "❌ | Not enough members in this group!",
      error: "❌ | Failed to generate. Try again.",
      selfPair: "😅 You can't pair with yourself!",
      waiting: "💫 Matching... please wait",
      noBest: "😅 No Best Match yet today.",
      bestTitle: "🏆 Today's Best Match"
    },
    bn: {
      noMembers: "❌ | গ্রুপে যথেষ্ট সদস্য নেই!",
      error: "❌ | তৈরি করতে সমস্যা হয়েছে।",
      selfPair: "😅 নিজের সাথে pair করা যায় না!",
      waiting: "💫 ম্যাচিং হচ্ছে... অপেক্ষা করো",
      noBest: "😅 আজকে এখনো Best Match হয়নি।",
      bestTitle: "🏆 আজকের Best Match"
    },
    hi: {
      noMembers: "❌ | Group mein kaafi members nahi hain!",
      error: "❌ | Banana fail hua.",
      selfPair: "😅 Khud se pair nahi kar sakte!",
      waiting: "💫 Matching... please wait",
      noBest: "😅 Aaj abhi Best Match nahi hua.",
      bestTitle: "🏆 Aaj ka Best Match"
    },
    tl: {
      noMembers: "❌ | Hindi sapat ang mga miyembro sa grupo!",
      error: "❌ | Hindi nagawa.",
      selfPair: "😅 Hindi pwedeng mag-pair sa sarili!",
      waiting: "💫 Matching... please wait",
      noBest: "😅 Wala pang Best Match ngayon.",
      bestTitle: "🏆 Best Match Ngayon"
    },
    ar: {
      noMembers: "❌ | لا يوجد أعضاء كافيون في المجموعة!",
      error: "❌ | فشل الإنشاء.",
      selfPair: "😅 لا يمكنك الاقتران بنفسك!",
      waiting: "💫 جاري المطابقة... انتظر",
      noBest: "😅 لا يوجد أفضل تطابق اليوم بعد.",
      bestTitle: "🏆 أفضل تطابق اليوم"
    }
  },

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

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

      // Best match
      if ((args[0] || "").toLowerCase() === "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;
      if (messageReply && messageReply.senderID) targetID = messageReply.senderID;
      else if (mentions && Object.keys(mentions).length) targetID = Object.keys(mentions)[0];

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

      // Random opposite gender preference
      if (!targetID) {
        const thread = await api.getThreadInfo(threadID);
        const members = (thread.participantIDs || []).filter(id => id != senderID && id != api.getCurrentUserID());
        if (!members.length) {
          if (waiting) try { await api.unsendMessage(waiting.messageID); } catch (e) {}
          return message.reply(getLang("noMembers"));
        }

        const sg = await getGender(senderID, api);
        const opposite = [];
        const others = [];
        for (let i = 0; i < members.length; i++) {
          const g = await getGender(members[i], api);
          if ((sg === "male" && g === "female") || (sg === "female" && g === "male")) {
            opposite.push(members[i]);
          } else {
            others.push(members[i]);
          }
        }
        const list = opposite.length > 0 ? opposite : others;
        targetID = list[Math.floor(Math.random() * list.length)];
      }

      let senderName = "You";
      let targetName = "Partner";

      try {
        const info = await api.getUserInfo([senderID, targetID]);
        if (info[senderID]?.name) senderName = info[senderID].name;
        if (info[targetID]?.name) targetName = info[targetID].name;
      } 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 senderGender = (await getGender(senderID, api)) || "male";
      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);

      // Update 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
        });
      }

      // Mentions with fromIndex
      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