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"));
}
}
};