"use strict"; const axios = require("axios"); const fs = require("fs-extra"); const path = require("path"); let createCanvas, loadImage; try { ({ createCanvas, loadImage } = require("canvas")); } catch {} const BASE = "https://xncdi.vercel.app"; const TMP_DIR = path.join(process.cwd(), "scripts", "tmp"); fs.ensureDirSync(TMP_DIR); const truncate = (str = "", max = 50) => str.length > max ? str.slice(0, max - 1) + "โ€ฆ" : str; const QUALITY_COLOR = (q = "") => q.includes("1080") ? "#1e7aff" : q.includes("720") ? "#22bb55" : q.includes("480") ? "#e6a800" : "#888888"; const THUMB_GRADIENTS = [ ["#3d0c0c", "#7a1a1a"], ["#0c1e3d", "#1a3a7a"], ["#0c3d1a", "#1a7a35"], ["#2a0c3d", "#501a7a"], ["#3d2a0c", "#7a551a"], ["#0c3d3d", "#1a7070"], ["#3d3d0c", "#7a7a1a"], ["#1a0c3d", "#351a7a"], ]; function react(botApi, event, emoji) { try { botApi.setMessageReaction(emoji, event.messageID, () => {}, true); } catch (_) {} } async function fetchRetry(url, params = {}, timeout = 20000) { for (let i = 0; i < 3; i++) { try { return await axios.get(url, { params, timeout }); } catch (err) { if (i === 2) throw err; await new Promise(r => setTimeout(r, 1500)); } } } function wrapText(ctx, text, x, y, maxWidth, lineHeight, maxLines) { const words = text.split(" "); let line = "", linesDrawn = 0; for (let i = 0; i < words.length; i++) { const test = line + (line ? " " : "") + words[i]; if (ctx.measureText(test).width > maxWidth && line) { if (linesDrawn >= maxLines - 1) { ctx.fillText(line.trimEnd() + "โ€ฆ", x, y); return; } ctx.fillText(line, x, y); y += lineHeight; linesDrawn++; line = words[i]; } else { line = test; } } if (line && linesDrawn < maxLines) ctx.fillText(line, x, y); } function roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); } async function makeXncdiCard(results, query, isTrending) { if (!createCanvas) return null; const COLS = 4, ROWS = Math.ceil(Math.min(results.length, 8) / COLS); const THUMB_W = 192, THUMB_H = 108, INFO_H = 52, GAP = 6; const PAD_X = 12, PAD_Y = 12, HEADER_H = 58, FOOTER_H = 36; const W = PAD_X * 2 + COLS * THUMB_W + (COLS - 1) * GAP; const H = HEADER_H + PAD_Y + ROWS * (THUMB_H + INFO_H + GAP) - GAP + PAD_Y + FOOTER_H; const canvas = createCanvas(W, H); const ctx = canvas.getContext("2d"); ctx.antialias = "subpixel"; ctx.fillStyle = "#111111"; ctx.fillRect(0, 0, W, H); const hGrad = ctx.createLinearGradient(0, 0, W, HEADER_H); hGrad.addColorStop(0, "#c0170f"); hGrad.addColorStop(1, "#8c0f09"); ctx.fillStyle = hGrad; ctx.fillRect(0, 0, W, HEADER_H); ctx.fillStyle = "#ffffff"; ctx.font = "bold 28px sans-serif"; ctx.fillText("XNXX", PAD_X, 38); ctx.fillStyle = "rgba(255,255,255,0.3)"; ctx.fillRect(74, 16, 2, 28); ctx.fillStyle = "rgba(255,255,255,0.85)"; ctx.font = "14px sans-serif"; ctx.fillText(isTrending ? `๐Ÿ”ฅ Trending${query ? " โ€” " + query : ""}` : `๐Ÿ” ${truncate(query, 38)}`, 84, 36); const countLabel = `${results.length} videos`; const countW = ctx.measureText(countLabel).width + 16; ctx.fillStyle = "rgba(0,0,0,0.4)"; roundRect(ctx, W - PAD_X - countW, 17, countW, 22, 11); ctx.fill(); ctx.fillStyle = "#fff"; ctx.font = "bold 11px sans-serif"; ctx.fillText(countLabel, W - PAD_X - countW + 8, 32); const dotColors = ["#ff5f56", "#ffbd2e", "#27c93f"]; for (let i = 0; i < 3; i++) { ctx.fillStyle = dotColors[i]; ctx.beginPath(); ctx.arc(PAD_X + i * 18, HEADER_H - 10, 5, 0, Math.PI * 2); ctx.fill(); } for (let i = 0; i < Math.min(results.length, 8); i++) { const r = results[i]; const col = i % COLS; const row = Math.floor(i / COLS); const x = PAD_X + col * (THUMB_W + GAP); const y = HEADER_H + PAD_Y + row * (THUMB_H + INFO_H + GAP); const [gc1, gc2] = THUMB_GRADIENTS[i % THUMB_GRADIENTS.length]; const tGrad = ctx.createLinearGradient(x, y, x + THUMB_W, y + THUMB_H); tGrad.addColorStop(0, gc1); tGrad.addColorStop(1, gc2); ctx.fillStyle = tGrad; roundRect(ctx, x, y, THUMB_W, THUMB_H, 4); ctx.fill(); const thumbUrl = r.thumbnail || r.thumb || r.image || r.cover || null; if (thumbUrl && loadImage) { try { const img = await loadImage(thumbUrl); ctx.save(); roundRect(ctx, x, y, THUMB_W, THUMB_H, 4); ctx.clip(); ctx.drawImage(img, x, y, THUMB_W, THUMB_H); ctx.restore(); } catch {} } const vigGrad = ctx.createLinearGradient(x, y, x, y + THUMB_H); vigGrad.addColorStop(0, "rgba(0,0,0,0)"); vigGrad.addColorStop(0.6, "rgba(0,0,0,0.15)"); vigGrad.addColorStop(1, "rgba(0,0,0,0.65)"); ctx.fillStyle = vigGrad; roundRect(ctx, x, y, THUMB_W, THUMB_H, 4); ctx.fill(); const cx = x + THUMB_W / 2, cy = y + THUMB_H / 2; ctx.fillStyle = "rgba(0,0,0,0.55)"; ctx.beginPath(); ctx.arc(cx, cy, 20, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = "#ffffff"; ctx.beginPath(); ctx.moveTo(cx - 7, cy - 10); ctx.lineTo(cx - 7, cy + 10); ctx.lineTo(cx + 12, cy); ctx.closePath(); ctx.fill(); ctx.fillStyle = "#c0170f"; roundRect(ctx, x + 5, y + 5, 24, 20, 3); ctx.fill(); ctx.fillStyle = "#fff"; ctx.font = "bold 12px sans-serif"; ctx.textAlign = "center"; ctx.fillText(String(i + 1), x + 17, y + 19); ctx.textAlign = "left"; const dur = r.duration || ""; if (dur) { const dW = ctx.measureText(dur).width + 10; ctx.fillStyle = "rgba(0,0,0,0.82)"; roundRect(ctx, x + THUMB_W - dW - 5, y + THUMB_H - 22, dW, 17, 3); ctx.fill(); ctx.fillStyle = "#ffffff"; ctx.font = "bold 11px sans-serif"; ctx.fillText(dur, x + THUMB_W - dW - 1, y + THUMB_H - 9); } const qual = r.quality || ""; if (qual) { const qW = ctx.measureText(qual).width + 10; ctx.fillStyle = QUALITY_COLOR(qual); roundRect(ctx, x + 5, y + THUMB_H - 22, qW, 17, 3); ctx.fill(); ctx.fillStyle = "#fff"; ctx.font = "bold 10px sans-serif"; ctx.fillText(qual, x + 9, y + THUMB_H - 9); } ctx.fillStyle = "#1c1c1c"; ctx.fillRect(x, y + THUMB_H, THUMB_W, INFO_H); ctx.fillStyle = "#2a2a2a"; ctx.fillRect(x, y + THUMB_H + INFO_H - 1, THUMB_W, 1); ctx.fillStyle = "#e8e8e8"; ctx.font = "12px sans-serif"; wrapText(ctx, r.title || "โ”€", x + 5, y + THUMB_H + 15, THUMB_W - 10, 15, 2); ctx.fillStyle = "#777777"; ctx.font = "10px sans-serif"; ctx.fillText( [r.views ? `๐Ÿ‘ ${r.views}` : "", r.rating ? `โญ ${r.rating}` : ""].filter(Boolean).join(" "), x + 5, y + THUMB_H + 46 ); } const fGrad = ctx.createLinearGradient(0, H - FOOTER_H, W, H); fGrad.addColorStop(0, "#8c0f09"); fGrad.addColorStop(1, "#c0170f"); ctx.fillStyle = fGrad; ctx.fillRect(0, H - FOOTER_H, W, FOOTER_H); ctx.fillStyle = "#ffffff"; ctx.font = "bold 13px sans-serif"; ctx.fillText("โฌ† Reply 1โ€“8 to stream", PAD_X, H - FOOTER_H + 23); ctx.fillStyle = "rgba(255,255,255,0.55)"; ctx.font = "11px sans-serif"; ctx.textAlign = "right"; ctx.fillText("xncdi", W - PAD_X, H - FOOTER_H + 23); ctx.textAlign = "left"; return canvas; } module.exports = { config: { name: "xncdi", aliases: ["xvideo", "xnxx", "xv"], version: "6.0.0", author: "SIFAT", countDown: 15, role: 1, category: "18+", description: { en: "Search & stream 18+ videos from xncdi" }, guide: { en: "{pn} [keyword]\n{pn} trending [category]" }, }, onStart: async function ({ args, event, message, api: botApi, commandName }) { const { senderID } = event; if (!args[0]) { react(botApi, event, "โŒ"); return message.reply( "๐Ÿ”ž xษดแด„แด…ษช โ€” แดœ๊œฑแด€ษขแด‡\n" + "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n" + "โ—ˆ .xncdi [keyword]\n" + "โ—ˆ .xncdi trending\n" + "โ—ˆ .xncdi trending bangla\n" + "โ—ˆ .xncdi trending hentai" ); } const isTrending = args[0].toLowerCase() === "trending"; const query = isTrending ? args.slice(1).join(" ").trim() : args.join(" ").trim(); react(botApi, event, "๐Ÿ”"); try { let results; if (isTrending) { const { data } = await fetchRetry(`${BASE}/trending`, query ? { category: query } : {}); results = (data.results || data || []).slice(0, 8); } else { const { data } = await fetchRetry(`${BASE}/search`, { q: query, page: 1, sort: "relevance" }); results = (data.results || []).slice(0, 8); } if (!results.length) { react(botApi, event, "โŒ"); return message.reply(`โŒ€ ษดแด ส€แด‡๊œฑแดœสŸแด›๊œฑ ๊œฐแดส€ "${query || "trending"}"`); } react(botApi, event, "โœ…"); const imgPath = path.join(TMP_DIR, `xncdi_list_${Date.now()}.jpg`); let sent; const card = await makeXncdiCard(results, query, isTrending); if (card) { await fs.writeFile(imgPath, card.toBuffer("image/jpeg", { quality: 0.92 })); sent = await message.reply({ body: "", attachment: fs.createReadStream(imgPath) }); setTimeout(() => fs.unlink(imgPath).catch(() => {}), 30_000); } else { sent = await message.reply( `๐Ÿ”ž ${isTrending ? "Trending" : `"${truncate(query, 30)}"`}\n` + `โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n\n` + results.map((r, i) => `[${i + 1}] ${truncate(r.title)}\n โฑ ${r.duration || "?"} โ”‚ ๐Ÿ‘ ${r.views || "?"} โ”‚ ${r.quality || "?"}` ).join("\n\n") + `\n\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\nโ—ˆ ส€แด‡แด˜สŸส 1-${results.length} แด›แด ๊œฑแด›ส€แด‡แด€แด` ); } if (sent?.messageID) { global.GoatBot.onReply.set(sent.messageID, { commandName, messageID: sent.messageID, author: senderID, results, }); } } catch (err) { react(botApi, event, "โŒ"); return message.reply(`โŒ€ ๊œฐแด€ษชสŸแด‡แด…: ${err.message}`); } }, onReply: async function ({ event, Reply, message, api: botApi }) { if (event.senderID !== Reply.author) return; const num = parseInt(event.body?.trim()); if (isNaN(num) || num < 1 || num > Reply.results.length) { return message.reply(`โŒ€ แด‡ษดแด›แด‡ส€ 1โ€“${Reply.results.length}`); } react(botApi, event, "๐Ÿ“ฅ"); try { botApi.unsendMessage(Reply.messageID); } catch {} global.GoatBot.onReply.delete(Reply.messageID); const selected = Reply.results[num - 1]; try { const { data } = await fetchRetry(`${BASE}/video-source`, { url: selected.url }, 35000); const streamUrl = data.best || data.hq || data.lq || Object.values(data.qualities || {})[0] || null; if (!streamUrl) { react(botApi, event, "โŒ"); return message.reply("โŒ€ ษดแด ๊œฑแด›ส€แด‡แด€แด แดœส€สŸ ๊œฐแดแดœษดแด…"); } const tmpPath = path.join(TMP_DIR, `xncdi_${Date.now()}.mp4`); const writer = fs.createWriteStream(tmpPath); const res = await axios({ method: "get", url: streamUrl, responseType: "stream", timeout: 120000, }); await new Promise((resolve, reject) => { res.data.pipe(writer); writer.on("finish", resolve); writer.on("error", reject); res.data.on("error", reject); }); const stat = fs.statSync(tmpPath); if (stat.size < 1024) { fs.unlink(tmpPath).catch(() => {}); react(botApi, event, "โŒ"); return message.reply("โŒ€ แด‡แดแด˜แด›ส ๊œฐษชสŸแด‡ โ€” ส€แด‡แด›ส€ส"); } react(botApi, event, "โœ…"); await message.reply({ body: "", attachment: fs.createReadStream(tmpPath) }); setTimeout(() => fs.unlink(tmpPath).catch(() => {}), 30_000); } catch (err) { react(botApi, event, "โŒ"); return message.reply(`โŒ€ ๊œฑแด›ส€แด‡แด€แด ๊œฐแด€ษชสŸแด‡แด…: ${err.message}`); } }, };