// code from goatstore // api: https://store.agi.bd const { createCanvas, loadImage } = require("canvas"); const axios = require("axios"); const fs = require("fs-extra"); const path = require("path"); /* ───────────── premium colour themes ───────────── */ const THEMES = [ { a: "#0b0a1f", b: "#3a1a6e", c: "#ffd27a", d: "#ff5fa2", glow: "#ffb86b" }, // royal purple + gold { a: "#06142e", b: "#124a9c", c: "#8fe9ff", d: "#b48cff", glow: "#6fd0ff" }, // midnight blue + ice { a: "#240a17", b: "#9b2457", c: "#ffc0a1", d: "#ff6f91", glow: "#ff8fa8" }, // wine rose { a: "#041f1e", b: "#0e7a6d", c: "#fff0b3", d: "#5dffc0", glow: "#7dffd8" }, // emerald gold { a: "#1a0b2e", b: "#6a2bd9", c: "#ffd9f6", d: "#ff7ad9", glow: "#e49bff" } // violet candy ]; /* ───────────── drawing helpers ───────────── */ function heartPath(ctx, x, y, s) { ctx.beginPath(); ctx.moveTo(x, y + s / 4); ctx.bezierCurveTo(x, y, x - s / 2, y, x - s / 2, y + s / 4); ctx.bezierCurveTo(x - s / 2, y + s / 2, x, y + s * 0.75, x, y + s); ctx.bezierCurveTo(x, y + s * 0.75, x + s / 2, y + s / 2, x + s / 2, y + s / 4); ctx.bezierCurveTo(x + s / 2, y, x, y, x, y + s / 4); ctx.closePath(); } function sparkle(ctx, x, y, s) { ctx.beginPath(); ctx.moveTo(x, y - s); ctx.quadraticCurveTo(x, y, x + s, y); ctx.quadraticCurveTo(x, y, x, y + s); ctx.quadraticCurveTo(x, y, x - s, y); ctx.quadraticCurveTo(x, y, x, y - s); ctx.closePath(); } function roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); } function fitText(ctx, text, maxW) { if (ctx.measureText(text).width <= maxW) return text; while (text.length > 1 && ctx.measureText(text + "…").width > maxW) text = text.slice(0, -1); return text + "…"; } function spaced(ctx, text, cx, y, gap, fill) { ctx.textAlign = "left"; const widths = [...text].map(ch => ctx.measureText(ch).width); const total = widths.reduce((a, b) => a + b, 0) + gap * (widths.length - 1); let x = cx - total / 2; if (fill) ctx.fillStyle = typeof fill === "function" ? fill(cx - total / 2, total) : fill; [...text].forEach((ch, i) => { ctx.fillText(ch, x, y); x += widths[i] + gap; }); ctx.textAlign = "center"; return total; } async function getAvatar(uid, usersData) { const urls = []; try { urls.push(await usersData.getAvatarUrl(uid)); } catch (e) { } urls.push(`https://graph.facebook.com/${uid}/picture?width=512&height=512&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`); for (const url of urls) { if (!url) continue; try { const res = await axios.get(url, { responseType: "arraybuffer", timeout: 10000 }); return await loadImage(Buffer.from(res.data)); } catch (e) { } } return null; } function drawAvatar(ctx, img, name, cx, cy, r, t) { // soft outer glow ctx.save(); ctx.shadowColor = t.glow; ctx.shadowBlur = 60; ctx.beginPath(); ctx.arc(cx, cy, r + 14, 0, Math.PI * 2); ctx.fillStyle = "rgba(255,255,255,0.10)"; ctx.fill(); ctx.restore(); // dashed outer ring ctx.save(); ctx.setLineDash([2, 12]); ctx.lineCap = "round"; ctx.lineWidth = 4; ctx.strokeStyle = t.c; ctx.globalAlpha = 0.8; ctx.beginPath(); ctx.arc(cx, cy, r + 30, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); // photo ctx.save(); ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.clip(); if (img) { ctx.drawImage(img, cx - r, cy - r, r * 2, r * 2); } else { const g = ctx.createLinearGradient(cx - r, cy - r, cx + r, cy + r); g.addColorStop(0, t.b); g.addColorStop(1, t.d); ctx.fillStyle = g; ctx.fillRect(cx - r, cy - r, r * 2, r * 2); ctx.fillStyle = "#fff"; ctx.font = "bold 110px sans-serif"; ctx.textAlign = "center"; ctx.fillText((name || "?").charAt(0).toUpperCase(), cx, cy + 38); } // glass shine on top of photo const shine = ctx.createLinearGradient(cx - r, cy - r, cx + r * 0.2, cy + r * 0.2); shine.addColorStop(0, "rgba(255,255,255,0.22)"); shine.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = shine; ctx.fillRect(cx - r, cy - r, r * 2, r * 2); ctx.restore(); // golden double ring const ring = ctx.createLinearGradient(cx - r, cy - r, cx + r, cy + r); ring.addColorStop(0, "#ffffff"); ring.addColorStop(0.35, t.c); ring.addColorStop(0.7, t.d); ring.addColorStop(1, "#ffffff"); ctx.lineWidth = 9; ctx.strokeStyle = ring; ctx.beginPath(); ctx.arc(cx, cy, r + 5, 0, Math.PI * 2); ctx.stroke(); ctx.lineWidth = 2; ctx.strokeStyle = "rgba(255,255,255,0.65)"; ctx.beginPath(); ctx.arc(cx, cy, r + 15, 0, Math.PI * 2); ctx.stroke(); // sparkles around ctx.fillStyle = "#ffffff"; ctx.globalAlpha = 0.9; sparkle(ctx, cx - r - 20, cy - r + 25, 11); ctx.fill(); sparkle(ctx, cx + r + 14, cy + r - 40, 8); ctx.fill(); sparkle(ctx, cx + r - 20, cy - r - 12, 6); ctx.fill(); ctx.globalAlpha = 1; } function namePill(ctx, name, cx, cy, t) { ctx.font = "bold 32px sans-serif"; ctx.textAlign = "center"; const text = fitText(ctx, name, 330); const w = Math.max(190, ctx.measureText(text).width + 70); const h = 58; roundRect(ctx, cx - w / 2, cy - h / 2, w, h, h / 2); ctx.fillStyle = "rgba(255,255,255,0.12)"; ctx.fill(); const bd = ctx.createLinearGradient(cx - w / 2, 0, cx + w / 2, 0); bd.addColorStop(0, t.c); bd.addColorStop(1, t.d); ctx.lineWidth = 2.5; ctx.strokeStyle = bd; ctx.stroke(); ctx.fillStyle = "#ffffff"; ctx.shadowColor = "rgba(0,0,0,0.5)"; ctx.shadowBlur = 6; ctx.fillText(text, cx, cy + 11); ctx.shadowBlur = 0; } function verdictOf(p) { if (p >= 92) return "SOULMATES"; if (p >= 80) return "PERFECT COUPLE"; if (p >= 68) return "SWEET MATCH"; if (p >= 58) return "GOOD VIBES"; return "BEST FRIENDS"; } /* ───────────── the card ───────────── */ async function makeCard(name1, name2, img1, img2, percent) { const W = 1200, H = 740; const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); const t = THEMES[Math.floor(Math.random() * THEMES.length)]; ctx.textAlign = "center"; // background gradient const bg = ctx.createLinearGradient(0, 0, W, H); bg.addColorStop(0, t.a); bg.addColorStop(0.5, t.b); bg.addColorStop(1, t.a); ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H); // light beams for (let i = 0; i < 5; i++) { const x = 80 + Math.random() * (W - 160); const w = 70 + Math.random() * 130; const g = ctx.createLinearGradient(x, 0, x + 200, H); g.addColorStop(0, "rgba(255,255,255,0.10)"); g.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x + w, 0); ctx.lineTo(x + w + 260, H); ctx.lineTo(x + 260, H); ctx.closePath(); ctx.fill(); } // center spotlight const sp = ctx.createRadialGradient(W / 2, 330, 20, W / 2, 330, 460); sp.addColorStop(0, "rgba(255,255,255,0.18)"); sp.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = sp; ctx.fillRect(0, 0, W, H); // bokeh for (let i = 0; i < 42; i++) { const x = Math.random() * W, y = Math.random() * H; const r = 6 + Math.random() * 42; const g = ctx.createRadialGradient(x, y, 0, x, y, r); g.addColorStop(0, "rgba(255,255,255,0.22)"); g.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill(); } // background hearts + sparkles for (let i = 0; i < 20; i++) { ctx.globalAlpha = 0.08 + Math.random() * 0.18; ctx.fillStyle = Math.random() > 0.5 ? "#ffffff" : t.d; heartPath(ctx, Math.random() * W, Math.random() * H, 14 + Math.random() * 28); ctx.fill(); } for (let i = 0; i < 26; i++) { ctx.globalAlpha = 0.35 + Math.random() * 0.5; ctx.fillStyle = "#ffffff"; sparkle(ctx, Math.random() * W, Math.random() * H, 3 + Math.random() * 7); ctx.fill(); } ctx.globalAlpha = 1; // glass panel + gradient border roundRect(ctx, 40, 40, W - 80, H - 80, 46); ctx.fillStyle = "rgba(255,255,255,0.075)"; ctx.fill(); const pb = ctx.createLinearGradient(40, 40, W - 40, H - 40); pb.addColorStop(0, t.c); pb.addColorStop(0.5, "rgba(255,255,255,0.25)"); pb.addColorStop(1, t.d); ctx.lineWidth = 3; ctx.strokeStyle = pb; ctx.stroke(); roundRect(ctx, 56, 56, W - 112, H - 112, 34); ctx.lineWidth = 1; ctx.strokeStyle = "rgba(255,255,255,0.18)"; ctx.stroke(); // title ctx.font = "bold 46px sans-serif"; ctx.shadowColor = t.glow; ctx.shadowBlur = 22; spaced(ctx, "PERFECT MATCH", W / 2, 118, 11, (x, w) => { const g = ctx.createLinearGradient(x, 0, x + w, 0); g.addColorStop(0, "#ffffff"); g.addColorStop(0.5, t.c); g.addColorStop(1, "#ffffff"); return g; }); ctx.shadowBlur = 0; // divider with heart const dg = ctx.createLinearGradient(W / 2 - 260, 0, W / 2 + 260, 0); dg.addColorStop(0, "rgba(255,255,255,0)"); dg.addColorStop(0.5, t.c); dg.addColorStop(1, "rgba(255,255,255,0)"); ctx.strokeStyle = dg; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(W / 2 - 260, 148); ctx.lineTo(W / 2 - 24, 148); ctx.moveTo(W / 2 + 24, 148); ctx.lineTo(W / 2 + 260, 148); ctx.stroke(); ctx.fillStyle = t.d; heartPath(ctx, W / 2, 138, 20); ctx.fill(); // avatars const cy = 345, r = 128, lx = 235, rx = W - 235; drawAvatar(ctx, img1, name1, lx, cy, r, t); drawAvatar(ctx, img2, name2, rx, cy, r, t); // little hearts between avatars and centre [[lx + r + 60, 11], [lx + r + 92, 14], [lx + r + 128, 17]].forEach(([x, s]) => { ctx.fillStyle = t.d; ctx.globalAlpha = 0.9; heartPath(ctx, x, cy - s * 0.5, s); ctx.fill(); heartPath(ctx, W - x, cy - s * 0.5, s); ctx.fill(); }); ctx.globalAlpha = 1; // gauge around the centre heart const gr = 112; ctx.lineCap = "round"; ctx.lineWidth = 12; ctx.strokeStyle = "rgba(255,255,255,0.16)"; ctx.beginPath(); ctx.arc(W / 2, cy, gr, 0, Math.PI * 2); ctx.stroke(); const gg = ctx.createLinearGradient(W / 2 - gr, cy - gr, W / 2 + gr, cy + gr); gg.addColorStop(0, t.c); gg.addColorStop(1, t.d); ctx.save(); ctx.shadowColor = t.glow; ctx.shadowBlur = 20; ctx.strokeStyle = gg; ctx.beginPath(); ctx.arc(W / 2, cy, gr, -Math.PI / 2, -Math.PI / 2 + (Math.PI * 2 * percent) / 100); ctx.stroke(); ctx.restore(); // centre heart const hs = 150, hy = cy - hs * 0.52; ctx.save(); ctx.shadowColor = t.glow; ctx.shadowBlur = 45; const hg = ctx.createLinearGradient(W / 2 - 70, hy, W / 2 + 70, hy + hs); hg.addColorStop(0, "#ff9ec7"); hg.addColorStop(0.5, "#ff4d8d"); hg.addColorStop(1, "#c4195c"); ctx.fillStyle = hg; heartPath(ctx, W / 2, hy, hs); ctx.fill(); ctx.restore(); // heart gloss ctx.save(); heartPath(ctx, W / 2, hy, hs); ctx.clip(); const gl = ctx.createRadialGradient(W / 2 - 34, hy + 28, 4, W / 2 - 34, hy + 28, 70); gl.addColorStop(0, "rgba(255,255,255,0.65)"); gl.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = gl; ctx.fillRect(W / 2 - hs, hy, hs * 2, hs); ctx.restore(); // percent text ctx.fillStyle = "#ffffff"; ctx.shadowColor = "rgba(120,0,50,0.6)"; ctx.shadowBlur = 8; ctx.font = "bold 46px sans-serif"; ctx.textAlign = "center"; ctx.fillText(percent + "%", W / 2, cy + 10); ctx.shadowBlur = 0; // names namePill(ctx, name1, lx, cy + r + 68, t); namePill(ctx, name2, rx, cy + r + 68, t); // love meter const bw = 640, bh = 18, bx = (W - bw) / 2, by = 624; ctx.textAlign = "left"; ctx.font = "bold 20px sans-serif"; ctx.fillStyle = "rgba(255,255,255,0.8)"; ctx.fillText("LOVE METER", bx, by - 12); ctx.textAlign = "right"; ctx.fillStyle = t.c; ctx.fillText(percent + " / 100", bx + bw, by - 12); roundRect(ctx, bx, by, bw, bh, 9); ctx.fillStyle = "rgba(255,255,255,0.18)"; ctx.fill(); const fg = ctx.createLinearGradient(bx, 0, bx + bw, 0); fg.addColorStop(0, t.c); fg.addColorStop(1, t.d); ctx.save(); ctx.shadowColor = t.glow; ctx.shadowBlur = 14; roundRect(ctx, bx, by, Math.max(bh, (bw * percent) / 100), bh, 9); ctx.fillStyle = fg; ctx.fill(); ctx.restore(); ctx.strokeStyle = "rgba(255,255,255,0.35)"; ctx.lineWidth = 1.5; for (let i = 1; i < 10; i++) { ctx.beginPath(); ctx.moveTo(bx + (bw * i) / 10, by + 3); ctx.lineTo(bx + (bw * i) / 10, by + bh - 3); ctx.stroke(); } // verdict ctx.textAlign = "center"; ctx.font = "bold 28px sans-serif"; ctx.shadowColor = t.glow; ctx.shadowBlur = 16; spaced(ctx, verdictOf(percent), W / 2, 690, 8, t.c); ctx.shadowBlur = 0; // panel corner ornaments ctx.strokeStyle = t.c; ctx.lineWidth = 3; ctx.lineCap = "round"; const o = 72, L = 36; [[o, o, 1, 1], [W - o, o, -1, 1], [o, H - o, 1, -1], [W - o, H - o, -1, -1]].forEach(([x, y, sx, sy]) => { ctx.beginPath(); ctx.moveTo(x, y + sy * L); ctx.lineTo(x, y); ctx.lineTo(x + sx * L, y); ctx.stroke(); }); return canvas.toBuffer("image/png"); } /* ───────────── command ───────────── */ module.exports = { config: { name: "pair", aliases: ["jori", "match"], version: "2.0", author: "Zihad", countDown: 10, role: 0, description: { en: "Premium pair card with a random group member (or a tagged person)", bn: "গ্রুপের কারো সাথে (বা ট্যাগ করা কারো সাথে) প্রিমিয়াম কার্ডে জুটি বানাও" }, category: "fun", guide: { en: "{pn} → random pair\n{pn} @tag → pair with tagged person\nreply to someone with {pn} → pair with them" } }, onStart: async function ({ api, event, message, usersData }) { const { threadID, senderID, mentions } = event; try { let partnerID = Object.keys(mentions || {})[0]; if (!partnerID && event.messageReply) partnerID = event.messageReply.senderID; if (!partnerID) { const info = await api.getThreadInfo(threadID); const botID = api.getCurrentUserID(); const pool = (info.participantIDs || []).filter(id => id != senderID && id != botID); if (!pool.length) return message.reply("❌ জুটি বানানোর মতো কেউ নেই!"); // opposite gender first when known (GoatBot: 1 = female, 2 = male) let myGender = 0; try { myGender = await usersData.get(senderID, "gender"); } catch (e) { } let candidates = pool; if (myGender === 1 || myGender === 2) { const opposite = []; for (const id of pool) { try { const g = await usersData.get(id, "gender"); if (g && g !== myGender) opposite.push(id); } catch (e) { } } if (opposite.length) candidates = opposite; } partnerID = candidates[Math.floor(Math.random() * candidates.length)]; } if (partnerID == senderID) return message.reply("😅 নিজের সাথে নিজে জুটি হওয়া যায় না!"); const [name1, name2, img1, img2] = await Promise.all([ usersData.getName(senderID), usersData.getName(partnerID), getAvatar(senderID, usersData), getAvatar(partnerID, usersData) ]); const percent = 55 + Math.floor(Math.random() * 46); // 55 – 100 const buffer = await makeCard(name1, name2, img1, img2, percent); const dir = path.join(__dirname, "tmp"); await fs.ensureDir(dir); const file = path.join(dir, `pair_${senderID}_${Date.now()}.png`); await fs.writeFile(file, buffer); const msg = percent >= 92 ? "🔥 একদম সোলমেট!" : percent >= 80 ? "💖 পারফেক্ট জুটি!" : percent >= 68 ? "🌸 দারুণ মানিয়েছে!" : percent >= 58 ? "✨ ভালো ভাইব আছে!" : "😄 বেস্ট ফ্রেন্ড!"; await message.reply({ body: `💞 জুটি তৈরি!\n\n👤 ${name1} ❤ ${name2}\n💘 মিল: ${percent}%\n${msg}`, mentions: [{ tag: name2, id: partnerID }], attachment: fs.createReadStream(file) }); fs.unlink(file).catch(() => { }); } catch (err) { console.error("[pair]", err); return message.reply("❌ কিছু একটা সমস্যা হয়েছে, আবার চেষ্টা করো।"); } } };