// 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 DW = 2000; // ডিজাইনের মাপ (সব পজিশন এই মাপে ধরা) const DH = 1100; const SCALE = 0.5; // আউটপুট = ডিজাইন × SCALE (0.5 → 1000×550)। কমালে দ্রুত, বাড়ালে ঝকঝকে const FRAMES = 16; // বাড়ালে স্মুথ, কিন্তু ধীর const FRAME_DELAY = 120; // ms const MAX_CARDS = 2; // একসাথে বেশি জন জয়েন করলে সর্বোচ্চ কয়টা কার্ড const BATCH_WAIT = 1500; // ms, একসাথে জয়েন করা মেম্বারদের জমা করার সময় const CREDIT = "Zihad"; // কার্ডের নিচে "Powered by ..." এ যা দেখাবে // ============================================================ const W = Math.round(DW * SCALE); const H = Math.round(DH * SCALE); const TAU = Math.PI * 2; const FONT = '"Segoe UI", Arial, sans-serif'; const AVATAR_TOKEN = "6628568379%7Cc1e620fa708a1d5696fb991c1bde5662"; // shadowBlur স্কেলের সাথে বদলায় না, তাই নিজেরা স্কেল করে নিই const sb = v => v * SCALE; const DEFAULT_MESSAGE = "👋 স্বাগতম {userName}!\n" + "🏠 {boxName} গ্রুপে তোমাকে অভিনন্দন।\n" + "👥 তুমি এই গ্রুপের {memberCount} নম্বর মেম্বার। ভালো সময় কাটুক! 💜"; // ---------- helpers ---------- // স্থির র‍্যান্ডম (ব্যাকগ্রাউন্ড একবার বানিয়ে ক্যাশ হয়, তাই প্রতিবার একই থাকবে) function rng(seed) { let a = seed >>> 0; return function () { a = (a + 0x6D2B79F5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } function roundedRect(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 + "…"; } // ফন্ট সাইজ কমিয়ে টেক্সটকে maxW-র মধ্যে আনে (ctx.font সেট করে দেয়) function fitFont(ctx, text, maxW, start, min) { let size = start; while (size > min) { ctx.font = `bold ${size}px ${FONT}`; if (ctx.measureText(text).width <= maxW) return size; size -= 2; } ctx.font = `bold ${min}px ${FONT}`; return min; } // ================= স্ট্যাটিক এনিমে ব্যাকগ্রাউন্ড ================= function drawAnimeMountains(ctx, width, height) { ctx.save(); ctx.fillStyle = "rgba(8,12,42,0.92)"; ctx.beginPath(); ctx.moveTo(0, height * 0.67); ctx.lineTo(width * 0.18, height * 0.48); ctx.lineTo(width * 0.34, height * 0.65); ctx.lineTo(width * 0.52, height * 0.43); ctx.lineTo(width * 0.72, height * 0.64); ctx.lineTo(width * 0.88, height * 0.5); ctx.lineTo(width, height * 0.65); ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.closePath(); ctx.fill(); ctx.strokeStyle = "rgba(104,135,255,0.18)"; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(0, height * 0.67); ctx.lineTo(width * 0.18, height * 0.48); ctx.lineTo(width * 0.34, height * 0.65); ctx.lineTo(width * 0.52, height * 0.43); ctx.lineTo(width * 0.72, height * 0.64); ctx.lineTo(width * 0.88, height * 0.5); ctx.lineTo(width, height * 0.65); ctx.stroke(); ctx.restore(); } function drawAnimeCity(ctx, width, height, rand) { ctx.save(); const baseY = height * 0.72; for (let i = 0; i < 34; i++) { const buildingWidth = 35 + rand() * 65; const buildingHeight = 80 + rand() * 190; const x = i * 63 + rand() * 20; const y = baseY - buildingHeight; ctx.fillStyle = i % 2 === 0 ? "rgba(7,11,34,0.95)" : "rgba(12,15,43,0.96)"; ctx.fillRect(x, y, buildingWidth, buildingHeight); for (let wy = y + 25; wy < baseY - 15; wy += 28) { if (rand() > 0.35) { ctx.fillStyle = rand() > 0.5 ? "rgba(80,220,255,0.75)" : "rgba(182,102,255,0.65)"; ctx.fillRect(x + 10, wy, 8, 12); } } } ctx.fillStyle = "rgba(4,7,20,0.98)"; ctx.fillRect(0, baseY, width, height - baseY); ctx.restore(); } function drawAnimeTrees(ctx, width, height, rand) { ctx.save(); const treePositions = [[110, 730], [280, 700], [1680, 710], [1880, 735]]; for (const [x, y] of treePositions) { ctx.fillStyle = "#070a20"; ctx.fillRect(x - 10, y - 180, 20, 180); for (let i = 0; i < 18; i++) { const px = x + (rand() - 0.5) * 170; const py = y - 130 - rand() * 150; const size = 25 + rand() * 45; const glow = ctx.createRadialGradient(px, py, 2, px, py, size); glow.addColorStop(0, "rgba(255,120,215,0.65)"); glow.addColorStop(0.45, "rgba(177,92,255,0.28)"); glow.addColorStop(1, "rgba(100,70,255,0)"); ctx.fillStyle = glow; ctx.beginPath(); ctx.arc(px, py, size, 0, TAU); ctx.fill(); } } ctx.restore(); } function drawAnimeParticles(ctx, width, height, rand) { ctx.save(); for (let i = 0; i < 90; i++) { const x = rand() * width; const y = rand() * height; const r = 1 + rand() * 3; ctx.fillStyle = i % 3 === 0 ? "rgba(255,140,225,0.8)" : i % 3 === 1 ? "rgba(93,225,255,0.7)" : "rgba(190,150,255,0.65)"; ctx.shadowColor = ctx.fillStyle; ctx.shadowBlur = sb(12); ctx.beginPath(); ctx.arc(x, y, r, 0, TAU); ctx.fill(); } ctx.restore(); } function drawAnimeBackground(ctx, width, height, rand) { const sky = ctx.createLinearGradient(0, 0, 0, height); sky.addColorStop(0, "#070b25"); sky.addColorStop(0.35, "#11194a"); sky.addColorStop(0.62, "#35205a"); sky.addColorStop(0.82, "#10152f"); sky.addColorStop(1, "#030510"); ctx.fillStyle = sky; ctx.fillRect(0, 0, width, height); // চাঁদের গ্লো const moonGlow = ctx.createRadialGradient(width * 0.77, height * 0.22, 10, width * 0.77, height * 0.22, 430); moonGlow.addColorStop(0, "rgba(180,220,255,0.34)"); moonGlow.addColorStop(0.35, "rgba(100,150,255,0.13)"); moonGlow.addColorStop(1, "rgba(80,100,255,0)"); ctx.fillStyle = moonGlow; ctx.fillRect(0, 0, width, height); // চাঁদ ctx.save(); ctx.shadowColor = "#d9edff"; ctx.shadowBlur = sb(35); ctx.fillStyle = "#edf6ff"; ctx.beginPath(); ctx.arc(width * 0.77, height * 0.22, 82, 0, TAU); ctx.fill(); ctx.restore(); // মেঘ const cloudGradient = ctx.createLinearGradient(0, height * 0.3, 0, height * 0.58); cloudGradient.addColorStop(0, "rgba(110,125,210,0.06)"); cloudGradient.addColorStop(1, "rgba(20,20,70,0)"); ctx.fillStyle = cloudGradient; for (let i = 0; i < 12; i++) { const x = (i / 12) * width - 150; const y = height * 0.38 + Math.sin(i * 1.8) * 45; ctx.beginPath(); ctx.ellipse(x, y, 260, 55, 0, 0, TAU); ctx.fill(); } drawAnimeMountains(ctx, width, height); drawAnimeCity(ctx, width, height, rand); drawAnimeTrees(ctx, width, height, rand); drawAnimeParticles(ctx, width, height, rand); const darkOverlay = ctx.createLinearGradient(0, 0, 0, height); darkOverlay.addColorStop(0, "rgba(2,4,20,0.05)"); darkOverlay.addColorStop(0.55, "rgba(3,4,20,0.08)"); darkOverlay.addColorStop(1, "rgba(0,0,10,0.58)"); ctx.fillStyle = darkOverlay; ctx.fillRect(0, 0, width, height); } let STATIC_BG = null; function getStaticBackground() { if (STATIC_BG) return STATIC_BG; const c = createCanvas(W, H); const ctx = c.getContext("2d"); ctx.scale(SCALE, SCALE); drawAnimeBackground(ctx, DW, DH, rng(20261009)); STATIC_BG = c; return c; } // ================= অ্যানিমেটেড ব্যাকগ্রাউন্ড লেয়ার ================= const GLOW_PARTICLES = Array.from({ length: 70 }, (_, i) => ({ x: (i * 211 + 37) % DW, y: (i * 157 + 91) % DH, r: 1.5 + (i % 4) * 0.9, k: 1 + (i % 2), // পূর্ণসংখ্যা গতি → সীমলেস লুপ kt: 1 + (i % 2), c: ["255,140,225", "93,225,255", "190,150,255"][i % 3], sway: 10 + (i % 4) * 8 })); const PETALS = Array.from({ length: 26 }, (_, i) => ({ x: (i * 149 + 60) % DW, y: (i * 97 + 40) % DH, s: 9 + (i % 4) * 3, k: 1 + (i % 2), sway: 40 + (i % 3) * 25, rk: 1 + (i % 3), off: i })); function drawDynamicBackground(ctx, t) { const phase = t * TAU; // চাঁদের গ্লো পাল্স const mx = DW * 0.77; const my = DH * 0.22; const mg = ctx.createRadialGradient(mx, my, 40, mx, my, 380); mg.addColorStop(0, `rgba(190,225,255,${0.16 + 0.08 * Math.sin(phase)})`); mg.addColorStop(1, "rgba(120,160,255,0)"); ctx.fillStyle = mg; ctx.fillRect(mx - 380, my - 380, 760, 760); // ভাসমান ঝিকমিক কণা for (let i = 0; i < GLOW_PARTICLES.length; i++) { const p = GLOW_PARTICLES[i]; const y = ((p.y - p.k * t * DH) % DH + DH) % DH; const x = p.x + Math.sin(phase + i) * p.sway; const a = 0.35 + 0.65 * (0.5 + 0.5 * Math.sin(phase * p.kt + i)); ctx.fillStyle = `rgba(${p.c},${a * 0.22})`; ctx.beginPath(); ctx.arc(x, y, p.r * 3.2, 0, TAU); ctx.fill(); ctx.fillStyle = `rgba(${p.c},${a})`; ctx.beginPath(); ctx.arc(x, y, p.r, 0, TAU); ctx.fill(); } // ঝরে পড়া সাকুরা পাপড়ি for (let i = 0; i < PETALS.length; i++) { const p = PETALS[i]; const y = (p.y + p.k * t * DH) % DH; const x = p.x + Math.sin(phase + p.off) * p.sway; ctx.save(); ctx.translate(x, y); ctx.rotate(phase * p.rk + p.off); ctx.fillStyle = "rgba(255,150,215,0.8)"; ctx.beginPath(); ctx.ellipse(0, 0, p.s, p.s * 0.55, 0, 0, TAU); ctx.fill(); ctx.fillStyle = "rgba(255,230,247,0.55)"; ctx.beginPath(); ctx.ellipse(-p.s * 0.25, -p.s * 0.1, p.s * 0.45, p.s * 0.2, 0, 0, TAU); ctx.fill(); ctx.restore(); } } // চলন্ত স্ক্যানলাইন (৮px পরপর, তাই ৮px সরলেই লুপ মেলে) function drawScanlines(ctx, width, height, t) { ctx.save(); ctx.globalAlpha = 0.035; ctx.strokeStyle = "#ffffff"; ctx.lineWidth = 1; for (let y = -8 + t * 8; y < height; y += 8) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke(); } ctx.restore(); } function drawHUDCorners(ctx, width, height, t) { ctx.save(); ctx.lineWidth = 3; ctx.strokeStyle = `rgba(78,224,255,${0.6 + 0.25 * Math.sin(t * TAU)})`; const d = 85; const p = 55; ctx.beginPath(); ctx.moveTo(p, p + d); ctx.lineTo(p, p); ctx.lineTo(p + d, p); ctx.moveTo(width - p - d, p); ctx.lineTo(width - p, p); ctx.lineTo(width - p, p + d); ctx.moveTo(p, height - p - d); ctx.lineTo(p, height - p); ctx.lineTo(p + d, height - p); ctx.moveTo(width - p - d, height - p); ctx.lineTo(width - p, height - p); ctx.lineTo(width - p, height - p - d); ctx.stroke(); ctx.restore(); } // ================= প্যানেল ================= function drawGlassPanel(ctx, x, y, w, h, radius = 24) { ctx.save(); roundedRect(ctx, x, y, w, h, radius); const gradient = ctx.createLinearGradient(x, y, x + w, y + h); gradient.addColorStop(0, "rgba(8,20,50,0.72)"); gradient.addColorStop(0.5, "rgba(5,12,35,0.52)"); gradient.addColorStop(1, "rgba(20,10,55,0.65)"); ctx.fillStyle = gradient; ctx.fill(); ctx.strokeStyle = "rgba(92,220,255,0.22)"; ctx.lineWidth = 2; ctx.stroke(); ctx.restore(); } function drawCornerDetails(ctx, x, y, w, h, color, t, off) { ctx.save(); ctx.globalAlpha = 0.65 + 0.35 * Math.sin(t * TAU + off); ctx.strokeStyle = color; ctx.lineWidth = 3; const size = 24; ctx.beginPath(); ctx.moveTo(x, y + size); ctx.lineTo(x, y); ctx.lineTo(x + size, y); ctx.moveTo(x + w - size, y); ctx.lineTo(x + w, y); ctx.lineTo(x + w, y + size); ctx.moveTo(x, y + h - size); ctx.lineTo(x, y + h); ctx.lineTo(x + size, y + h); ctx.moveTo(x + w - size, y + h); ctx.lineTo(x + w, y + h); ctx.lineTo(x + w, y + h - size); ctx.stroke(); ctx.restore(); } // ================= অবতার ================= // ছবি না পেলে নামের প্রথম অক্ষর দিয়ে ফলব্যাক function drawFallbackAvatar(ctx, label, x, y, r) { const fg = ctx.createLinearGradient(x - r, y - r, x + r, y + r); fg.addColorStop(0, "#7c3aed"); fg.addColorStop(1, "#0ea5e9"); ctx.fillStyle = fg; ctx.fillRect(x - r, y - r, r * 2, r * 2); ctx.fillStyle = "#ffffff"; ctx.font = `bold ${Math.round(r * 0.95)}px ${FONT}`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(Array.from(label || "?")[0].toUpperCase(), x, y + r * 0.05); } // অবতারের উপর দিয়ে একবার ঝলক বয়ে যায় function drawShine(ctx, x, y, r, t) { const sx = x - r * 2 + t * (r * 4); const band = ctx.createLinearGradient(sx - r * 0.5, y - r, sx + r * 0.5, y + r); band.addColorStop(0, "rgba(255,255,255,0)"); band.addColorStop(0.5, "rgba(255,255,255,0.24)"); band.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = band; ctx.fillRect(x - r, y - r, r * 2, r * 2); } function drawAvatarRing(ctx, x, y, radius, t) { const phase = t * TAU; ctx.save(); // উজ্জ্বল বাইরের রিং (শ্বাস নেওয়ার মতো গ্লো) ctx.shadowColor = "#42e7ff"; ctx.shadowBlur = sb(45 + 15 * Math.sin(phase)); ctx.strokeStyle = "#55e8ff"; ctx.lineWidth = 5; ctx.beginPath(); ctx.arc(x, y, radius + 18, 0, TAU); ctx.stroke(); ctx.shadowBlur = 0; // বেগুনি ড্যাশড আর্ক (ঘোরে) ctx.strokeStyle = "rgba(189,116,255,0.85)"; ctx.lineWidth = 3; ctx.setLineDash([25, 14]); ctx.lineDashOffset = -t * 39; ctx.beginPath(); ctx.arc(x, y, radius + 34, -0.5 + phase, Math.PI * 1.35 + phase); ctx.stroke(); // সায়ান ড্যাশড আর্ক (উল্টো দিকে ঘোরে) ctx.strokeStyle = "rgba(74,226,255,0.8)"; ctx.setLineDash([10, 20]); ctx.lineDashOffset = t * 30; ctx.beginPath(); ctx.arc(x, y, radius + 48, 1.8 - phase, Math.PI * 1.85 - phase); ctx.stroke(); ctx.setLineDash([]); ctx.lineDashOffset = 0; // বাইরের রিং ধরে ঘুরন্ত উজ্জ্বল বিন্দু const bx = x + Math.cos(phase) * (radius + 48); const by = y + Math.sin(phase) * (radius + 48); ctx.shadowColor = "#7df3ff"; ctx.shadowBlur = sb(24); ctx.fillStyle = "#ffffff"; ctx.beginPath(); ctx.arc(bx, by, 7, 0, TAU); ctx.fill(); ctx.restore(); } function drawMainAvatar(ctx, image, label, x, y, size, t) { const radius = size / 2; const phase = t * TAU; drawAvatarRing(ctx, x, y, radius, t); ctx.save(); ctx.shadowColor = "#54eaff"; ctx.shadowBlur = sb(55 + 15 * Math.sin(phase)); ctx.beginPath(); ctx.arc(x, y, radius + 10, 0, TAU); const border = ctx.createLinearGradient(x - radius, y - radius, x + radius, y + radius); border.addColorStop(0, "#48e9ff"); border.addColorStop(0.3, "#ffffff"); border.addColorStop(0.65, "#ad69ff"); border.addColorStop(1, "#42e6ff"); ctx.fillStyle = border; ctx.fill(); ctx.shadowBlur = 0; ctx.beginPath(); ctx.arc(x, y, radius + 3, 0, TAU); ctx.fillStyle = "#05091d"; ctx.fill(); ctx.save(); ctx.beginPath(); ctx.arc(x, y, radius, 0, TAU); ctx.clip(); if (image) ctx.drawImage(image, x - radius, y - radius, size, size); else drawFallbackAvatar(ctx, label, x, y, radius); drawShine(ctx, x, y, radius, t); ctx.restore(); ctx.strokeStyle = "rgba(255,255,255,0.9)"; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(x, y, radius, 0, TAU); ctx.stroke(); ctx.restore(); } function drawSmallAvatar(ctx, image, label, x, y, size, t, color, dir) { const radius = size / 2; ctx.save(); ctx.shadowColor = color; ctx.shadowBlur = sb(25); ctx.beginPath(); ctx.arc(x, y, radius + 7, 0, TAU); ctx.strokeStyle = color; ctx.lineWidth = 4; ctx.stroke(); ctx.shadowBlur = 0; ctx.save(); ctx.beginPath(); ctx.arc(x, y, radius, 0, TAU); ctx.clip(); if (image) ctx.drawImage(image, x - radius, y - radius, size, size); else drawFallbackAvatar(ctx, label, x, y, radius); drawShine(ctx, x, y, radius, t); ctx.restore(); // রিং ধরে ঘুরন্ত বিন্দু const a = t * TAU * dir; ctx.shadowColor = color; ctx.shadowBlur = sb(18); ctx.fillStyle = "#ffffff"; ctx.beginPath(); ctx.arc(x + Math.cos(a) * (radius + 7), y + Math.sin(a) * (radius + 7), 5, 0, TAU); ctx.fill(); ctx.restore(); } // ================= প্যানেলের কনটেন্ট ================= function drawGroupPanel(ctx, image, groupName, totalMembers, t) { const x = 100, y = 125, w = 570, h = 420; drawGlassPanel(ctx, x, y, w, h, 32); drawCornerDetails(ctx, x, y, w, h, "#58e5ff", t, 0); ctx.save(); ctx.textBaseline = "middle"; ctx.textAlign = "left"; ctx.font = `bold 18px ${FONT}`; ctx.fillStyle = "rgba(91,229,255,0.72)"; ctx.fillText("OUR GROUP", x + 45, y + 48); ctx.textAlign = "right"; ctx.font = `bold 16px ${FONT}`; ctx.fillStyle = "rgba(170,190,220,0.65)"; ctx.fillText(`${totalMembers} MEMBERS`, x + w - 45, y + 48); ctx.restore(); const avatarX = x + w / 2; drawSmallAvatar(ctx, image, groupName, avatarX, y + 175, 180, t, "#48dcff", 1); ctx.save(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; fitFont(ctx, String(groupName), w - 80, 34, 20); const gradient = ctx.createLinearGradient(x + 40, 0, x + w - 40, 0); gradient.addColorStop(0, "#62e4ff"); gradient.addColorStop(0.5, "#ffffff"); gradient.addColorStop(1, "#b279ff"); ctx.fillStyle = gradient; ctx.shadowColor = "rgba(76,226,255,0.35)"; ctx.shadowBlur = sb(20); ctx.fillText(fitText(ctx, String(groupName), w - 80), avatarX, y + 330); ctx.restore(); ctx.save(); ctx.strokeStyle = "rgba(82,226,255,0.22)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x + 50, y + 365); ctx.lineTo(x + w - 50, y + 365); ctx.stroke(); ctx.restore(); } function drawAdderPanel(ctx, image, adderName, viaLink, t) { const x = 100, y = 600, w = 570, h = 350; drawGlassPanel(ctx, x, y, w, h, 32); drawCornerDetails(ctx, x, y, w, h, "#ae7cff", t, 1.5); ctx.save(); ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.font = `bold 18px ${FONT}`; ctx.fillStyle = "rgba(185,137,255,0.85)"; ctx.fillText(viaLink ? "JOINED VIA" : "INVITED BY", x + 45, y + 48); ctx.restore(); const avatarX = x + w / 2; drawSmallAvatar(ctx, image, viaLink ? "+" : adderName, avatarX, y + 145, 145, t, "#b98bff", -1); ctx.save(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; fitFont(ctx, String(adderName), w - 70, 31, 20); ctx.fillStyle = "#ffffff"; ctx.shadowColor = "rgba(174,124,255,0.45)"; ctx.shadowBlur = sb(20); ctx.fillText(fitText(ctx, String(adderName), w - 70), avatarX, y + 275); ctx.restore(); } function drawHeroPanel(ctx, image, userName, memberNo, t) { const x = 740, y = 90, w = 1160, h = 880; const cx = x + w / 2; const phase = t * TAU; drawGlassPanel(ctx, x, y, w, h, 42); drawCornerDetails(ctx, x, y, w, h, "#58e5ff", t, 3); // উপরের লেবেল ctx.save(); ctx.textBaseline = "middle"; ctx.textAlign = "left"; ctx.font = `bold 24px ${FONT}`; ctx.fillStyle = "rgba(91,229,255,0.85)"; ctx.fillText("WELCOME", x + 55, y + 47); ctx.textAlign = "right"; ctx.font = `bold 20px ${FONT}`; ctx.fillStyle = "rgba(185,137,255,0.85)"; ctx.fillText(`ACCESS GRANTED • MEMBER #${memberNo}`, x + w - 50, y + 47); ctx.restore(); // বড় অবতার drawMainAvatar(ctx, image, userName, cx, 408, 430, t); // নাম (গ্রেডিয়েন্ট + গ্লো + ঝলক) ctx.save(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; const nameMax = w - 140; fitFont(ctx, String(userName), nameMax, 100, 44); const label = fitText(ctx, String(userName), nameMax); const ny = 730; const gradient = ctx.createLinearGradient(cx - 450, 0, cx + 450, 0); gradient.addColorStop(0, "#62e4ff"); gradient.addColorStop(0.5, "#ffffff"); gradient.addColorStop(1, "#b279ff"); ctx.fillStyle = gradient; ctx.shadowColor = "rgba(76,226,255,0.55)"; ctx.shadowBlur = sb(30); ctx.fillText(label, cx, ny); ctx.shadowBlur = 0; const sx = cx - 700 + t * 1400; const shimmer = ctx.createLinearGradient(sx - 130, 0, sx + 130, 0); shimmer.addColorStop(0, "rgba(255,255,255,0)"); shimmer.addColorStop(0.5, "rgba(255,255,255,0.95)"); shimmer.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = shimmer; ctx.fillText(label, cx, ny); ctx.restore(); // WELCOME ctx.save(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = `bold 46px ${FONT}`; const wg = ctx.createLinearGradient(cx - 120, 0, cx + 120, 0); wg.addColorStop(0, "#9ff1ff"); wg.addColorStop(0.5, "#ffffff"); wg.addColorStop(1, "#d3b3ff"); ctx.fillStyle = wg; ctx.shadowColor = "rgba(76,226,255,0.6)"; ctx.shadowBlur = sb(18 + 10 * Math.sin(phase)); ctx.fillText("WELCOME", cx, 790); ctx.restore(); // সাবটাইটেল ctx.save(); ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = `bold 22px ${FONT}`; ctx.fillStyle = "rgba(190,205,235,0.72)"; ctx.fillText("YOU ARE NOW PART OF THE COMMUNITY", cx, 838); ctx.restore(); // ডিভাইডার + চলন্ত আলোর বিন্দু const lx1 = x + 110; const lx2 = x + w - 110; const dg = ctx.createLinearGradient(lx1, 0, lx2, 0); dg.addColorStop(0, "rgba(82,226,255,0)"); dg.addColorStop(0.3, "rgba(82,226,255,0.55)"); dg.addColorStop(0.7, "rgba(185,137,255,0.55)"); dg.addColorStop(1, "rgba(185,137,255,0)"); ctx.fillStyle = dg; ctx.fillRect(lx1, 877, lx2 - lx1, 2); const dx = lx1 + t * (lx2 - lx1); const dot = ctx.createRadialGradient(dx, 878, 0, dx, 878, 46); dot.addColorStop(0, "rgba(255,255,255,0.95)"); dot.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = dot; ctx.fillRect(dx - 46, 832, 92, 92); // ক্রেডিট ctx.save(); ctx.textBaseline = "middle"; ctx.textAlign = "left"; ctx.font = `bold 26px ${FONT}`; const part1 = "Powered by "; const w1 = ctx.measureText(part1).width; const w2 = ctx.measureText(CREDIT).width; const startX = cx - (w1 + w2) / 2; ctx.fillStyle = "#8fe9ff"; ctx.fillText(part1, startX, 925); ctx.fillStyle = "#ffffff"; ctx.fillText(CREDIT, startX + w1, 925); ctx.restore(); } // ================= পুরো সিন ================= function drawScene(ctx, t, d) { ctx.save(); ctx.scale(SCALE, SCALE); ctx.drawImage(getStaticBackground(), 0, 0, DW, DH); drawDynamicBackground(ctx, t); drawScanlines(ctx, DW, DH, t); drawHUDCorners(ctx, DW, DH, t); drawGroupPanel(ctx, d.groupImg, d.groupName, d.totalMembers, t); drawAdderPanel(ctx, d.inviterImg, d.inviterName, d.viaLink, t); drawHeroPanel(ctx, d.avatar, d.name, d.memberNo, t); ctx.restore(); } // ================= ছবি লোড ================= async function loadImg(url) { try { const res = await axios.get(url, { responseType: "arraybuffer", timeout: 20000, headers: { "User-Agent": "Mozilla/5.0" } }); return await loadImage(Buffer.from(res.data)); } catch (e) { return null; } } function loadAvatar(uid) { return loadImg(`https://graph.facebook.com/${uid}/picture?width=512&height=512&access_token=${AVATAR_TOKEN}`); } async function getNameSafe(usersData, id) { try { const n = await usersData.getName(id); return n || "Unknown"; } catch (e) { return "Unknown"; } } // কার্ড বানিয়ে ফাইলের পাথ রিটার্ন করে async function generateCard({ uid, name, groupName, groupImage, inviterID, inviterName, memberNo, totalMembers }) { const tmpDir = path.join(__dirname, "tmp"); fs.ensureDirSync(tmpDir); const stamp = `${uid}_${Date.now()}_${Math.floor(Math.random() * 1000)}`; const viaLink = !inviterID || String(inviterID) === String(uid); const [avatar, groupImg, inviterImg] = await Promise.all([ loadAvatar(uid), groupImage ? loadImg(groupImage) : null, viaLink ? null : loadAvatar(inviterID) ]); const data = { name: name || "New member", groupName: groupName || "this group", memberNo, totalMembers: totalMembers || memberNo, avatar, groupImg, inviterImg, inviterName: viaLink ? "Invite Link" : (inviterName || "Unknown"), viaLink }; const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); // gifencoder না থাকলে স্ট্যাটিক PNG if (!GIFEncoder) { drawScene(ctx, 0, data); const pngPath = path.join(tmpDir, `welcome_${stamp}.png`); fs.writeFileSync(pngPath, canvas.toBuffer("image/png")); return pngPath; } const gifPath = path.join(tmpDir, `welcome_${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(20); for (let i = 0; i < FRAMES; i++) { drawScene(ctx, i / FRAMES, data); encoder.addFrame(ctx); } encoder.finish(); await finished; return gifPath; } function applyTemplate(template, vars) { return template .replace(/\{userName\}/g, vars.userName) .replace(/\{boxName\}/g, vars.boxName) .replace(/\{memberCount\}/g, vars.memberCount); } function getPrefixSafe(threadID) { try { return global.utils.getPrefix(threadID); } catch (e) { return (global.GoatBot && global.GoatBot.config && global.GoatBot.config.prefix) || "/"; } } // ================= command ================= module.exports = { config: { name: "welcome", version: "3.0", author: "Zihad", countDown: 5, role: 1, description: { en: "Send an animated anime-HUD welcome card when someone joins the group", bn: "কেউ গ্রুপে জয়েন করলে অ্যানিমেটেড এনিমে-HUD ওয়েলকাম কার্ড পাঠাবে" }, category: "events", guide: { en: "{pn} on | off: enable/disable welcome\n{pn} setmsg : custom message\n{pn} resetmsg: back to default\n{pn} test: preview with your own card\n\nPlaceholders: {userName}, {boxName}, {memberCount}", bn: "{pn} on | off: ওয়েলকাম চালু/বন্ধ\n{pn} setmsg <লেখা>: নিজের মেসেজ সেট\n{pn} resetmsg: ডিফল্ট মেসেজে ফেরত\n{pn} test: নিজের কার্ড দিয়ে প্রিভিউ\n\nপ্লেসহোল্ডার: {userName}, {boxName}, {memberCount}" } }, onStart: async function (ctx) { // scripts/events ফোল্ডারে থাকলে GoatBot `args` পাঠায় না → জয়েন ইভেন্ট হ্যান্ডেল করে if (!Array.isArray(ctx.args)) { if (ctx.event.logMessageType !== "log:subscribe") return; return async function () { await handleJoin(ctx); }; } // scripts/cmds ফোল্ডারে থাকলে এটা সাধারণ কমান্ড const { message, event, args, threadsData, usersData, api } = ctx; const { threadID, senderID } = event; const sub = (args[0] || "").toLowerCase(); if (sub === "on" || sub === "off") { await threadsData.set(threadID, sub === "on", "settings.sendWelcomeMessage"); return message.reply(sub === "on" ? "✅ ওয়েলকাম মেসেজ চালু করা হয়েছে।" : "🔕 ওয়েলকাম মেসেজ বন্ধ করা হয়েছে।"); } if (sub === "setmsg") { const text = args.slice(1).join(" ").trim(); if (!text) return message.reply("✍️ মেসেজ লিখুন।\nউদাহরণ: welcome setmsg স্বাগতম {userName}! {boxName}-এ তুমি {memberCount} নম্বর মেম্বার।"); await threadsData.set(threadID, text, "data.welcomeMessage"); return message.reply("✅ কাস্টম ওয়েলকাম মেসেজ সেট হয়েছে।"); } if (sub === "resetmsg") { await threadsData.set(threadID, null, "data.welcomeMessage"); return message.reply("♻️ ডিফল্ট মেসেজে ফেরত আনা হয়েছে।"); } if (sub === "test") { if (!createCanvas) return message.reply("❌ `canvas` প্যাকেজ ইনস্টল নেই। টার্মিনালে `npm i canvas` চালান।"); try { message.reaction("⏳", event.messageID); } catch (e) { } let file; try { const threadData = await threadsData.get(threadID); let info = null; try { info = await api.getThreadInfo(threadID); } catch (e) { } const groupName = (info && info.threadName) || threadData.threadName || "this group"; const count = (info && info.participantIDs && info.participantIDs.length) || 1; const name = await usersData.getName(senderID); // টেস্টে বটকে ইনভাইটার ধরা হয়েছে, যাতে "INVITED BY" প্যানেল দেখা যায় const botID = api.getCurrentUserID(); const botName = await getNameSafe(usersData, botID); file = await generateCard({ uid: senderID, name, groupName, groupImage: info && info.imageSrc, inviterID: botID, inviterName: botName === "Unknown" ? "GoatBot" : botName, memberNo: count, totalMembers: count }); try { message.reaction("✅", event.messageID); } catch (e) { } return message.reply({ body: applyTemplate(threadData.data.welcomeMessage || DEFAULT_MESSAGE, { userName: name, boxName: groupName, memberCount: String(count) }), attachment: fs.createReadStream(file) }, () => fs.remove(file)); } catch (err) { console.error("[welcome.js]", err); try { message.reaction("❌", event.messageID); } catch (e) { } if (file) fs.remove(file).catch(() => { }); return message.reply("❌ কার্ড বানাতে সমস্যা হয়েছে। `canvas` ও `gifencoder` ইনস্টল আছে কিনা দেখুন।"); } } return message.SyntaxError(); }, // cmds ফোল্ডারে থাকলে GoatBot এটা কল করে onEvent: async function (ctx) { await handleJoin(ctx); } }; // ================= জয়েন ইভেন্ট হ্যান্ডলার (events ও cmds দুই জায়গাতেই চলে) ================= async function handleJoin({ event, api, message, threadsData, usersData }) { if (event.logMessageType !== "log:subscribe") return; const { threadID } = event; const added = (event.logMessageData && event.logMessageData.addedParticipants) || []; if (!added.length) return; const threadData = await threadsData.get(threadID); if (threadData.settings && threadData.settings.sendWelcomeMessage === false) return; const botID = api.getCurrentUserID(); // বট নিজে অ্যাড হলে if (added.some(u => String(u.userFbId) === String(botID))) { const prefix = getPrefixSafe(threadID); return message.send(`✅ অ্যাড করার জন্য ধন্যবাদ!\nকমান্ডের তালিকা দেখতে লিখুন: ${prefix}help`); } // একসাথে জয়েন করা মেম্বারদের জমা করা global.temp = global.temp || {}; global.temp.welcomeEvent = global.temp.welcomeEvent || {}; const store = global.temp.welcomeEvent; if (!store[threadID]) store[threadID] = { joined: [], timeout: null }; // event.author = যে অ্যাড করেছে (লিংক দিয়ে জয়েন করলে নিজেই) for (const u of added) store[threadID].joined.push({ id: u.userFbId, name: u.fullName, author: event.author }); clearTimeout(store[threadID].timeout); store[threadID].timeout = setTimeout(async () => { const batch = store[threadID]; delete store[threadID]; const files = []; try { const data = await threadsData.get(threadID); if (data.settings && data.settings.sendWelcomeMessage === false) return; const joined = batch.joined.filter(u => String(u.id) !== String(botID)); if (!joined.length) return; for (const u of joined) { if (!u.name) { u.name = await getNameSafe(usersData, u.id); if (u.name === "Unknown") u.name = "New member"; } } let info = null; try { info = await api.getThreadInfo(threadID); } catch (e) { } const groupName = (info && info.threadName) || data.threadName || "this group"; const totalMembers = (info && info.participantIDs && info.participantIDs.length) || (data.members ? Object.values(data.members).filter(m => m.inGroup).length : joined.length); // টেক্সট const names = joined.map(u => u.name); const body = applyTemplate(data.data.welcomeMessage || DEFAULT_MESSAGE, { userName: names.join(", "), boxName: groupName, memberCount: joined.length > 1 ? `${totalMembers - joined.length + 1}–${totalMembers}` : String(totalMembers) }); const form = { body, mentions: joined.map(u => ({ tag: u.name, id: u.id })) }; // কার্ড (সর্বোচ্চ MAX_CARDS টা) if (createCanvas) { const cardUsers = joined.slice(0, MAX_CARDS); for (let i = 0; i < cardUsers.length; i++) { try { const u = cardUsers[i]; const viaLink = !u.author || String(u.author) === String(u.id); const inviterName = viaLink ? null : await getNameSafe(usersData, u.author); const file = await generateCard({ uid: u.id, name: u.name, groupName, groupImage: info && info.imageSrc, inviterID: viaLink ? null : u.author, inviterName, memberNo: totalMembers - joined.length + i + 1, totalMembers }); files.push(file); } catch (e) { console.error("[welcome.js] card error:", e.message); } } if (files.length) form.attachment = files.map(f => fs.createReadStream(f)); } await message.send(form); } catch (err) { console.error("[welcome.js]", err); } finally { for (const f of files) fs.remove(f).catch(() => { }); } }, BATCH_WAIT); }