// code from goatstore // api: https://store.agi.bd const fs = require("fs-extra"); const path = require("path"); const axios = require("axios"); const { createCanvas, loadImage } = require("canvas"); module.exports = { config: { name: "pair", version: "2.0.0", author: "Abir", countDown: 5, role: 0, description: { en: "Find a random love pair in the group", bn: "গ্রুপে Love Pair খুঁজে বের করো" }, category: "fun", guide: { en: "{pn}", bn: "{pn}" } }, langs: { en: { noMembers: "❌ Not enough members in this group!", error: "❌ Failed to create the pair image. Please try again.", result: "💕 LOVE PAIR 💕\n\n" + "👤 %1\n❤️ + ❤️\n👤 %2\n\n" + "💞 Compatibility: %3%\n\n" + "✨ Just for fun!" }, bn: { noMembers: "❌ গ্রুপে Pair করার মতো যথেষ্ট সদস্য নেই!", error: "❌ Pair image তৈরি করা যায়নি। আবার চেষ্টা করো।", result: "💕 আজকের LOVE PAIR 💕\n\n" + "👤 %1\n❤️ + ❤️\n👤 %2\n\n" + "💞 মিল: %3%\n\n" + "✨ শুধু মজার জন্য!" } }, onStart: async function ({ event, message, getLang, usersData, api }) { const tempFiles = []; try { const { threadID, senderID } = event; const threadInfo = await api.getThreadInfo(threadID); const members = [ ...new Set(threadInfo.participantIDs || []) ].filter(id => String(id) !== String(api.getCurrentUserID()) && String(id) !== String(senderID) ); if (members.length < 1) { return message.reply(getLang("noMembers")); } // Pick a random group member const id2 = members[ Math.floor(Math.random() * members.length) ]; const compatibility = Math.floor(Math.random() * 51) + 50; const [user1, user2] = await Promise.all([ usersData.get(senderID), usersData.get(id2) ]); const name1 = user1?.name || "Member 1"; const name2 = user2?.name || "Member 2"; const cacheDir = path.join(__dirname, "cache"); await fs.ensureDir(cacheDir); const stamp = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; const avatar1Path = path.join( cacheDir, `pair_a_${stamp}.jpg` ); const avatar2Path = path.join( cacheDir, `pair_b_${stamp}.jpg` ); const outputPath = path.join( cacheDir, `pair_${stamp}.jpg` ); tempFiles.push(avatar1Path, avatar2Path, outputPath); // Uses the bot's logged-in Messenger API for avatars const avatar1 = await usersData.getAvatarUrl(senderID); const avatar2 = await usersData.getAvatarUrl(id2); const downloadImage = async (url, output) => { if (!url || !/^https?:\/\//i.test(url)) { throw new Error("Invalid avatar URL"); } const response = await axios.get(url, { responseType: "arraybuffer", timeout: 15000, maxContentLength: 8 * 1024 * 1024, headers: { "User-Agent": "Mozilla/5.0" } }); await fs.writeFile(output, Buffer.from(response.data)); }; await Promise.all([ downloadImage(avatar1, avatar1Path), downloadImage(avatar2, avatar2Path) ]); const [img1, img2] = await Promise.all([ loadImage(avatar1Path), loadImage(avatar2Path) ]); const W = 800; const H = 440; const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); // Pink gradient background const gradient = ctx.createLinearGradient(0, 0, W, H); gradient.addColorStop(0, "#ff758c"); gradient.addColorStop(0.5, "#ff2e88"); gradient.addColorStop(1, "#8e44ad"); ctx.fillStyle = gradient; ctx.fillRect(0, 0, W, H); // Decorative hearts ctx.font = "30px sans-serif"; ctx.fillStyle = "rgba(255,255,255,0.22)"; ctx.fillText("♥", 70, 65); ctx.fillText("♥", 700, 85); ctx.fillText("♥", 390, 90); const radius = 115; const centerY = 190; const centerX1 = 175; const centerX2 = 625; function drawAvatar(img, x, y, r) { ctx.save(); ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.clip(); // Cover crop to make a square avatar const size = Math.min(img.width, img.height); const sx = (img.width - size) / 2; const sy = (img.height - size) / 2; ctx.drawImage( img, sx, sy, size, size, x - r, y - r, r * 2, r * 2 ); ctx.restore(); ctx.strokeStyle = "#ffffff"; ctx.lineWidth = 7; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.stroke(); } drawAvatar(img1, centerX1, centerY, radius); drawAvatar(img2, centerX2, centerY, radius); // Center heart ctx.font = "bold 66px sans-serif"; ctx.textAlign = "center"; ctx.fillStyle = "#ffffff"; ctx.fillText("♥", W / 2, centerY + 23); // Names ctx.font = "bold 23px sans-serif"; ctx.fillStyle = "#ffffff"; ctx.fillText( name1.slice(0, 18), centerX1, 335 ); ctx.fillText( name2.slice(0, 18), centerX2, 335 ); // Compatibility bar const barX = 200; const barY = 375; const barW = 400; const barH = 20; const radiusBar = 10; function roundedRect(x, y, w, h, r) { ctx.beginPath(); if (typeof ctx.roundRect === "function") { ctx.roundRect(x, y, w, h, r); } else { 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(); } ctx.fillStyle = "rgba(255,255,255,0.35)"; roundedRect(barX, barY, barW, barH, radiusBar); ctx.fill(); ctx.save(); roundedRect( barX, barY, barW * compatibility / 100, barH, radiusBar ); ctx.clip(); ctx.fillStyle = "#ffffff"; ctx.fillRect(barX, barY, barW, barH); ctx.restore(); ctx.font = "bold 20px sans-serif"; ctx.fillStyle = "#ffffff"; ctx.fillText( `${compatibility}% COMPATIBILITY`, W / 2, 420 ); await fs.writeFile( outputPath, canvas.toBuffer("image/jpeg", { quality: 0.9 }) ); const body = getLang( "result", name1, name2, compatibility ); await message.reply({ body, attachment: fs.createReadStream(outputPath) }); } catch (err) { console.error("[PAIR COMMAND ERROR]", err); return message.reply(getLang("error")); } finally { for (const file of tempFiles) { try { await fs.remove(file); } catch (err) { console.error("Temporary file cleanup failed:", err.message); } } } } };