// code from goatstore // api: https://store.agi.bd const { createCanvas, loadImage } = require("canvas"); const GIFEncoder = require("gifencoder"); const fs = require("fs-extra"); const path = require("path"); const axios = require("axios"); const W = 800, H = 450, FRAMES = 36, DELAY = 60, COUNT_FRAMES = 22; const TAU = Math.PI * 2; const TIERS = [ { n: "BRONZE", min: 0, c: ["#f0b27a", "#8c5a2b"] }, { n: "SILVER", min: 1e4, c: ["#ffffff", "#9aa0ad"] }, { n: "GOLD", min: 1e5, c: ["#fff0a0", "#d19a10"] }, { n: "PLATINUM", min: 1e7, c: ["#eaf8ff", "#79b4d6"] }, { n: "DIAMOND", min: 1e9, c: ["#d6f6ff", "#8a7bff"] } ]; function fmtShort(n) { n = Number(n) || 0; for (const [v, s] of [[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]]) { if (Math.abs(n) >= v) return (n / v).toFixed(2).replace(/\.?0+$/, "") + s; } return String(Math.floor(n)); } const fmtFull = n => Math.floor(Number(n) || 0).toLocaleString("en-US"); function getTier(money) { let i = 0; for (let k = 0; k < TIERS.length; k++) if (money >= TIERS[k].min) i = k; const cur = TIERS[i], next = TIERS[i + 1] || null; const prog = next ? Math.min(1, (money - cur.min) / (next.min - cur.min)) : 1; return { cur, next, prog }; } function rr(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 spaced(ctx, text, x, y, sp, align = "left") { let w = 0; for (const ch of text) w += ctx.measureText(ch).width + sp; w -= sp; let cx = align === "center" ? x - w / 2 : align === "right" ? x - w : x; const old = ctx.textAlign; ctx.textAlign = "left"; for (const ch of text) { ctx.fillText(ch, cx, y); cx += ctx.measureText(ch).width + sp; } ctx.textAlign = old; } function star(ctx, x, y, r, a) { ctx.save(); ctx.globalAlpha = a; ctx.fillStyle = "#fff6c8"; ctx.beginPath(); for (let i = 0; i < 8; i++) { const ang = (i * Math.PI) / 4 - Math.PI / 2; const rad = i % 2 === 0 ? r : r * 0.22; ctx.lineTo(x + Math.cos(ang) * rad, y + Math.sin(ang) * rad); } ctx.closePath(); ctx.shadowColor = "#ffd75a"; ctx.shadowBlur = 8; ctx.fill(); ctx.restore(); } async function getAvatar(uid) { try { const url = `https://graph.facebook.com/${uid}/picture?width=256&height=256&access_token=6628568379|c1e620fa708a1d5696fb991c1bde5662`; const res = await axios.get(url, { responseType: "arraybuffer", timeout: 10000 }); return await loadImage(Buffer.from(res.data)); } catch (e) { return null; } } async function makeCard({ name, uid, money, avatar }) { const encoder = new GIFEncoder(W, H); const out = path.join(__dirname, "tmp", `balance_${uid}_${Date.now()}.gif`); await fs.ensureDir(path.dirname(out)); const stream = fs.createWriteStream(out); encoder.createReadStream().pipe(stream); encoder.start(); encoder.setRepeat(0); encoder.setDelay(DELAY); encoder.setQuality(14); const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); const layer = createCanvas(W, H); const lctx = layer.getContext("2d"); const tier = getTier(money); const dust = Array.from({ length: 34 }, (_, i) => ({ x: (i * 137) % W, y: (i * 71) % H, r: 1 + (i % 3) * 0.8, sp: 1 + (i % 4), ph: i * 0.7 })); const sparkles = [ [90, 70, 9], [735, 120, 7], [590, 215, 6], [710, 400, 8], [385, 70, 6], [255, 420, 7] ]; for (let f = 0; f < FRAMES; f++) { const t = f / FRAMES; // ---------- background ---------- const bg = ctx.createRadialGradient(W / 2, H / 2, 40, W / 2, H / 2, 520); bg.addColorStop(0, "#1a1426"); bg.addColorStop(0.6, "#0c0a14"); bg.addColorStop(1, "#040307"); ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H); // gold dust for (const d of dust) { const y = ((d.y - t * H * 0.35 * d.sp + H * 4) % H); const a = 0.15 + 0.35 * ((Math.sin(t * TAU * d.sp + d.ph) + 1) / 2); ctx.fillStyle = `rgba(255,215,100,${a})`; ctx.beginPath(); ctx.arc(d.x, y, d.r, 0, TAU); ctx.fill(); } // ---------- card body ---------- ctx.save(); ctx.shadowColor = "rgba(255,200,60,0.35)"; ctx.shadowBlur = 40; rr(ctx, 40, 40, 720, 370, 30); const cg = ctx.createLinearGradient(40, 40, 760, 410); cg.addColorStop(0, "#24202f"); cg.addColorStop(0.5, "#14111c"); cg.addColorStop(1, "#0b0910"); ctx.fillStyle = cg; ctx.fill(); ctx.restore(); ctx.save(); rr(ctx, 40, 40, 720, 370, 30); ctx.clip(); // guilloche rings ctx.lineWidth = 1; for (let r = 30; r < 330; r += 16) { ctx.strokeStyle = `rgba(212,175,55,${0.045 + 0.025 * Math.sin(t * TAU + r / 40)})`; ctx.beginPath(); ctx.arc(690, 260, r, 0, TAU); ctx.stroke(); } for (let r = 30; r < 220; r += 22) { ctx.strokeStyle = "rgba(212,175,55,0.035)"; ctx.beginPath(); ctx.arc(120, 380, r, 0, TAU); ctx.stroke(); } // fine diagonal lines ctx.strokeStyle = "rgba(255,255,255,0.018)"; for (let x = -H; x < W; x += 14) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x + H, H); ctx.stroke(); } // light sweep const sx = -300 + t * (W + 600); const sg = ctx.createLinearGradient(sx, 0, sx + 220, H); sg.addColorStop(0, "rgba(255,230,150,0)"); sg.addColorStop(0.5, "rgba(255,230,150,0.10)"); sg.addColorStop(1, "rgba(255,230,150,0)"); ctx.fillStyle = sg; ctx.fillRect(0, 0, W, H); ctx.restore(); // gold border const bgGrad = ctx.createLinearGradient(40, 40, 760, 410); bgGrad.addColorStop(0, "#fff0a0"); bgGrad.addColorStop(0.35, "#c9972b"); bgGrad.addColorStop(0.65, "#fff0a0"); bgGrad.addColorStop(1, "#9c6f12"); ctx.lineWidth = 2.5; ctx.strokeStyle = bgGrad; rr(ctx, 40, 40, 720, 370, 30); ctx.stroke(); ctx.lineWidth = 1; ctx.strokeStyle = "rgba(255,215,100,0.25)"; rr(ctx, 48, 48, 704, 354, 24); ctx.stroke(); // ---------- chip ---------- const chg = ctx.createLinearGradient(80, 80, 138, 124); chg.addColorStop(0, "#fff0a0"); chg.addColorStop(0.5, "#d9a62a"); chg.addColorStop(1, "#8f6410"); rr(ctx, 80, 80, 58, 44, 8); ctx.fillStyle = chg; ctx.fill(); ctx.strokeStyle = "rgba(90,55,0,0.7)"; ctx.lineWidth = 1; ctx.stroke(); ctx.beginPath(); ctx.moveTo(80, 102); ctx.lineTo(138, 102); ctx.moveTo(109, 80); ctx.lineTo(109, 124); ctx.moveTo(95, 80); ctx.lineTo(95, 92); ctx.lineTo(80, 92); ctx.moveTo(123, 80); ctx.lineTo(123, 92); ctx.lineTo(138, 92); ctx.moveTo(95, 124); ctx.lineTo(95, 112); ctx.lineTo(80, 112); ctx.moveTo(123, 124); ctx.lineTo(123, 112); ctx.lineTo(138, 112); ctx.stroke(); // contactless ctx.strokeStyle = "rgba(255,215,100,0.8)"; ctx.lineWidth = 2.5; ctx.lineCap = "round"; for (let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc(160 + i * 2, 102, 8 + i * 7, -0.9, 0.9); ctx.stroke(); } // header ctx.fillStyle = "#e9c25a"; ctx.font = "bold 15px sans-serif"; ctx.textBaseline = "alphabetic"; spaced(ctx, "PREMIUM WALLET", 215, 108, 5); // tier badge const bx = 590, by = 76, bw = 140, bh = 34; const tg = ctx.createLinearGradient(bx, by, bx + bw, by + bh); tg.addColorStop(0, tier.cur.c[0]); tg.addColorStop(1, tier.cur.c[1]); ctx.save(); ctx.shadowColor = tier.cur.c[0]; ctx.shadowBlur = 14 + 6 * Math.sin(t * TAU); rr(ctx, bx, by, bw, bh, 17); ctx.fillStyle = tg; ctx.fill(); ctx.restore(); ctx.fillStyle = "#1a1206"; ctx.font = "bold 15px sans-serif"; spaced(ctx, tier.cur.n, bx + bw / 2, by + 22, 3, "center"); // ---------- avatar with spinning metallic ring ---------- const ax = 150, ay = 250, ar = 62; const SEG = 72; ctx.lineWidth = 7; ctx.lineCap = "butt"; for (let i = 0; i < SEG; i++) { const a0 = (i / SEG) * TAU; const b = (Math.cos(a0 - t * TAU) + 1) / 2; const L = 35 + b * 52; ctx.strokeStyle = `hsl(${42 + b * 6}, 85%, ${L}%)`; ctx.beginPath(); ctx.arc(ax, ay, ar + 12, a0, a0 + TAU / SEG + 0.02); ctx.stroke(); } ctx.save(); ctx.beginPath(); ctx.arc(ax, ay, ar + 3, 0, TAU); ctx.fillStyle = "#0b0910"; ctx.fill(); ctx.beginPath(); ctx.arc(ax, ay, ar, 0, TAU); ctx.clip(); if (avatar) ctx.drawImage(avatar, ax - ar, ay - ar, ar * 2, ar * 2); else { ctx.fillStyle = "#33406b"; ctx.fillRect(ax - ar, ay - ar, ar * 2, ar * 2); } ctx.restore(); // holder name + masked uid ctx.textAlign = "center"; ctx.fillStyle = "rgba(233,194,90,0.65)"; ctx.font = "11px sans-serif"; spaced(ctx, "CARD HOLDER", ax, 352, 3, "center"); let nm = String(name).toUpperCase(); if (nm.length > 15) nm = nm.slice(0, 14) + "…"; ctx.fillStyle = "#ffffff"; ctx.font = "bold 20px sans-serif"; ctx.fillText(nm, ax, 376); const u = String(uid); const masked = "•••• " + u.slice(-4); ctx.fillStyle = "rgba(255,255,255,0.5)"; ctx.font = "13px sans-serif"; ctx.fillText(masked, ax, 396); ctx.textAlign = "left"; // ---------- balance ---------- const p = Math.min(1, f / COUNT_FRAMES); const ease = 1 - Math.pow(1 - p, 3); const cur = money * ease; ctx.fillStyle = "rgba(233,194,90,0.75)"; ctx.font = "bold 13px sans-serif"; spaced(ctx, "AVAILABLE BALANCE", 285, 168, 4); // text layer with gold gradient + moving shine lctx.clearRect(0, 0, W, H); let size = 92; const txt = fmtShort(cur) + "$"; lctx.font = `bold ${size}px sans-serif`; while (lctx.measureText(txt).width > 440 && size > 34) { size -= 4; lctx.font = `bold ${size}px sans-serif`; } const ty = 260; const tgd = lctx.createLinearGradient(0, ty - size, 0, ty); tgd.addColorStop(0, "#fff8cf"); tgd.addColorStop(0.45, "#f6c744"); tgd.addColorStop(1, "#b07a0c"); lctx.fillStyle = tgd; lctx.textBaseline = "alphabetic"; lctx.fillText(txt, 285, ty); lctx.globalCompositeOperation = "source-atop"; const shx = 200 + ((t * 2) % 1) * 560; const shg = lctx.createLinearGradient(shx - 70, 0, shx + 70, 0); shg.addColorStop(0, "rgba(255,255,255,0)"); shg.addColorStop(0.5, "rgba(255,255,255,0.85)"); shg.addColorStop(1, "rgba(255,255,255,0)"); lctx.fillStyle = shg; lctx.fillRect(0, ty - size - 10, W, size + 30); lctx.globalCompositeOperation = "source-over"; ctx.save(); ctx.shadowColor = "rgba(255,190,50,0.65)"; ctx.shadowBlur = 22; ctx.drawImage(layer, 0, 0); ctx.restore(); ctx.fillStyle = "rgba(255,255,255,0.7)"; ctx.font = "19px sans-serif"; ctx.fillText(`${fmtFull(cur)} coins`, 287, 295); // ---------- tier progress ---------- const px = 285, py = 328, pw = 440, ph = 12; rr(ctx, px, py, pw, ph, 6); ctx.fillStyle = "rgba(255,255,255,0.08)"; ctx.fill(); const fill = Math.max(ph, pw * tier.prog * ease); rr(ctx, px, py, fill, ph, 6); const pg = ctx.createLinearGradient(px, 0, px + pw, 0); pg.addColorStop(0, "#b07a0c"); pg.addColorStop(0.6, "#f6c744"); pg.addColorStop(1, "#fff3b0"); ctx.fillStyle = pg; ctx.fill(); // glint on bar ctx.save(); rr(ctx, px, py, fill, ph, 6); ctx.clip(); const gx = px + (t * 1.5 % 1) * (fill + 80) - 40; const gg = ctx.createLinearGradient(gx - 25, 0, gx + 25, 0); gg.addColorStop(0, "rgba(255,255,255,0)"); gg.addColorStop(0.5, "rgba(255,255,255,0.7)"); gg.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = gg; ctx.fillRect(px, py, pw, ph); ctx.restore(); ctx.font = "bold 12px sans-serif"; ctx.fillStyle = "rgba(233,194,90,0.8)"; const pct = Math.floor(tier.prog * ease * 100); spaced(ctx, tier.next ? `TIER PROGRESS ${pct}%` : "MAX TIER REACHED", px, py + 32, 2); ctx.fillStyle = "rgba(255,255,255,0.55)"; spaced(ctx, tier.next ? `NEXT: ${tier.next.n}` : "★ ★ ★", px + pw, py + 32, 2, "right"); // ---------- sparkles ---------- for (const [sx2, sy2, sr] of sparkles) { const a = Math.max(0, Math.sin(t * TAU * 2 + sx2)); if (a > 0.05) star(ctx, sx2, sy2, sr * (0.6 + a * 0.6), a); } encoder.addFrame(ctx); } encoder.finish(); await new Promise((res, rej) => { stream.on("finish", res); stream.on("error", rej); }); return out; } module.exports = { config: { name: "balance", aliases: ["bal", "money", "wallet"], version: "2.0", author: "Zihad", countDown: 8, role: 0, description: { en: "Check balance with a premium animated black-gold wallet card", bn: "প্রিমিয়াম অ্যানিমেটেড ওয়ালেট কার্ডে ব্যালেন্স দেখো" }, category: "economy", guide: { en: "{pn}: your balance\n{pn} @tag: tagged user's balance\n{pn} : balance by UID\nReply to a message with {pn} to see that user's balance" } }, onStart: async function ({ message, event, args, usersData }) { let uid = event.senderID; if (event.type === "message_reply") uid = event.messageReply.senderID; else if (Object.keys(event.mentions || {}).length > 0) uid = Object.keys(event.mentions)[0]; else if (args[0] && /^\d{5,}$/.test(args[0])) uid = args[0]; let file; try { const money = (await usersData.get(uid, "money")) || 0; const name = (await usersData.getName(uid)) || "User"; const avatar = await getAvatar(uid); file = await makeCard({ name, uid, money, avatar }); return message.reply({ body: `💰 ${name}\nBalance: ${fmtFull(money)}$ (${fmtShort(money)}$)`, attachment: fs.createReadStream(file) }, () => fs.remove(file).catch(() => { })); } catch (err) { console.error("[balance.js]", err); if (file) fs.remove(file).catch(() => { }); const money = (await usersData.get(uid, "money").catch(() => 0)) || 0; return message.reply(`💰 Balance: ${fmtFull(money)}$`); } } };