// code from goatstore // api: https://store.agi.bd const axios = require("axios"); const fs = require("fs-extra"); const path = require("path"); const moment = require("moment-timezone"); let Canvas = null; try { Canvas = require("canvas"); } catch (e) { try { Canvas = require("@napi-rs/canvas"); } catch (e2) { Canvas = null; } } const W = 760; const H = 420; const FRAMES = 20; const DELAY = 90; const TAU = Math.PI * 2; /* ------------------------------ helpers ------------------------------ */ function makeEncoder(w, h) { try { const GIFEncoder = require("gif-encoder-2"); return new GIFEncoder(w, h, "neuquant", false); } catch (e) { /* try next */ } try { const GIFEncoder = require("gifencoder"); return new GIFEncoder(w, h); } catch (e) { /* none */ } return null; } function rr(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 glow(ctx, x, y, r, rgb, a) { const g = ctx.createRadialGradient(x, y, 0, x, y, r); g.addColorStop(0, `rgba(${rgb},${a})`); g.addColorStop(1, `rgba(${rgb},0)`); ctx.fillStyle = g; ctx.fillRect(x - r, y - r, r * 2, r * 2); } function fit(ctx, text, maxW) { text = String(text); 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 mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; } // fixed particles so every frame (and the loop) is consistent const rand = mulberry32(20261007); const PARTICLES = Array.from({ length: 46 }, () => ({ x: rand() * W, y0: rand() * H, r: 0.8 + rand() * 2.2, k: 1 + Math.floor(rand() * 2), // integer → seamless loop k2: 1 + Math.floor(rand() * 3), ph: rand() * TAU, a: 0.25 + rand() * 0.5 })); /* ------------------------------ drawing ------------------------------ */ function drawFrame(ctx, t, d) { /* ---- background ---- */ const bg = ctx.createLinearGradient(0, 0, W, H); bg.addColorStop(0, "#0a0618"); bg.addColorStop(0.5, "#1b0a3c"); bg.addColorStop(1, "#071b3a"); ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H); glow(ctx, W * 0.25 + Math.cos(t * TAU) * 40, H * 0.5 + Math.sin(t * TAU) * 30, 260, "139,92,246", 0.38); glow(ctx, W * 0.8 + Math.sin(t * TAU) * 50, H * 0.2 + Math.cos(t * TAU) * 30, 240, "34,211,238", 0.22); glow(ctx, W * 0.7, H * 0.95 + Math.sin(t * TAU) * 20, 220, "236,72,153", 0.2); /* ---- moving grid ---- */ const off = (t * 40) % 40; ctx.lineWidth = 1; ctx.strokeStyle = "rgba(167,139,250,0.06)"; ctx.beginPath(); for (let x = -40 + off; x < W; x += 40) { ctx.moveTo(x, 0); ctx.lineTo(x, H); } for (let y = -40 + off; y < H; y += 40) { ctx.moveTo(0, y); ctx.lineTo(W, y); } ctx.stroke(); /* ---- particles ---- */ for (const p of PARTICLES) { const y = (((p.y0 - t * H * p.k) % H) + H) % H; const a = p.a * (0.5 + 0.5 * Math.sin(t * TAU * p.k2 + p.ph)); ctx.fillStyle = `rgba(196,181,253,${a.toFixed(3)})`; ctx.beginPath(); ctx.arc(p.x, y, p.r, 0, TAU); ctx.fill(); } /* ---- scanner line ---- */ const sy = t * H; const sg = ctx.createLinearGradient(0, sy - 50, 0, sy + 2); sg.addColorStop(0, "rgba(167,139,250,0)"); sg.addColorStop(1, "rgba(167,139,250,0.14)"); ctx.fillStyle = sg; ctx.fillRect(0, sy - 50, W, 52); /* ---- avatar section ---- */ const cx = 190, cy = 190, R = 86; // tick marks with a travelling light for (let i = 0; i < 48; i++) { const ang = (i / 48) * TAU; const sweep = 1 - (((i / 48 - t) % 1) + 1) % 1; const a = 0.12 + 0.75 * Math.pow(sweep, 3); const r1 = R + 44, r2 = R + 52; ctx.strokeStyle = `rgba(34,211,238,${a.toFixed(3)})`; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(cx + Math.cos(ang) * r1, cy + Math.sin(ang) * r1); ctx.lineTo(cx + Math.cos(ang) * r2, cy + Math.sin(ang) * r2); ctx.stroke(); } // dashed rotating ring ctx.save(); ctx.translate(cx, cy); ctx.rotate(t * TAU); ctx.setLineDash([14, 10]); ctx.strokeStyle = "rgba(34,211,238,0.65)"; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(0, 0, R + 30, 0, TAU); ctx.stroke(); ctx.restore(); ctx.setLineDash([]); // counter-rotating glowing arcs ctx.save(); ctx.translate(cx, cy); ctx.rotate(-t * TAU * 2); const arcCols = ["#a78bfa", "#22d3ee", "#f472b6"]; for (let i = 0; i < 3; i++) { ctx.strokeStyle = arcCols[i]; ctx.shadowColor = arcCols[i]; ctx.shadowBlur = 14; ctx.lineWidth = 4; ctx.lineCap = "round"; ctx.beginPath(); ctx.arc(0, 0, R + 17, i * (TAU / 3), i * (TAU / 3) + 1.05); ctx.stroke(); } ctx.restore(); ctx.shadowBlur = 0; ctx.lineCap = "butt"; // solid inner ring with pulsing glow const ring = ctx.createLinearGradient(cx - R, cy - R, cx + R, cy + R); ring.addColorStop(0, "#8b5cf6"); ring.addColorStop(0.5, "#22d3ee"); ring.addColorStop(1, "#ec4899"); ctx.save(); ctx.shadowColor = "#8b5cf6"; ctx.shadowBlur = 14 + 10 * Math.sin(t * TAU); ctx.strokeStyle = ring; ctx.lineWidth = 5; ctx.beginPath(); ctx.arc(cx, cy, R + 5, 0, TAU); ctx.stroke(); ctx.restore(); ctx.shadowBlur = 0; // avatar (clipped circle, soft breathing zoom) ctx.save(); ctx.beginPath(); ctx.arc(cx, cy, R, 0, TAU); ctx.clip(); if (d.avatar) { const z = 1 + 0.025 * Math.sin(t * TAU); const s = R * 2 * z; ctx.drawImage(d.avatar, cx - s / 2, cy - s / 2, s, s); } else { const ph = ctx.createLinearGradient(cx - R, cy - R, cx + R, cy + R); ph.addColorStop(0, "#4c1d95"); ph.addColorStop(1, "#0e7490"); ctx.fillStyle = ph; ctx.fillRect(cx - R, cy - R, R * 2, R * 2); ctx.fillStyle = "#fff"; ctx.font = "bold 72px sans-serif"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(String(d.name || "?").trim().charAt(0).toUpperCase(), cx, cy + 4); ctx.textBaseline = "alphabetic"; } // glass shine sweeping over the avatar const shx = cx - R - 60 + (t * (R * 2 + 120)); const sh = ctx.createLinearGradient(shx - 30, 0, shx + 30, 0); sh.addColorStop(0, "rgba(255,255,255,0)"); sh.addColorStop(0.5, "rgba(255,255,255,0.18)"); sh.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = sh; ctx.fillRect(cx - R, cy - R, R * 2, R * 2); ctx.restore(); // name ctx.textAlign = "center"; ctx.font = "bold 24px sans-serif"; ctx.save(); ctx.shadowColor = "rgba(139,92,246,0.9)"; ctx.shadowBlur = 12; ctx.fillStyle = "#ffffff"; ctx.fillText(fit(ctx, d.name, 320), cx, 326); ctx.restore(); // UID pill ctx.font = "bold 14px monospace"; const uidTxt = "ID " + d.uid; const pw0 = Math.min(ctx.measureText(uidTxt).width + 34, 320); rr(ctx, cx - pw0 / 2, 342, pw0, 28, 14); ctx.fillStyle = "rgba(139,92,246,0.22)"; ctx.fill(); ctx.strokeStyle = "rgba(167,139,250,0.7)"; ctx.lineWidth = 1.5; ctx.stroke(); ctx.fillStyle = "#e9d5ff"; ctx.fillText(fit(ctx, uidTxt, pw0 - 20), cx, 361); /* ---- info panel ---- */ const px = 355, py = 28, pw = 380, ph = 364; // glass body rr(ctx, px, py, pw, ph, 22); ctx.fillStyle = "rgba(255,255,255,0.055)"; ctx.fill(); ctx.lineWidth = 1.5; ctx.strokeStyle = "rgba(255,255,255,0.13)"; ctx.stroke(); // travelling bright border const bx = px + (t * 2 - 1) * pw; const bg2 = ctx.createLinearGradient(bx, 0, bx + pw, 0); bg2.addColorStop(0, "rgba(34,211,238,0)"); bg2.addColorStop(0.35, "rgba(34,211,238,0.95)"); bg2.addColorStop(0.65, "rgba(167,139,250,0.95)"); bg2.addColorStop(1, "rgba(236,72,153,0)"); ctx.save(); ctx.shadowColor = "#a78bfa"; ctx.shadowBlur = 10; rr(ctx, px, py, pw, ph, 22); ctx.strokeStyle = bg2; ctx.lineWidth = 2.5; ctx.stroke(); ctx.restore(); ctx.shadowBlur = 0; // title ctx.textAlign = "left"; rr(ctx, px + 22, py + 22, 5, 36, 3); const tb = ctx.createLinearGradient(0, py + 22, 0, py + 58); tb.addColorStop(0, "#22d3ee"); tb.addColorStop(1, "#a78bfa"); ctx.fillStyle = tb; ctx.fill(); ctx.font = "bold 20px sans-serif"; ctx.fillStyle = "#ffffff"; ctx.fillText("USER INFORMATION", px + 38, py + 40); ctx.font = "11px sans-serif"; ctx.fillStyle = "#67e8f9"; ctx.fillText("PROFILE SCAN • VERIFIED", px + 38, py + 56); // shimmering underline const ux = px + 22, uw = pw - 44, uy = py + 66; ctx.fillStyle = "rgba(255,255,255,0.1)"; ctx.fillRect(ux, uy, uw, 2); const sx = ux + (t * (uw + 120)) - 120; const ug = ctx.createLinearGradient(sx, 0, sx + 120, 0); ug.addColorStop(0, "rgba(34,211,238,0)"); ug.addColorStop(0.5, "rgba(34,211,238,1)"); ug.addColorStop(1, "rgba(34,211,238,0)"); ctx.save(); ctx.beginPath(); ctx.rect(ux, uy - 2, uw, 6); ctx.clip(); ctx.fillStyle = ug; ctx.fillRect(sx, uy - 1, 120, 4); ctx.restore(); // rows const rows = [ { label: "GENDER", value: d.gender, col: "#f472b6" }, { label: "BIRTHDAY", value: d.birthday, col: "#fbbf24" }, { label: "USERNAME", value: d.username, col: "#a78bfa" }, { label: "BOT FRIEND", value: d.isFriend, col: d.isFriendBool ? "#4ade80" : "#f87171" }, { label: "MONEY", value: d.money, col: "#facc15" }, { label: "EXP", value: d.exp, col: "#22d3ee" }, { label: "PROFILE", value: d.profile, col: "#60a5fa" } ]; const rowH = 38, rowY0 = py + 80; const phase = t * rows.length; const active = Math.floor(phase) % rows.length; const frac = phase - Math.floor(phase); rows.forEach((r, i) => { const y = rowY0 + i * rowH; rr(ctx, px + 16, y, pw - 32, rowH - 6, 10); ctx.fillStyle = "rgba(255,255,255,0.045)"; ctx.fill(); if (i === active) { rr(ctx, px + 16, y, pw - 32, rowH - 6, 10); ctx.fillStyle = `rgba(139,92,246,${(0.28 * (1 - frac)).toFixed(3)})`; ctx.fill(); ctx.strokeStyle = `rgba(167,139,250,${(0.8 * (1 - frac)).toFixed(3)})`; ctx.lineWidth = 1.5; ctx.stroke(); } // dot ctx.save(); ctx.shadowColor = r.col; ctx.shadowBlur = i === active ? 12 * (1 - frac) + 4 : 4; ctx.fillStyle = r.col; ctx.beginPath(); ctx.arc(px + 34, y + (rowH - 6) / 2, 5, 0, TAU); ctx.fill(); ctx.restore(); ctx.shadowBlur = 0; // label ctx.font = "bold 11px sans-serif"; ctx.fillStyle = "rgba(196,181,253,0.85)"; ctx.fillText(r.label, px + 48, y + 21); // value ctx.font = "bold 15px sans-serif"; ctx.fillStyle = "#ffffff"; ctx.fillText(fit(ctx, r.value, pw - 32 - 128 - 14), px + 150, y + 21); }); /* ---- HUD corners ---- */ const ca = 0.45 + 0.35 * Math.sin(t * TAU); ctx.strokeStyle = `rgba(34,211,238,${ca.toFixed(3)})`; ctx.lineWidth = 3; const L = 22, m = 10; ctx.beginPath(); ctx.moveTo(m, m + L); ctx.lineTo(m, m); ctx.lineTo(m + L, m); ctx.moveTo(W - m - L, m); ctx.lineTo(W - m, m); ctx.lineTo(W - m, m + L); ctx.moveTo(m, H - m - L); ctx.lineTo(m, H - m); ctx.lineTo(m + L, H - m); ctx.moveTo(W - m - L, H - m); ctx.lineTo(W - m, H - m); ctx.lineTo(W - m, H - m - L); ctx.stroke(); /* ---- footer ---- */ ctx.textAlign = "center"; ctx.font = "11px sans-serif"; ctx.fillStyle = "rgba(196,181,253,0.7)"; ctx.fillText(`GOATBOT • USER INFO • ${d.time}`, W / 2, H - 6); ctx.textAlign = "left"; } /* ------------------------------ GIF maker ------------------------------ */ async function buildGif(data, avatarBuffer) { if (!Canvas) throw new Error("NO_CANVAS"); const encoder = makeEncoder(W, H); if (!encoder) throw new Error("NO_ENCODER"); const { createCanvas, loadImage } = Canvas; const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); let avatar = null; if (avatarBuffer) { try { avatar = await loadImage(avatarBuffer); } catch (e) { avatar = null; } } data.avatar = avatar; encoder.start(); encoder.setRepeat(0); encoder.setDelay(DELAY); encoder.setQuality(20); for (let i = 0; i < FRAMES; i++) { drawFrame(ctx, i / FRAMES, data); encoder.addFrame(ctx); await new Promise(r => setImmediate(r)); } encoder.finish(); return encoder.out.getData(); } /* ------------------------------ command ------------------------------ */ module.exports = { config: { name: "userinfo", aliases: ["uinfo", "whois"], version: "2.0", author: "Zihad", countDown: 10, role: 0, description: { en: "Premium animated GIF card with user info" }, category: "info", guide: { en: "{pn} → your info" + "\n{pn} @tag → tagged user's info" + "\n{pn} → info by UID" + "\n(reply to a message) {pn} → replied user's info" } }, langs: { en: { error: "❌ Could not fetch info for this user.", male: "Male", female: "Female", unknown: "Unknown", yes: "Yes", no: "No" } }, onStart: async function ({ api, event, args, message, usersData, getLang }) { let tmpFile = null; try { // target uid: reply > mention > uid arg > sender let uid; 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]; else uid = event.senderID; api.setMessageReaction("⏳", event.messageID, () => { }, true); const userInfo = await api.getUserInfo(uid); const info = userInfo[uid]; if (!info) { api.setMessageReaction("❌", event.messageID, () => { }, true); return message.reply(getLang("error")); } let db = {}; try { db = await usersData.get(uid) || {}; } catch (e) { /* not in DB */ } const name = info.name || db.name || getLang("unknown"); const gender = info.gender == 2 ? getLang("male") : info.gender == 1 ? getLang("female") : getLang("unknown"); const username = info.vanity && info.vanity !== "0" ? info.vanity : "None"; const profileUrl = info.profileUrl || `https://www.facebook.com/profile.php?id=${uid}`; const birthday = info.isBirthday ? "Today!" : (info.birthday || getLang("unknown")); const isFriendBool = !!info.isFriend; const money = Number(db.money || 0); const exp = Number(db.exp || 0); const time = moment.tz("Asia/Dhaka").format("DD/MM/YYYY hh:mm A"); const data = { name, uid, gender, birthday, username, isFriend: isFriendBool ? getLang("yes") : getLang("no"), isFriendBool, money: money.toLocaleString("en-US"), exp: exp.toLocaleString("en-US"), profile: profileUrl.replace(/^https?:\/\/(www\.)?/, ""), time }; const caption = `👤 ${name}\n🆔 ${uid}\n🔗 ${profileUrl}`; // avatar let avatarBuffer = null; try { const url = `https://graph.facebook.com/${uid}/picture?width=512&height=512&access_token=6628568379%7Cc1e620fa708a1d5696fb991c1bde5662`; const res = await axios.get(url, { responseType: "arraybuffer", timeout: 15000 }); avatarBuffer = Buffer.from(res.data); } catch (e) { avatarBuffer = null; } // build animated card try { const gif = await buildGif(data, avatarBuffer); const dir = path.join(__dirname, "cache"); fs.ensureDirSync(dir); tmpFile = path.join(dir, `userinfo_${uid}_${Date.now()}.gif`); fs.writeFileSync(tmpFile, gif); api.setMessageReaction("✅", event.messageID, () => { }, true); return message.reply({ body: caption, attachment: fs.createReadStream(tmpFile) }, () => { try { fs.unlinkSync(tmpFile); } catch (e) { } }); } catch (e) { console.error("[userinfo] gif error:", e && e.message ? e.message : e); // fallback: plain text (+ avatar if we have it) const text = "╭─────『 USER INFO 』─────╮\n" + `│ 👤 Name: ${name}\n` + `│ 🆔 UID: ${uid}\n` + `│ 🚻 Gender: ${gender}\n` + `│ 🎂 Birthday: ${birthday}\n` + `│ 🔖 Username: ${username}\n` + `│ 🤝 Bot friend: ${data.isFriend}\n` + `│ 💰 Money: ${data.money}\n` + `│ ⭐ EXP: ${data.exp}\n` + `│ 🔗 Profile: ${profileUrl}\n` + "╰────────────────────╯" + (e && (e.message === "NO_ENCODER" || e.message === "NO_CANVAS") ? "\n\n⚠️ GIF card needs: npm i canvas gif-encoder-2" : ""); api.setMessageReaction("✅", event.messageID, () => { }, true); return message.reply(text); } } catch (err) { console.error("[userinfo]", err); try { api.setMessageReaction("❌", event.messageID, () => { }, true); } catch (e) { } if (tmpFile) { try { fs.unlinkSync(tmpFile); } catch (e) { } } return message.reply(getLang("error")); } } };