// code from goatstore // api: https://store.agi.bd const fs = require("fs-extra"); const path = require("path"); const axios = require("axios"); // নোট: প্যাকেজের নাম ভেরিয়েবলে রাখা হয়েছে, যাতে GoatBot-এর লোডার // অটো-ইনস্টল করতে গিয়ে (process.stderr.clearLine) এরর না দেয়। let createCanvas, loadImage; for (const pkgName of ["canvas", "@napi-rs/canvas"]) { try { ({ createCanvas, loadImage } = require(pkgName)); break; } catch (e) { /* পরেরটা চেষ্টা করবে */ } } let GIFEncoder = null; try { const gifPkg = "gifencoder"; GIFEncoder = require(gifPkg); } catch (e) { /* gifencoder না থাকলে স্ট্যাটিক PNG যাবে */ } const W = 800; const H = 450; const FRAMES = 36; const FRAME_DELAY = 60; // ms const AVATAR_TOKEN = "6628568379%7Cc1e620fa708a1d5696fb991c1bde5662"; // ---------- helpers ---------- function getDeltaNext() { try { const v = global.GoatBot.configCommands.envCommands.rank.deltaNext; if (Number(v) > 0) return Number(v); } catch (e) { } return 5; } function levelFromExp(exp, delta) { return Math.floor((1 + Math.sqrt(1 + 8 * exp / delta)) / 2); } function expForLevel(level, delta) { return delta * level * (level - 1) / 2; } function roundRect(ctx, 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 fitText(ctx, text, maxWidth) { if (ctx.measureText(text).width <= maxWidth) return text; let t = text; while (t.length > 1 && ctx.measureText(t + "…").width > maxWidth) t = t.slice(0, -1); return t + "…"; } function fmt(n) { return Number(n || 0).toLocaleString("en-US"); } // স্থির র‍্যান্ডম কণা (প্রতি ফ্রেমে একই পজিশন থেকে হিসাব হয়, তাই লুপ মসৃণ) const PARTICLES = Array.from({ length: 22 }, (_, i) => ({ x: (i * 83 + 40) % W, y: (i * 131 + 20) % H, r: 1.5 + (i % 4) * 0.8, k: 1 + (i % 2), // পূর্ণসংখ্যা গতি → সীমলেস লুপ a: 0.25 + (i % 5) * 0.1 })); // ---------- drawing ---------- function drawFrame(ctx, t, d) { const TAU = Math.PI * 2; const phase = t * TAU; // background const bg = ctx.createLinearGradient(0, 0, W, H); bg.addColorStop(0, "#0e0720"); bg.addColorStop(1, "#2b1156"); ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H); // moving glow orbs const orbs = [ { x: 140 + Math.cos(phase) * 60, y: 120 + Math.sin(phase) * 40, r: 260, c: "168,85,247" }, { x: 680 + Math.cos(phase + 2) * 70, y: 330 + Math.sin(phase + 2) * 50, r: 280, c: "99,102,241" }, { x: 420 + Math.cos(phase + 4) * 90, y: 60 + Math.sin(phase + 4) * 30, r: 200, c: "217,70,239" } ]; for (const o of orbs) { const g = ctx.createRadialGradient(o.x, o.y, 0, o.x, o.y, o.r); g.addColorStop(0, `rgba(${o.c},0.35)`); g.addColorStop(1, `rgba(${o.c},0)`); ctx.fillStyle = g; ctx.fillRect(0, 0, W, H); } // floating particles for (const p of PARTICLES) { const y = ((p.y - p.k * t * H) % H + H) % H; ctx.globalAlpha = p.a; ctx.fillStyle = "#e9d5ff"; ctx.beginPath(); ctx.arc(p.x, y, p.r, 0, TAU); ctx.fill(); } ctx.globalAlpha = 1; // glass card roundRect(ctx, 24, 24, W - 48, H - 48, 28); ctx.fillStyle = "rgba(255,255,255,0.06)"; ctx.fill(); ctx.lineWidth = 2; ctx.strokeStyle = `rgba(192,132,252,${0.55 + 0.25 * Math.sin(phase)})`; ctx.stroke(); // avatar const cx = 160, cy = 225, r = 90; // glow const glow = ctx.createRadialGradient(cx, cy, r, cx, cy, r + 50); glow.addColorStop(0, "rgba(192,132,252,0.45)"); glow.addColorStop(1, "rgba(192,132,252,0)"); ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(cx, cy, r + 50, 0, TAU); ctx.fill(); ctx.save(); ctx.beginPath(); ctx.arc(cx, cy, r, 0, TAU); ctx.closePath(); ctx.clip(); if (d.avatar) { ctx.drawImage(d.avatar, cx - r, cy - r, r * 2, r * 2); } else { ctx.fillStyle = "#6d28d9"; ctx.fillRect(cx - r, cy - r, r * 2, r * 2); ctx.fillStyle = "#fff"; ctx.font = "bold 80px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText((d.name || "?").charAt(0).toUpperCase(), cx, cy + 4); } ctx.restore(); // static ring ctx.lineWidth = 4; ctx.strokeStyle = "rgba(255,255,255,0.15)"; ctx.beginPath(); ctx.arc(cx, cy, r + 14, 0, TAU); ctx.stroke(); // rotating arcs ctx.lineCap = "round"; ctx.lineWidth = 6; ctx.strokeStyle = "#c084fc"; ctx.beginPath(); ctx.arc(cx, cy, r + 14, phase, phase + Math.PI * 0.6); ctx.stroke(); ctx.strokeStyle = "#818cf8"; ctx.beginPath(); ctx.arc(cx, cy, r + 14, phase + Math.PI, phase + Math.PI * 1.35); ctx.stroke(); ctx.lineCap = "butt"; // name + uid ctx.textAlign = "left"; ctx.textBaseline = "alphabetic"; ctx.fillStyle = "#ffffff"; ctx.font = "bold 38px sans-serif"; ctx.fillText(fitText(ctx, d.name, 440), 290, 110); ctx.fillStyle = "rgba(233,213,255,0.7)"; ctx.font = "18px sans-serif"; ctx.fillText("UID: " + d.uid, 290, 142); // stat boxes const stats = [ { label: "LEVEL", value: String(d.level) }, { label: "RANK", value: "#" + d.rank }, { label: "EXP", value: fmt(d.exp) }, { label: "MONEY", value: "$" + fmt(d.money) } ]; stats.forEach((s, i) => { const x = 290 + i * 115; const y = 175; roundRect(ctx, x, y, 105, 84, 16); ctx.fillStyle = "rgba(168,85,247,0.18)"; ctx.fill(); ctx.lineWidth = 1.5; ctx.strokeStyle = "rgba(192,132,252,0.45)"; ctx.stroke(); ctx.textAlign = "center"; ctx.fillStyle = "rgba(233,213,255,0.7)"; ctx.font = "13px sans-serif"; ctx.fillText(s.label, x + 52.5, y + 27); ctx.fillStyle = "#fff"; ctx.font = "bold 22px sans-serif"; ctx.fillText(fitText(ctx, s.value, 92), x + 52.5, y + 62); }); // exp bar const bx = 290, by = 308, bw = 450, bh = 24; ctx.textAlign = "left"; ctx.fillStyle = "rgba(233,213,255,0.8)"; ctx.font = "15px sans-serif"; ctx.fillText(`Progress to level ${d.level + 1}`, bx, by - 10); ctx.textAlign = "right"; ctx.fillText(`${fmt(d.expInLevel)} / ${fmt(d.expNeeded)}`, bx + bw, by - 10); roundRect(ctx, bx, by, bw, bh, 12); ctx.fillStyle = "rgba(255,255,255,0.1)"; ctx.fill(); const fillW = Math.max(bh, bw * d.progress); ctx.save(); roundRect(ctx, bx, by, bw, bh, 12); ctx.clip(); const barGrad = ctx.createLinearGradient(bx, 0, bx + bw, 0); barGrad.addColorStop(0, "#7c3aed"); barGrad.addColorStop(1, "#d946ef"); ctx.fillStyle = barGrad; ctx.fillRect(bx, by, fillW, bh); // shine sweeping across the bar const sx = bx - 80 + t * (bw + 160); const shine = ctx.createLinearGradient(sx, 0, sx + 80, 0); shine.addColorStop(0, "rgba(255,255,255,0)"); shine.addColorStop(0.5, "rgba(255,255,255,0.45)"); shine.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = shine; ctx.fillRect(bx, by, fillW, bh); ctx.restore(); ctx.textAlign = "center"; ctx.fillStyle = "#fff"; ctx.font = "bold 14px sans-serif"; ctx.fillText(Math.round(d.progress * 100) + "%", bx + bw / 2, by + 17); // footer ctx.textAlign = "right"; ctx.fillStyle = "rgba(233,213,255,0.45)"; ctx.font = "14px sans-serif"; ctx.fillText("GoatBot • Profile Card", W - 50, H - 44); } async function loadAvatar(uid) { try { const url = `https://graph.facebook.com/${uid}/picture?width=512&height=512&access_token=${AVATAR_TOKEN}`; const res = await axios.get(url, { responseType: "arraybuffer", timeout: 20000 }); return await loadImage(Buffer.from(res.data)); } catch (e) { return null; } } async function buildData(uid, usersData) { const delta = getDeltaNext(); const user = (await usersData.get(uid)) || {}; const exp = Number(user.exp) || 0; const money = Number(user.money) || 0; const level = Math.max(1, levelFromExp(exp, delta)); const curBase = expForLevel(level, delta); const nextBase = expForLevel(level + 1, delta); const expNeeded = Math.max(1, nextBase - curBase); const expInLevel = Math.max(0, exp - curBase); const progress = Math.min(1, expInLevel / expNeeded); let name = user.name; try { name = name || (await usersData.getName(uid)); } catch (e) { } name = name || "Unknown"; let rank = "-"; try { const all = await usersData.getAll(); const sorted = all.slice().sort((a, b) => (b.exp || 0) - (a.exp || 0)); const idx = sorted.findIndex(u => String(u.userID) === String(uid)); if (idx !== -1) rank = idx + 1; } catch (e) { } return { uid, name, exp, money, level, rank, expInLevel, expNeeded, progress }; } // ---------- command ---------- module.exports = { config: { name: "profilecard", aliases: ["pcard", "card"], version: "1.0", author: "Zihad", countDown: 10, role: 0, description: { en: "Animated profile card (level, rank, exp, money)", bn: "অ্যানিমেটেড প্রোফাইল কার্ড (লেভেল, র‍্যাংক, এক্সপি, মানি)" }, category: "info", guide: { en: "{pn}: your card\n{pn} @tag: tagged user's card\n{pn} : card by UID\nReply to someone's message with {pn} to see their card", bn: "{pn}: আপনার কার্ড\n{pn} @ট্যাগ: ট্যাগ করা ইউজারের কার্ড\n{pn} : UID দিয়ে কার্ড\nকারো মেসেজে রিপ্লাই দিয়ে {pn} লিখলে তার কার্ড আসবে" } }, onStart: async function ({ message, event, args, usersData }) { let uid = event.senderID; const mentioned = Object.keys(event.mentions || {}); if (mentioned.length) uid = mentioned[0]; else if (event.messageReply && event.messageReply.senderID) uid = event.messageReply.senderID; else if (args[0] && /^\d{5,}$/.test(args[0])) uid = args[0]; if (!createCanvas) return message.reply("❌ `canvas` প্যাকেজ ইনস্টল নেই। টার্মিনালে `npm i canvas` চালান।"); try { message.reaction("⏳", event.messageID); } catch (e) { } const tmpDir = path.join(__dirname, "tmp"); fs.ensureDirSync(tmpDir); const stamp = `${uid}_${Date.now()}`; try { const [data, avatar] = await Promise.all([ buildData(uid, usersData), loadAvatar(uid) ]); data.avatar = avatar; const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); // gifencoder না থাকলে স্ট্যাটিক PNG if (!GIFEncoder) { drawFrame(ctx, 0, data); const pngPath = path.join(tmpDir, `profilecard_${stamp}.png`); fs.writeFileSync(pngPath, canvas.toBuffer("image/png")); try { message.reaction("✅", event.messageID); } catch (e) { } return message.reply({ body: "ℹ️ অ্যানিমেশনের জন্য `npm i gifencoder` চালান।", attachment: fs.createReadStream(pngPath) }, () => fs.remove(pngPath)); } const gifPath = path.join(tmpDir, `profilecard_${stamp}.gif`); const encoder = new GIFEncoder(W, H); const out = fs.createWriteStream(gifPath); const finished = new Promise((resolve, reject) => { out.on("finish", resolve); out.on("error", reject); }); encoder.createReadStream().pipe(out); encoder.start(); encoder.setRepeat(0); encoder.setDelay(FRAME_DELAY); encoder.setQuality(10); for (let i = 0; i < FRAMES; i++) { drawFrame(ctx, i / FRAMES, data); encoder.addFrame(ctx); } encoder.finish(); await finished; try { message.reaction("✅", event.messageID); } catch (e) { } return message.reply({ body: `🪪 ${data.name} এর প্রোফাইল কার্ড`, attachment: fs.createReadStream(gifPath) }, () => fs.remove(gifPath)); } catch (err) { console.error("[profilecard.js]", err); try { message.reaction("❌", event.messageID); } catch (e) { } return message.reply("❌ কার্ড বানাতে সমস্যা হয়েছে। `canvas` ও `gifencoder` ইনস্টল আছে কিনা দেখুন।"); } } };