goat
command
vip
Premium VIP marketplace system
0
views
0
likes
0
installs
raw source
const fs = require("fs");
const path = require("path");
const axios = require("axios");
const { createCanvas, loadImage } = require("canvas");
const DAY = 24 * 60 * 60 * 1000;
const HOUR = 60 * 60 * 1000;
const cooldown = new Map();
const CACHE_DIR = path.join(__dirname, "cache");
if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true });
const VIP_PRICES = {
1: 10_000_000,
2: 20_000_000,
3: 30_000_000,
4: 40_000_000,
5: 50_000_000,
6: 60_000_000,
7: 70_000_000
};
const BASE_PRICE_PER_DAY = 10_000_000;
const LIST_PAGE_SIZE = 10;
const formatDatePlain = ts => {
const d = new Date(ts);
return `${String(d.getDate()).padStart(2,"0")}-${String(d.getMonth()+1).padStart(2,"0")}-${d.getFullYear()}`;
};
const formatMoneyPlain = n => {
if (n >= 1e9) return `$${(n/1e9).toFixed(n%1e9?1:0)}B`;
if (n >= 1e6) return `$${(n/1e6).toFixed(n%1e6?1:0)}M`;
if (n >= 1e3) return `$${(n/1e3).toFixed(n%1e3?1:0)}K`;
return `$${n}`;
};
const calculatePrice = (days) => {
if (VIP_PRICES[days]) return VIP_PRICES[days];
return days * BASE_PRICE_PER_DAY;
};
const getTimeRemainingPlain = (expiryTimestamp) => {
const now = Date.now();
const diff = expiryTimestamp - now;
if (diff <= 0) return { text: "Expired", days: 0 };
const days = Math.floor(diff / DAY);
const hours = Math.floor((diff % DAY) / HOUR);
return {
text: days === 0 ? `${hours}h left` : `${days}d ${hours}h left`,
days
};
};
const PALETTE = {
bgTop: "#020203",
bgMid: "#0a0a11",
bgBottom: "#000000",
gold: ["#fff6d8", "#f3d581", "#c9922a", "#f3d581", "#7a5510"],
rose: ["#ffe3da", "#e3a58f", "#a85c42", "#e3a58f", "#5e2f1f"],
platinum: ["#ffffff", "#e2e2e2", "#9a9a9a", "#e2e2e2", "#5c5c5c"],
emerald: ["#c9f7e4", "#3fd18c", "#0d6b45", "#3fd18c", "#063d28"],
garnetGrad: ["#ffc9c9", "#e05a5a", "#7a1f1f", "#e05a5a", "#3d0f0f"],
ink: "#e0524f",
text: "#f6f1e4",
muted: "#9b937f"
};
function lineGradient(ctx, x0, y0, x1, y1, stops) {
const g = ctx.createLinearGradient(x0, y0, x1, y1);
const n = stops.length - 1;
stops.forEach((c, i) => g.addColorStop(i / n, c));
return g;
}
function fillTextTracked(ctx, text, cx, y, spacing) {
const chars = text.split("");
const prevAlign = ctx.textAlign;
ctx.textAlign = "left";
const widths = chars.map(c => ctx.measureText(c).width);
const total = widths.reduce((a, b) => a + b, 0) + spacing * (chars.length - 1);
let x = cx - total / 2;
for (let i = 0; i < chars.length; i++) {
ctx.fillText(chars[i], x, y);
x += widths[i] + spacing;
}
ctx.textAlign = prevAlign;
}
function hexPath(ctx, cx, cy, r, rotationDeg = -90) {
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const a = (Math.PI / 180) * (rotationDeg + i * 60);
const px = cx + r * Math.cos(a);
const py = cy + r * Math.sin(a);
if (i === 0) ctx.moveTo(px, py); else ctx.lineTo(px, py);
}
ctx.closePath();
}
function drawHexGrid(ctx, width, height, r, alpha, color) {
ctx.save();
ctx.globalAlpha = alpha;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
const hSpacing = Math.sqrt(3) * r;
const vSpacing = 1.5 * r;
let row = 0;
for (let y = -r; y < height + r; y += vSpacing) {
const offsetX = (row % 2 === 0) ? 0 : hSpacing / 2;
for (let x = -r; x < width + r; x += hSpacing) {
hexPath(ctx, x + offsetX, y, r);
ctx.stroke();
}
row++;
}
ctx.restore();
}
function drawRoundedRect(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 drawEmblem(ctx, cx, cy, size, colorStops) {
const w = size, h = size * 1.15;
ctx.save();
ctx.translate(cx, cy);
ctx.beginPath();
ctx.moveTo(0, -h / 2);
ctx.lineTo(w / 2, -h / 2 + h * 0.28);
ctx.lineTo(w * 0.32, h / 2);
ctx.lineTo(-w * 0.32, h / 2);
ctx.lineTo(-w / 2, -h / 2 + h * 0.28);
ctx.closePath();
ctx.fillStyle = lineGradient(ctx, -w / 2, -h / 2, w / 2, h / 2, colorStops);
ctx.fill();
ctx.strokeStyle = "rgba(0,0,0,0.45)";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.strokeStyle = "rgba(255,255,255,0.35)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, -h / 2); ctx.lineTo(0, h / 2);
ctx.moveTo(-w / 2, -h / 2 + h * 0.28); ctx.lineTo(0, -h * 0.05);
ctx.moveTo(w / 2, -h / 2 + h * 0.28); ctx.lineTo(0, -h * 0.05);
ctx.moveTo(-w * 0.32, h / 2); ctx.lineTo(0, -h * 0.05);
ctx.moveTo(w * 0.32, h / 2); ctx.lineTo(0, -h * 0.05);
ctx.stroke();
ctx.fillStyle = "rgba(255,255,255,0.9)";
ctx.beginPath();
ctx.moveTo(-w * 0.08, -h * 0.42);
ctx.lineTo(0, -h * 0.30);
ctx.lineTo(w * 0.08, -h * 0.42);
ctx.lineTo(0, -h * 0.50);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function baseShop(width, height) {
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
const bg = ctx.createLinearGradient(0, 0, 0, height);
bg.addColorStop(0, PALETTE.bgTop);
bg.addColorStop(0.5, PALETTE.bgMid);
bg.addColorStop(1, PALETTE.bgBottom);
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
drawHexGrid(ctx, width, height, 24, 0.05, "#f3d581");
const vignette = ctx.createRadialGradient(width / 2, height / 2, height * 0.2, width / 2, height / 2, height * 0.9);
vignette.addColorStop(0, "rgba(0,0,0,0)");
vignette.addColorStop(1, "rgba(0,0,0,0.55)");
ctx.fillStyle = vignette;
ctx.fillRect(0, 0, width, height);
const glow = ctx.createRadialGradient(width / 2, -30, 10, width / 2, -30, width * 0.65);
glow.addColorStop(0, "rgba(243,213,129,0.28)");
glow.addColorStop(1, "rgba(243,213,129,0)");
ctx.fillStyle = glow;
ctx.fillRect(0, 0, width, height * 0.55);
ctx.strokeStyle = lineGradient(ctx, 0, 0, width, height, PALETTE.gold);
ctx.lineWidth = 3;
drawRoundedRect(ctx, 14, 14, width - 28, height - 28, 18);
ctx.stroke();
ctx.strokeStyle = "rgba(243,213,129,0.5)";
ctx.lineWidth = 1;
drawRoundedRect(ctx, 22, 22, width - 44, height - 44, 14);
ctx.stroke();
const tick = (x, y) => {
ctx.save();
ctx.translate(x, y);
ctx.rotate(Math.PI / 4);
ctx.fillStyle = "#f3d581";
ctx.fillRect(-3, -3, 6, 6);
ctx.restore();
};
tick(22, 22); tick(width - 22, 22); tick(22, height - 22); tick(width - 22, height - 22);
return { canvas, ctx };
}
function drawHeaderPlaque(ctx, width, title, subtitle) {
const topY = 44;
drawEmblem(ctx, width / 2, topY + 36, 44, PALETTE.gold);
ctx.textAlign = "center";
ctx.font = "bold 30px sans-serif";
ctx.fillStyle = lineGradient(ctx, width / 2 - 220, 0, width / 2 + 220, 0, PALETTE.gold);
fillTextTracked(ctx, title.toUpperCase(), width / 2, topY + 108, 6);
if (subtitle) {
ctx.font = "13px sans-serif";
ctx.fillStyle = PALETTE.muted;
fillTextTracked(ctx, subtitle.toUpperCase(), width / 2, topY + 132, 3);
}
const lineY = topY + 156;
ctx.strokeStyle = "rgba(243,213,129,0.45)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(width / 2 - 180, lineY);
ctx.lineTo(width / 2 - 14, lineY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(width / 2 + 14, lineY);
ctx.lineTo(width / 2 + 180, lineY);
ctx.stroke();
ctx.save();
ctx.translate(width / 2, lineY);
ctx.rotate(Math.PI / 4);
ctx.fillStyle = "#f3d581";
ctx.fillRect(-4, -4, 8, 8);
ctx.restore();
}
function drawCornerSeal(ctx, width, text) {
const cx = width - 74, cy = 74, r = 34;
ctx.save();
hexPath(ctx, cx, cy, r);
ctx.fillStyle = lineGradient(ctx, cx - r, cy - r, cx + r, cy + r, PALETTE.platinum);
ctx.fill();
ctx.strokeStyle = "rgba(0,0,0,0.4)";
ctx.lineWidth = 1.5;
ctx.stroke();
hexPath(ctx, cx, cy, r * 0.74);
ctx.strokeStyle = "rgba(0,0,0,0.25)";
ctx.lineWidth = 1;
ctx.stroke();
ctx.textAlign = "center";
ctx.font = "bold 9px sans-serif";
ctx.fillStyle = "#141414";
ctx.fillText(text.toUpperCase(), cx, cy + 3);
ctx.restore();
}
const CYAN = { bright: "#eafcff", mid: "#4be3ff", deep: "#0a6c86", dark: "#053544" };
function baseTerminal(width, height) {
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
const bg = ctx.createLinearGradient(0, 0, 0, height);
bg.addColorStop(0, "#030608");
bg.addColorStop(0.55, "#05141c");
bg.addColorStop(1, "#010304");
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
ctx.save();
ctx.globalAlpha = 0.07;
ctx.strokeStyle = CYAN.mid;
ctx.lineWidth = 1;
for (let x = 0; x < width; x += 28) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, height); ctx.stroke();
}
for (let y = 0; y < height; y += 28) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(width, y); ctx.stroke();
}
ctx.restore();
ctx.save();
ctx.globalAlpha = 0.05;
ctx.fillStyle = CYAN.mid;
for (let y = 0; y < height; y += 4) {
ctx.fillRect(0, y, width, 1);
}
ctx.restore();
const glow = ctx.createRadialGradient(width / 2, -20, 10, width / 2, -20, width * 0.6);
glow.addColorStop(0, "rgba(75,227,255,0.22)");
glow.addColorStop(1, "rgba(75,227,255,0)");
ctx.fillStyle = glow;
ctx.fillRect(0, 0, width, height * 0.5);
const vignette = ctx.createRadialGradient(width / 2, height / 2, height * 0.25, width / 2, height / 2, height * 0.9);
vignette.addColorStop(0, "rgba(0,0,0,0)");
vignette.addColorStop(1, "rgba(0,0,0,0.6)");
ctx.fillStyle = vignette;
ctx.fillRect(0, 0, width, height);
ctx.strokeStyle = "rgba(75,227,255,0.55)";
ctx.lineWidth = 1.5;
drawRoundedRect(ctx, 16, 16, width - 32, height - 32, 10);
ctx.stroke();
return { canvas, ctx };
}
function drawHUDCorners(ctx, width, height, color = CYAN.mid) {
const len = 30, inset = 26;
ctx.save();
ctx.strokeStyle = color;
ctx.lineWidth = 2.5;
const corners = [
[inset, inset, 1, 1],
[width - inset, inset, -1, 1],
[inset, height - inset, 1, -1],
[width - inset, height - inset, -1, -1]
];
for (const [x, y, dx, dy] of corners) {
ctx.beginPath();
ctx.moveTo(x, y + len * dy);
ctx.lineTo(x, y);
ctx.lineTo(x + len * dx, y);
ctx.stroke();
}
ctx.restore();
}
function drawDigitalHeader(ctx, width, title, subtitle) {
const topY = 44;
const cx = width / 2, cy = topY + 34;
ctx.strokeStyle = "rgba(75,227,255,0.55)";
ctx.lineWidth = 1.5;
ctx.beginPath();
ctx.moveTo(cx - 26, cy); ctx.lineTo(cx - 92, cy); ctx.lineTo(cx - 92, cy - 16);
ctx.moveTo(cx + 26, cy); ctx.lineTo(cx + 92, cy); ctx.lineTo(cx + 92, cy - 16);
ctx.stroke();
const node = (x, y) => { ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fillStyle = CYAN.mid; ctx.fill(); };
node(cx - 92, cy - 16); node(cx + 92, cy - 16);
hexPath(ctx, cx, cy, 26);
ctx.fillStyle = lineGradient(ctx, cx - 26, cy - 26, cx + 26, cy + 26,
[CYAN.bright, CYAN.mid, CYAN.deep, CYAN.mid, CYAN.dark]);
ctx.fill();
ctx.strokeStyle = "rgba(255,255,255,0.55)";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.fillStyle = "#03222b";
ctx.fillRect(cx - 7, cy - 7, 14, 14);
ctx.strokeStyle = CYAN.mid;
ctx.lineWidth = 1;
ctx.strokeRect(cx - 7, cy - 7, 14, 14);
ctx.textAlign = "center";
ctx.font = "bold 27px monospace";
ctx.fillStyle = lineGradient(ctx, cx - 200, 0, cx + 200, 0, [CYAN.bright, CYAN.mid, CYAN.bright]);
fillTextTracked(ctx, title.toUpperCase(), cx, topY + 100, 5);
if (subtitle) {
ctx.font = "12px monospace";
ctx.fillStyle = "#7fb8c9";
fillTextTracked(ctx, subtitle.toUpperCase(), cx, topY + 122, 3);
}
const lineY = topY + 144;
ctx.strokeStyle = "rgba(75,227,255,0.4)";
ctx.lineWidth = 1;
ctx.setLineDash([4, 3]);
ctx.beginPath();
ctx.moveTo(cx - 200, lineY);
ctx.lineTo(cx + 200, lineY);
ctx.stroke();
ctx.setLineDash([]);
}
function drawDigitalRow(ctx, x, y, width, label, value, ratio = 0, accentColor = CYAN.mid) {
const rowH = 34, top = y - 24;
ctx.fillStyle = "rgba(75,227,255,0.06)";
drawRoundedRect(ctx, x, top, width, rowH, 6);
ctx.fill();
const barW = Math.max(6, width * Math.min(1, Math.max(0, ratio)));
ctx.save();
ctx.beginPath();
drawRoundedRect(ctx, x, top, width, rowH, 6);
ctx.clip();
ctx.fillStyle = lineGradient(ctx, x, 0, x + barW, 0, ["rgba(75,227,255,0.28)", "rgba(75,227,255,0.03)"]);
ctx.fillRect(x, top, barW, rowH);
ctx.restore();
ctx.strokeStyle = "rgba(75,227,255,0.3)";
ctx.lineWidth = 1;
drawRoundedRect(ctx, x, top, width, rowH, 6);
ctx.stroke();
ctx.textAlign = "left";
ctx.font = "14px monospace";
ctx.fillStyle = "#bfe9f5";
ctx.fillText(label, x + 14, y);
ctx.textAlign = "right";
ctx.font = "bold 17px monospace";
ctx.fillStyle = accentColor;
ctx.fillText(value, x + width - 14, y);
}
function drawApprovalChip(ctx, cx, cy, text, color = CYAN.mid) {
const label = text.toUpperCase();
ctx.save();
ctx.font = "bold 15px monospace";
const textW = ctx.measureText(label).width;
const w = textW + 46, h = 34;
ctx.strokeStyle = color;
ctx.lineWidth = 1.5;
drawRoundedRect(ctx, cx - w / 2, cy - h / 2, w, h, 6);
ctx.stroke();
ctx.fillStyle = "rgba(75,227,255,0.08)";
drawRoundedRect(ctx, cx - w / 2, cy - h / 2, w, h, 6);
ctx.fill();
ctx.beginPath();
ctx.arc(cx - w / 2 + 16, cy, 4, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillStyle = color;
ctx.fillText(label, cx - w / 2 + 30, cy + 1);
ctx.textBaseline = "alphabetic";
ctx.restore();
}
function drawPriceChip(ctx, x, y, text, color = CYAN.bright) {
ctx.save();
ctx.font = "bold 20px monospace";
const label = `[ ${text} ]`;
ctx.textAlign = "left";
ctx.fillStyle = color;
ctx.fillText(label, x, y);
ctx.restore();
}
function drawStamp(ctx, cx, cy, text, color, rotationDeg = -14) {
ctx.save();
ctx.translate(cx, cy);
ctx.rotate((rotationDeg * Math.PI) / 180);
ctx.globalAlpha = 0.9;
ctx.strokeStyle = color;
ctx.lineWidth = 3;
ctx.beginPath(); ctx.arc(0, 0, 60, 0, Math.PI * 2); ctx.stroke();
ctx.lineWidth = 1.5;
ctx.beginPath(); ctx.arc(0, 0, 52, 0, Math.PI * 2); ctx.stroke();
for (let i = 0; i < 8; i++) {
const a = (Math.PI / 4) * i;
const x = Math.cos(a) * 56, y = Math.sin(a) * 56;
ctx.save();
ctx.translate(x, y);
ctx.rotate(a + Math.PI / 4);
ctx.fillStyle = color;
ctx.fillRect(-2, -2, 4, 4);
ctx.restore();
}
ctx.textAlign = "center";
ctx.font = "bold 17px sans-serif";
ctx.fillStyle = color;
const lines = text.toUpperCase().split(" ");
if (lines.length === 1) {
ctx.fillText(lines[0], 0, 6);
} else {
ctx.fillText(lines[0], 0, -5);
ctx.font = "bold 13px sans-serif";
ctx.fillText(lines.slice(1).join(" "), 0, 15);
}
ctx.globalAlpha = 1;
ctx.restore();
}
function drawPriceTag(ctx, x, y, text, rotationDeg = -6) {
const w = 150, h = 46;
ctx.save();
ctx.translate(x, y);
ctx.rotate((rotationDeg * Math.PI) / 180);
ctx.beginPath();
ctx.moveTo(0, h / 2);
ctx.lineTo(18, 0);
ctx.lineTo(w, 0);
ctx.lineTo(w, h);
ctx.lineTo(18, h);
ctx.closePath();
ctx.fillStyle = lineGradient(ctx, 0, 0, w, 0, PALETTE.gold);
ctx.fill();
ctx.strokeStyle = "rgba(0,0,0,0.3)";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.arc(20, h / 2, 4, 0, Math.PI * 2);
ctx.fillStyle = "#0d1420";
ctx.fill();
ctx.textAlign = "center";
ctx.font = "bold 20px sans-serif";
ctx.fillStyle = "#1c1409";
ctx.fillText(text, (w + 18) / 2, h / 2 + 7);
ctx.restore();
}
function drawReceiptRow(ctx, x, y, width, label, value, accent = PALETTE.text) {
ctx.textAlign = "left";
ctx.font = "20px sans-serif";
ctx.fillStyle = PALETTE.muted;
ctx.fillText(label, x, y);
const labelW = ctx.measureText(label).width;
ctx.textAlign = "right";
ctx.font = "bold 22px sans-serif";
ctx.fillStyle = accent;
const valueW = ctx.measureText(value).width;
ctx.strokeStyle = "rgba(244,240,230,0.25)";
ctx.lineWidth = 1.5;
ctx.setLineDash([2, 4]);
ctx.beginPath();
ctx.moveTo(x + labelW + 12, y - 5);
ctx.lineTo(x + width - valueW - 12, y - 5);
ctx.stroke();
ctx.setLineDash([]);
ctx.fillText(value, x + width, y);
}
function drawMedal(ctx, cx, cy, rank) {
const colors = {
1: ["#fff6cf", "#f7d774", "#c9922a"],
2: ["#f4f4f4", "#cfcfcf", "#9a9a9a"],
3: ["#f0c9a0", "#c98a4b", "#8a5622"]
};
const stops = colors[rank] || ["#4a4536", "#33301f", "#1f1c10"];
ctx.save();
ctx.beginPath();
ctx.arc(cx, cy, 20, 0, Math.PI * 2);
ctx.fillStyle = lineGradient(ctx, cx - 20, cy - 20, cx + 20, cy + 20, stops);
ctx.fill();
ctx.strokeStyle = "rgba(0,0,0,0.3)";
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.textAlign = "center";
ctx.font = "bold 16px sans-serif";
ctx.fillStyle = "#1c1409";
ctx.fillText(String(rank), cx, cy + 5);
ctx.restore();
}
function getInitial(name) {
if (!name) return "V";
const firstWord = String(name).trim().split(/\s+/)[0] || "";
return (firstWord[0] || "V").toUpperCase();
}
function drawAvatar(ctx, cx, cy, radius, name, { colors = PALETTE.gold, textColor = "#1c1409", fontScale = 0.85 } = {}) {
ctx.save();
hexPath(ctx, cx, cy, radius);
ctx.fillStyle = lineGradient(ctx, cx - radius, cy - radius, cx + radius, cy + radius, colors);
ctx.fill();
ctx.strokeStyle = "rgba(255,255,255,0.4)";
ctx.lineWidth = Math.max(1.2, radius * 0.05);
ctx.stroke();
hexPath(ctx, cx, cy, radius * 0.8);
ctx.strokeStyle = "rgba(0,0,0,0.25)";
ctx.lineWidth = 1;
ctx.stroke();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = `bold ${Math.round(radius * fontScale)}px sans-serif`;
ctx.fillStyle = textColor;
ctx.fillText(getInitial(name), cx, cy + radius * 0.05);
ctx.textBaseline = "alphabetic";
ctx.restore();
}
/* ── AVATAR SYSTEM ──
Multi-source, timeout-protected profile picture fetching.
Tries, in order: usersData.getAvatarUrl -> usersData.getData (avatarUrl/
profilePicture/thumbSrc/avatar) -> api.getUserInfo (thumbSrc/profileUrl)
-> Facebook Graph API picture endpoint (always-available fallback).
Each candidate URL is loaded via a streaming fetch first (if the bot
framework exposes global.utils.getStreamFromURL), then via a direct
axios request, both guarded by timeouts so a slow/dead host can never
hang banner generation. */
function withTimeout(promise, ms = 8000) {
return Promise.race([
promise,
new Promise((_, rej) => setTimeout(() => rej(new Error("timeout")), ms))
]);
}
async function loadFromUrl(url) {
if (!url) return null;
try {
const res = await withTimeout(
axios.get(url, { responseType: "arraybuffer", timeout: 7000 }),
8500
);
const buf = Buffer.from(res.data);
if (buf.length < 100) return null;
return await loadImage(buf);
} catch (e) {
return null;
}
}
async function loadFromStream(url) {
if (!url || typeof global?.utils?.getStreamFromURL !== "function") return null;
try {
const stream = await withTimeout(global.utils.getStreamFromURL(url), 8000);
if (!stream) return null;
const chunks = [];
await withTimeout(
new Promise((res, rej) => {
stream.on("data", c => chunks.push(c));
stream.on("end", () => res());
stream.on("error", rej);
}),
8000
);
const buf = Buffer.concat(chunks);
if (buf.length < 100) return null;
return await loadImage(buf);
} catch (e) {
return null;
}
}
async function fetchAvatarImage(uid, api, usersData) {
if (!uid) return null;
const urls = [];
if (typeof usersData?.getAvatarUrl === "function") {
try {
const u = await withTimeout(usersData.getAvatarUrl(uid), 4000);
if (u) urls.push(u);
} catch (e) {}
}
if (typeof usersData?.getData === "function") {
try {
const d = await withTimeout(usersData.getData(uid), 4000);
for (const key of ["avatarUrl", "profilePicture", "thumbSrc", "avatar"]) {
if (d?.[key]) { urls.push(d[key]); break; }
}
} catch (e) {}
}
if (api?.getUserInfo) {
try {
const info = await withTimeout(api.getUserInfo(uid), 4000);
const u = info?.[uid]?.thumbSrc || info?.[uid]?.profileUrl;
if (u) urls.push(u);
} catch (e) {}
}
urls.push(`https://graph.facebook.com/${uid}/picture?width=512&height=512&type=large`);
for (const url of urls) {
const img = (await loadFromStream(url)) || (await loadFromUrl(url));
if (img) return img;
}
return null;
}
async function fetchAvatarImages(uids, api, usersData) {
const map = {};
await Promise.all(
[...new Set(uids)].map(async uid => {
map[uid] = await fetchAvatarImage(uid, api, usersData);
})
);
return map;
}
async function drawAvatarSmart(ctx, cx, cy, radius, name, avatarImg, opts = {}) {
if (avatarImg) {
try {
const img = avatarImg;
ctx.save();
hexPath(ctx, cx, cy, radius);
ctx.clip();
const scale = Math.max((radius * 2) / img.width, (radius * 2) / img.height);
const dw = img.width * scale, dh = img.height * scale;
ctx.drawImage(img, cx - dw / 2, cy - dh / 2, dw, dh);
ctx.restore();
hexPath(ctx, cx, cy, radius);
ctx.strokeStyle = "rgba(255,255,255,0.45)";
ctx.lineWidth = Math.max(1.2, radius * 0.05);
ctx.stroke();
hexPath(ctx, cx, cy, radius * 0.9);
ctx.strokeStyle = "rgba(0,0,0,0.3)";
ctx.lineWidth = 1;
ctx.stroke();
return;
} catch (e) {
// fall through to the letter badge below
}
}
drawAvatar(ctx, cx, cy, radius, name, opts);
}
function drawStars(ctx, cx, cy, filled) {
const spacing = 22;
const startX = cx - spacing * 2;
for (let i = 0; i < 5; i++) {
drawStar(ctx, startX + i * spacing, cy, 9, i < filled ? "#f7d774" : "rgba(244,240,230,0.2)");
}
}
function drawStar(ctx, cx, cy, r, color) {
ctx.save();
ctx.translate(cx, cy);
ctx.beginPath();
for (let i = 0; i < 5; i++) {
ctx.lineTo(Math.cos((18 + i * 72) * Math.PI / 180) * r, -Math.sin((18 + i * 72) * Math.PI / 180) * r);
ctx.lineTo(Math.cos((54 + i * 72) * Math.PI / 180) * r * 0.4, -Math.sin((54 + i * 72) * Math.PI / 180) * r * 0.4);
}
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.restore();
}
function tierFromDays(days) {
if (days >= 30) return { label: "PLATINUM", stars: 5 };
if (days >= 15) return { label: "GOLD", stars: 4 };
if (days >= 7) return { label: "SILVER", stars: 3 };
if (days >= 1) return { label: "BRONZE", stars: 2 };
return { label: "MEMBER", stars: 1 };
}
function saveCanvas(canvas, filename) {
const filePath = path.join(CACHE_DIR, filename);
fs.writeFileSync(filePath, canvas.toBuffer("image/png"));
return filePath;
}
async function generateBuyBanner({ name, days, price, remaining, expires, avatarImg }) {
const W = 940, H = 670;
const { canvas, ctx } = baseTerminal(W, H);
drawDigitalHeader(ctx, W, "VIP MARKETPLACE", "Digital Purchase Terminal");
drawHUDCorners(ctx, W, H);
const cardX = 80, cardY = 250, cardW = W - 160, cardH = 130;
ctx.fillStyle = "rgba(75,227,255,0.05)";
drawRoundedRect(ctx, cardX, cardY, cardW, cardH, 12);
ctx.fill();
ctx.strokeStyle = "rgba(75,227,255,0.4)";
ctx.lineWidth = 1.5;
drawRoundedRect(ctx, cardX, cardY, cardW, cardH, 12);
ctx.stroke();
const avR = 30, avX = cardX + 30 + avR, avY = cardY + 82;
await drawAvatarSmart(ctx, avX, avY, avR, name, avatarImg, { colors: [CYAN.bright, CYAN.mid, CYAN.deep, CYAN.mid, CYAN.dark] });
const textX = cardX + 30 + avR * 2 + 20;
ctx.textAlign = "left";
ctx.font = "bold 24px monospace";
ctx.fillStyle = CYAN.bright;
ctx.fillText("VIP MEMBERSHIP PASS", textX, cardY + 44);
ctx.font = "15px monospace";
ctx.fillStyle = "#7fb8c9";
ctx.fillText(`USER: ${name}`, textX, cardY + 74);
ctx.fillText(`ACCESS: ${days}-DAY`, textX, cardY + 100);
drawPriceChip(ctx, cardX + cardW - 180, cardY + 30, formatMoneyPlain(price));
drawApprovalChip(ctx, cardX + cardW - 100, cardY + cardH + 40, "APPROVED", CYAN.mid);
const maxTierPrice = calculatePrice(30);
const rowX = 80, rowW = W - 160;
let y = 470;
const gap = 46;
drawDigitalRow(ctx, rowX, y, rowW, "DURATION", `${days} DAYS`, Math.min(1, days / 30)); y += gap;
drawDigitalRow(ctx, rowX, y, rowW, "AMOUNT PAID", formatMoneyPlain(price), Math.min(1, price / maxTierPrice), CYAN.bright); y += gap;
drawDigitalRow(ctx, rowX, y, rowW, "BALANCE REMAINING", formatMoneyPlain(remaining), remaining / (remaining + price || 1)); y += gap;
drawDigitalRow(ctx, rowX, y, rowW, "VALID UNTIL", formatDatePlain(expires).toUpperCase(), 0.15);
return saveCanvas(canvas, `vip_buy_${Date.now()}.png`);
}
async function generateStatusBanner({ name, expires, timeLeft, avatarImg }) {
const W = 940, H = 560;
const { canvas, ctx } = baseTerminal(W, H);
drawDigitalHeader(ctx, W, "VIP MARKETPLACE", "Membership Status Terminal");
drawHUDCorners(ctx, W, H);
const tier = tierFromDays(timeLeft.days);
const avX = W / 2, avY = 250, avR = 46;
await drawAvatarSmart(ctx, avX, avY, avR, name, avatarImg, { colors: [CYAN.bright, CYAN.mid, CYAN.deep, CYAN.mid, CYAN.dark] });
ctx.textAlign = "center";
ctx.font = "bold 26px monospace";
ctx.fillStyle = CYAN.bright;
ctx.fillText(name, W / 2, avY + 82);
ctx.font = "bold 13px monospace";
ctx.fillStyle = "#7fb8c9";
ctx.fillText(`${tier.label} TIER`, W / 2, avY + 106);
drawApprovalChip(ctx, W / 2, avY + 144, "ACTIVE", CYAN.mid);
const rowX = 80, rowW = W - 160;
let y = 410;
const gap = 46;
drawDigitalRow(ctx, rowX, y, rowW, "STATUS", "ACTIVE MEMBER", 1, CYAN.mid); y += gap;
drawDigitalRow(ctx, rowX, y, rowW, "VALID UNTIL", formatDatePlain(expires).toUpperCase(), 0.4); y += gap;
drawDigitalRow(ctx, rowX, y, rowW, "TIME REMAINING", timeLeft.text.toUpperCase(), Math.min(1, timeLeft.days / 30), CYAN.bright);
return saveCanvas(canvas, `vip_status_${Date.now()}.png`);
}
async function generateListBanner(pageUsers, page, totalPages, totalCount) {
const rows = pageUsers.length;
const rowHeight = 60;
const W = 940;
const H = 300 + rows * rowHeight + 70;
const { canvas, ctx } = baseTerminal(W, H);
drawDigitalHeader(ctx, W, "VIP MARKETPLACE", `Member Directory // Page ${page} of ${totalPages}`);
drawHUDCorners(ctx, W, H);
const startX = 80, endX = W - 80;
let y = 270;
ctx.textAlign = "left";
ctx.font = "bold 13px monospace";
ctx.fillStyle = CYAN.bright;
ctx.fillText("MEMBER", startX + 56, y);
ctx.textAlign = "right";
ctx.fillText("VALID UNTIL", startX + 400, y);
ctx.fillText("TIME LEFT", endX, y);
y += 12;
ctx.strokeStyle = "rgba(75,227,255,0.35)";
ctx.lineWidth = 1;
ctx.setLineDash([4, 3]);
ctx.beginPath(); ctx.moveTo(startX, y); ctx.lineTo(endX, y); ctx.stroke();
ctx.setLineDash([]);
y += 34;
const baseRank = (page - 1) * LIST_PAGE_SIZE;
for (let i = 0; i < rows; i++) {
const u = pageUsers[i];
const top = y - 26;
ctx.fillStyle = "rgba(75,227,255,0.05)";
drawRoundedRect(ctx, startX, top, endX - startX, 42, 6);
ctx.fill();
ctx.strokeStyle = "rgba(75,227,255,0.28)";
ctx.lineWidth = 1;
drawRoundedRect(ctx, startX, top, endX - startX, 42, 6);
ctx.stroke();
drawMedal(ctx, startX + 26, y - 4, baseRank + i + 1);
await drawAvatarSmart(ctx, startX + 78, y - 4, 16, u.name, u.avatarImg, { colors: [CYAN.bright, CYAN.mid, CYAN.deep, CYAN.mid, CYAN.dark] });
ctx.textAlign = "left";
ctx.font = "bold 16px monospace";
ctx.fillStyle = CYAN.bright;
ctx.fillText(u.name, startX + 106, y);
ctx.textAlign = "right";
ctx.font = "13px monospace";
ctx.fillStyle = "#7fb8c9";
ctx.fillText(u.expiresText.toUpperCase(), startX + 400, y);
ctx.font = "bold 14px monospace";
ctx.fillStyle = CYAN.bright;
ctx.fillText(`[ ${u.timeLeftText.toUpperCase()} ]`, endX, y);
y += rowHeight;
}
ctx.textAlign = "center";
ctx.font = "11px monospace";
ctx.fillStyle = "rgba(75,227,255,0.5)";
ctx.fillText(`${totalCount} TOTAL MEMBERS // PAGE ${page}/${totalPages}`, W / 2, y + 20);
return saveCanvas(canvas, `vip_list_${Date.now()}.png`);
}
async function generateAddBanner({ targetName, days, expires, avatarImg }) {
const W = 940, H = 510;
const { canvas, ctx } = baseTerminal(W, H);
drawDigitalHeader(ctx, W, "VIP MARKETPLACE", "Admin Grant Terminal");
drawHUDCorners(ctx, W, H);
await drawAvatarSmart(ctx, W / 2, 190, 40, targetName, avatarImg, { colors: [CYAN.bright, CYAN.mid, CYAN.deep, CYAN.mid, CYAN.dark] });
ctx.textAlign = "center";
ctx.font = "bold 26px monospace";
ctx.fillStyle = CYAN.bright;
ctx.fillText(targetName, W / 2, 262);
ctx.font = "14px monospace";
ctx.fillStyle = "#7fb8c9";
ctx.fillText("HAS BEEN GRANTED VIP ACCESS", W / 2, 288);
drawApprovalChip(ctx, W / 2, 322, "GRANTED", CYAN.mid);
const rowX = 80, rowW = W - 160;
let y = 400;
const gap = 46;
drawDigitalRow(ctx, rowX, y, rowW, "DURATION ADDED", `${days} DAYS`, Math.min(1, days / 30), CYAN.bright); y += gap;
drawDigitalRow(ctx, rowX, y, rowW, "NEW EXPIRY", formatDatePlain(expires).toUpperCase(), 0.4);
return saveCanvas(canvas, `vip_add_${Date.now()}.png`);
}
async function generateRemoveBanner({ targetName, avatarImg }) {
const W = 940, H = 400;
const { canvas, ctx } = baseTerminal(W, H);
drawDigitalHeader(ctx, W, "VIP MARKETPLACE", "Admin Revocation Terminal");
drawHUDCorners(ctx, W, H);
await drawAvatarSmart(ctx, W / 2, 190, 34, targetName, avatarImg, {
colors: ["#ffd6d6", "#ff8a8a", "#a83232", "#ff8a8a", "#5e1414"],
textColor: "#2b0a0a"
});
ctx.textAlign = "center";
ctx.font = "bold 24px monospace";
ctx.fillStyle = "#ff8a8a";
ctx.fillText(targetName, W / 2, 272);
ctx.font = "14px monospace";
ctx.fillStyle = "#c98a8a";
ctx.fillText("VIP MEMBERSHIP HAS BEEN REVOKED", W / 2, 298);
drawApprovalChip(ctx, W / 2, 334, "REVOKED", "#ff5a5a");
return saveCanvas(canvas, `vip_remove_${Date.now()}.png`);
}
function generatePriceListBanner() {
const tiers = [1, 2, 3, 4, 5, 6, 7, 15, 30].map(d => [d, calculatePrice(d)]);
const rowHeight = 44;
const W = 780, H = 300 + tiers.length * rowHeight + 60;
const { canvas, ctx } = baseTerminal(W, H);
drawDigitalHeader(ctx, W, "VIP MARKETPLACE", "Live Access Rate Terminal");
drawHUDCorners(ctx, W, H);
const maxPrice = calculatePrice(30);
const rowX = 90, rowW = W - 180;
let y = 300;
for (const [d, p] of tiers) {
drawDigitalRow(ctx, rowX, y, rowW, `${d} DAY${d > 1 ? "S" : ""} ACCESS`, formatMoneyPlain(p), p / maxPrice, CYAN.bright);
y += rowHeight;
}
ctx.textAlign = "center";
ctx.font = "11px monospace";
ctx.fillStyle = "rgba(75,227,255,0.5)";
ctx.fillText("TAP {P}VIP BUY <DAYS> // OR REPLY WITH A NUMBER TO BUY", W / 2, y + 26);
return saveCanvas(canvas, `vip_prices_${Date.now()}.png`);
}
function replyCard(message, bannerPath, onSent) {
return message.reply({ attachment: fs.createReadStream(bannerPath) })
.then((info) => {
fs.unlink(bannerPath, () => {});
if (typeof onSent === "function") {
try { onSent(info); } catch (e) {}
}
return info;
});
}
function registerBuyReply(info, uid) {
if (!info || !info.messageID) return;
if (!global.GoatBot || !global.GoatBot.onReply) return;
global.GoatBot.onReply.set(info.messageID, {
commandName: "vip",
messageID: info.messageID,
author: uid,
type: "buyDays"
});
}
async function performBuy({ days, uid, api, usersData, message }) {
if (!days || isNaN(days) || days <= 0) {
return message.reply(
"• 📌 𝐔𝐬𝐞: {𝐩}𝐯𝐢𝐩 𝐛𝐮𝐲 <𝐝𝐚𝐲𝐬>\n" +
"📌 𝐄𝐱𝐚𝐦𝐩𝐥𝐞: {𝐩}𝐯𝐢𝐩 𝐛𝐮𝐲 𝟏𝟓\n" +
"💡 𝐘𝐨𝐮 𝐜𝐚𝐧 𝐛𝐮𝐲 𝐚𝐧𝐲 𝐧𝐮𝐦𝐛𝐞𝐫 𝐨𝐟 𝐝𝐚𝐲𝐬!"
);
}
const price = calculatePrice(days);
const user = await usersData.get(uid);
const wallet = user.money || 0;
if (wallet < price) {
return message.reply(
`• ❌ | 𝐈𝐧𝐬𝐮𝐟𝐟𝐢𝐜𝐢𝐞𝐧𝐭 𝐁𝐚𝐥𝐚𝐧𝐜𝐞!\n` +
`╭‣ 𝐘𝐨𝐮𝐫 𝐁𝐚𝐥𝐚𝐧𝐜𝐞: ${formatMoneyPlain(wallet)}\n` +
`╰‣ 𝐍𝐞𝐞𝐝𝐞𝐝: ${formatMoneyPlain(price - wallet)}`
);
}
await usersData.set(uid, { money: wallet - price });
const currentData = user.data || {};
const currentExpire = currentData.vip?.expires || Date.now();
const startTime = Math.max(currentExpire, Date.now());
const expires = startTime + (days * DAY);
await usersData.set(uid, { data: { ...currentData, vip: { expires } } });
const userName = await usersData.getName(uid);
const avatarImg = await fetchAvatarImage(uid, api, usersData);
let bannerPath;
try {
bannerPath = await generateBuyBanner({
name: userName,
days,
price,
remaining: wallet - price,
expires,
avatarImg
});
} catch (e) {
console.error("VIP banner render failed:", e);
}
if (bannerPath) return replyCard(message, bannerPath);
return message.reply("• ✅ | 𝐕𝐈𝐏 𝐚𝐜𝐭𝐢𝐯𝐚𝐭𝐞𝐝.");
}
module.exports = {
config: {
name: "vip",
version: "3.2",
author: "Arafat",
countDown: 5,
role: 0,
shortDescription: { en: "Premium VIP marketplace system" },
longDescription: { en: "Buy VIP, admin add/remove, auto-expire, extend time — premium marketplace canvas cards, paginated list" },
category: "economy",
guide: {
en: "{p}vip\n" +
"{p}vip list [page]\n" +
"{p}vip my\n" +
"{p}vip buy <days>\n" +
"{p}vip add <days> (reply/mention)\n" +
"{p}vip remove (reply/mention)\n" +
"{p}vip extend <days> (reply/mention)\n" +
"\nTip: reply to the {p}vip price board (or the {p}vip buy usage message) with just a number of days to buy instantly."
}
},
onStart: async function ({ api, event, args, usersData, message, role }) {
const uid = event.senderID;
const now = Date.now();
if (cooldown.get(uid) && now - cooldown.get(uid) < 3000)
return message.reply("• ⏳ 𝐏𝐥𝐞𝐚𝐬𝐞 𝐰𝐚𝐢𝐭 𝟑 𝐬𝐞𝐜𝐨𝐧𝐝𝐬.");
cooldown.set(uid, now);
const allUsers = await usersData.getAll();
for (const u of allUsers) {
if (u.data?.vip?.expires && u.data.vip.expires <= Date.now()) {
await usersData.set(u.userID, { data: { ...u.data, vip: null } });
}
}
if (args[0] === "my") {
const user = await usersData.get(uid);
const vipData = user.data?.vip;
if (!vipData || !vipData.expires || vipData.expires <= Date.now()) {
return message.reply(
"• ❌ | 𝐘𝐨𝐮 𝐝𝐨𝐧'𝐭 𝐡𝐚𝐯𝐞 𝐚𝐧 𝐚𝐜𝐭𝐢𝐯𝐞 𝐕𝐈𝐏 𝐬𝐮𝐛𝐬𝐜𝐫𝐢𝐩𝐭𝐢𝐨𝐧."
);
}
const timeLeftPlain = getTimeRemainingPlain(vipData.expires);
const userName = await usersData.getName(uid);
const avatarImg = await fetchAvatarImage(uid, api, usersData);
let bannerPath;
try {
bannerPath = await generateStatusBanner({
name: userName,
expires: vipData.expires,
timeLeft: timeLeftPlain,
avatarImg
});
} catch (e) {
console.error("VIP banner render failed:", e);
}
if (bannerPath) return replyCard(message, bannerPath);
return message.reply("• ⚠️ | 𝐂𝐨𝐮𝐥𝐝 𝐧𝐨𝐭 𝐫𝐞𝐧𝐝𝐞𝐫 𝐲𝐨𝐮𝐫 𝐕𝐈𝐏 𝐜𝐚𝐫𝐝.");
}
if (args[0] === "list") {
const freshUsers = await usersData.getAll();
const listData = [];
for (const u of freshUsers) {
if (u.data?.vip?.expires && u.data.vip.expires > Date.now()) {
const timeLeftPlain = getTimeRemainingPlain(u.data.vip.expires);
listData.push({
userID: u.userID,
name: u.name || "User",
expiresText: formatDatePlain(u.data.vip.expires),
timeLeftText: timeLeftPlain.text,
_sortDays: timeLeftPlain.days
});
}
}
if (!listData.length) return message.reply("• ⚠️ | 𝐍𝐨 𝐚𝐜𝐭𝐢𝐯𝐞 𝐕𝐈𝐏 𝐮𝐬𝐞𝐫𝐬 𝐟𝐨𝐮𝐧𝐝.");
listData.sort((a, b) => b._sortDays - a._sortDays);
const totalPages = Math.max(1, Math.ceil(listData.length / LIST_PAGE_SIZE));
let page = parseInt(args[1]) || 1;
if (page < 1) page = 1;
if (page > totalPages) {
return message.reply(`• ⚠️ | 𝐎𝐧𝐥𝐲 ${totalPages} 𝐩𝐚𝐠𝐞(𝐬) 𝐚𝐯𝐚𝐢𝐥𝐚𝐛𝐥𝐞. 𝐔𝐬𝐞: {𝐩}𝐯𝐢𝐩 𝐥𝐢𝐬𝐭 <𝐩𝐚𝐠𝐞>`);
}
const start = (page - 1) * LIST_PAGE_SIZE;
const pageUsers = listData.slice(start, start + LIST_PAGE_SIZE);
const avatarMap = await fetchAvatarImages(pageUsers.map(u => u.userID), api, usersData);
for (const u of pageUsers) u.avatarImg = avatarMap[u.userID] || null;
let bannerPath;
try {
bannerPath = await generateListBanner(pageUsers, page, totalPages, listData.length);
} catch (e) {
console.error("VIP banner render failed:", e);
}
if (bannerPath) return replyCard(message, bannerPath);
return message.reply("• ⚠️ | 𝐂𝐨𝐮𝐥𝐝 𝐧𝐨𝐭 𝐫𝐞𝐧𝐝𝐞𝐫 𝐭𝐡𝐞 𝐕𝐈𝐏 𝐥𝐢𝐬𝐭.");
}
const getTargetID = () => {
if (event.messageReply) return event.messageReply.senderID;
if (Object.keys(event.mentions).length > 0) return Object.keys(event.mentions)[0];
return null;
};
const isAdmin = role >= 2;
if (args[0] === "add") {
if (!isAdmin) return message.reply("• ❌ | 𝐎𝐧𝐥𝐲 𝐦𝐲 𝐚𝐝𝐦𝐢𝐧 𝐀𝐫𝐚𝐟𝐚𝐭 𝐜𝐚𝐧 𝐮𝐬𝐞 𝐭𝐡𝐢𝐬 𝐜𝐨𝐦𝐦𝐚𝐧𝐝!.");
const days = parseInt(args[1]);
if (!days || isNaN(days) || days <= 0) return message.reply("• ❌ 𝐏𝐥𝐞𝐚𝐬𝐞 𝐩𝐫𝐨𝐯𝐢𝐝𝐞 𝐯𝐚𝐥𝐢𝐝 𝐝𝐚𝐲𝐬.");
const tid = getTargetID();
if (!tid) return message.reply("• ❌ | 𝐑𝐞𝐩𝐥𝐲 𝐨𝐫 𝐦𝐞𝐧𝐭𝐢𝐨𝐧 𝐚 𝐮𝐬𝐞𝐫.");
const user = await usersData.get(tid);
const currentData = user.data || {};
const currentExpire = currentData.vip?.expires || Date.now();
const startTime = Math.max(currentExpire, Date.now());
const expires = startTime + (days * DAY);
await usersData.set(tid, { data: { ...currentData, vip: { expires } } });
const name = await usersData.getName(tid);
const avatarImg = await fetchAvatarImage(tid, api, usersData);
let bannerPath;
try {
bannerPath = await generateAddBanner({ targetName: name, days, expires, avatarImg });
} catch (e) {
console.error("VIP banner render failed:", e);
}
if (bannerPath) return replyCard(message, bannerPath);
return message.reply("• ✅ | 𝐕𝐈𝐏 𝐚𝐝𝐝𝐞𝐝.");
}
if (args[0] === "remove") {
if (!isAdmin) return message.reply("• ❌ | 𝐎𝐧𝐥𝐲 𝐦𝐲 𝐚𝐝𝐦𝐢𝐧 𝐀𝐫𝐚𝐟𝐚𝐭 𝐜𝐚𝐧 𝐮𝐬𝐞 𝐭𝐡𝐢𝐬 𝐜𝐨𝐦𝐦𝐚𝐧𝐝!");
const tid = getTargetID();
if (!tid) return message.reply("• ❌ | 𝐑𝐞𝐩𝐥𝐲 𝐨𝐫 𝐦𝐞𝐧𝐭𝐢𝐨𝐧 𝐚 𝐮𝐬𝐞𝐫.");
const user = await usersData.get(tid);
await usersData.set(tid, { data: { ...user.data, vip: null } });
const name = await usersData.getName(tid);
const avatarImg = await fetchAvatarImage(tid, api, usersData);
let bannerPath;
try {
bannerPath = await generateRemoveBanner({ targetName: name, avatarImg });
} catch (e) {
console.error("VIP banner render failed:", e);
}
if (bannerPath) return replyCard(message, bannerPath);
return message.reply("• ✅ | 𝐕𝐈𝐏 𝐫𝐞𝐦𝐨𝐯𝐞𝐝.");
}
if (args[0] === "buy") {
const days = parseInt(args[1]);
if (!days || isNaN(days) || days <= 0) {
const info = await message.reply(
"• 📌 𝐔𝐬𝐞: {𝐩}𝐯𝐢𝐩 𝐛𝐮𝐲 <𝐝𝐚𝐲𝐬>\n" +
"📌 𝐄𝐱𝐚𝐦𝐩𝐥𝐞: {𝐩}𝐯𝐢𝐩 𝐛𝐮𝐲 𝟏𝟓\n" +
"💡 𝐎𝐫 𝐣𝐮𝐬𝐭 𝐫𝐞𝐩𝐥𝐲 𝐭𝐨 𝐭𝐡𝐢𝐬 𝐦𝐞𝐬𝐬𝐚𝐠𝐞 𝐰𝐢𝐭𝐡 𝐚 𝐧𝐮𝐦𝐛𝐞𝐫 𝐨𝐟 𝐝𝐚𝐲𝐬!"
);
registerBuyReply(info, uid);
return;
}
return performBuy({ days, uid, api, usersData, message });
}
let priceBannerPath;
try {
priceBannerPath = generatePriceListBanner();
} catch (e) {
console.error("VIP banner render failed:", e);
}
if (priceBannerPath) {
return replyCard(message, priceBannerPath, (info) => registerBuyReply(info, uid));
}
return message.reply("• ⚠️ | 𝐂𝐨𝐮𝐥𝐝 𝐧𝐨𝐭 𝐫𝐞𝐧𝐝𝐞𝐫 𝐭𝐡𝐞 𝐩𝐫𝐢𝐜𝐞 𝐛𝐨𝐚𝐫𝐝.");
},
onReply: async function ({ api, event, Reply, message, usersData }) {
if (!Reply || Reply.type !== "buyDays") return;
if (event.senderID !== Reply.author) return;
const days = parseInt((event.body || "").trim());
if (!days || isNaN(days) || days <= 0) {
return message.reply("• ❌ | 𝐑𝐞𝐩𝐥𝐲 𝐰𝐢𝐭𝐡 𝐚 𝐯𝐚𝐥𝐢𝐝 𝐧𝐮𝐦𝐛𝐞𝐫 𝐨𝐟 𝐝𝐚𝐲𝐬, 𝐞.𝐠. 𝟏𝟓.");
}
return performBuy({ days, uid: event.senderID, api, usersData, message });
}
};