const axios = require("axios"); const fs = require("fs-extra"); const os = require("os"); const path = require("path"); const { createCanvas, loadImage } = require("canvas"); const TMP_DIR = path.join(__dirname, "cache", "ytb"); const PER_PAGE = 8; const REACT = { loading: "⏳", success: "✅", error: "❌", }; const baseApiUrl = async () => { const base = await axios.get(`https://raw.githubusercontent.com/mahmudx7/HINATA/main/baseApiUrl.json`); return base.data.mahmud; }; function truncate(text, maxLen) { if (!text) return "Untitled"; return text.length > maxLen ? text.slice(0, maxLen - 1) + "…" : text; } function formatCount(n) { n = Number(n); if (!n || isNaN(n)) return null; if (n >= 1e9) return (n / 1e9).toFixed(1) + "B"; if (n >= 1e6) return (n / 1e6).toFixed(1) + "M"; if (n >= 1e3) return (n / 1e3).toFixed(1) + "K"; return String(n); } function getChannel(v) { return v.channel?.name || v.channel || v.author || "Unknown"; } function getDuration(v) { return v.time || v.duration_raw || v.duration || ""; } function getViews(v) { return formatCount(v.views || v.view_count || v.viewCount); } const W = 640, HEADER_H = 80, ROW_H = 90, PADDING = 20, THUMB_W = 118, THUMB_H = 66; function drawYouTubeLogo(ctx, x, y) { const rw = 40, rh = 28, r = 8; ctx.fillStyle = "#FF0000"; ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + rw - r, y); ctx.quadraticCurveTo(x + rw, y, x + rw, y + r); ctx.lineTo(x + rw, y + rh - r); ctx.quadraticCurveTo(x + rw, y + rh, x + rw - r, y + rh); ctx.lineTo(x + r, y + rh); ctx.quadraticCurveTo(x, y + rh, x, y + rh - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); ctx.fill(); const cx = x + rw / 2, cy = y + rh / 2; ctx.fillStyle = "#ffffff"; ctx.beginPath(); ctx.moveTo(cx - 5, cy - 8); ctx.lineTo(cx - 5, cy + 8); ctx.lineTo(cx + 9, cy); ctx.closePath(); ctx.fill(); } async function generateSearchImage(pageResults, query, page, maxPage) { const totalH = HEADER_H + pageResults.length * ROW_H + 20; const canvas = createCanvas(W, totalH); const ctx = canvas.getContext("2d"); ctx.fillStyle = "#0f0f0f"; ctx.fillRect(0, 0, W, totalH); drawYouTubeLogo(ctx, PADDING, 22); ctx.fillStyle = "#ffffff"; ctx.font = "bold 22px sans-serif"; ctx.fillText("YouTube Results", PADDING + 50, 43); ctx.fillStyle = "#aaaaaa"; ctx.font = "13px sans-serif"; ctx.fillText(`"${truncate(query, 40)}" — Page ${page}/${maxPage}`, PADDING + 50, 62); ctx.strokeStyle = "#333333"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(PADDING, HEADER_H - 1); ctx.lineTo(W - PADDING, HEADER_H - 1); ctx.stroke(); for (let i = 0; i < pageResults.length; i++) { const v = pageResults[i]; const y = HEADER_H + i * ROW_H; const mid = y + ROW_H / 2; if (i % 2 === 0) { ctx.fillStyle = "#181818"; ctx.fillRect(0, y, W, ROW_H); } ctx.fillStyle = "#666666"; ctx.font = "bold 18px sans-serif"; ctx.fillText(String(i + 1), PADDING, mid + 7); const thumbX = PADDING + 30, thumbY = y + (ROW_H - THUMB_H) / 2; ctx.fillStyle = "#333333"; ctx.fillRect(thumbX, thumbY, THUMB_W, THUMB_H); try { const thumb = v.thumbnail; const imgBuf = await axios.get(thumb, { responseType: "arraybuffer", timeout: 6000, headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } }); ctx.drawImage(await loadImage(Buffer.from(imgBuf.data)), thumbX, thumbY, THUMB_W, THUMB_H); } catch { ctx.fillStyle = "#888888"; ctx.font = "11px sans-serif"; ctx.fillText("No image", thumbX + 28, thumbY + 36); } ctx.strokeStyle = "#444444"; ctx.lineWidth = 1; ctx.strokeRect(thumbX, thumbY, THUMB_W, THUMB_H); const dur = getDuration(v); if (dur) { ctx.fillStyle = "rgba(0,0,0,0.75)"; ctx.fillRect(thumbX, thumbY + THUMB_H - 16, THUMB_W, 16); ctx.fillStyle = "#ffffff"; ctx.font = "10px sans-serif"; ctx.fillText(dur, thumbX + 4, thumbY + THUMB_H - 5); } const textX = thumbX + THUMB_W + 14; ctx.fillStyle = "#ffffff"; ctx.font = "bold 14px sans-serif"; ctx.fillText(truncate(v.title, 52), textX, mid - 14); ctx.fillStyle = "#FF0000"; ctx.font = "12px sans-serif"; ctx.fillText(truncate(getChannel(v), 32), textX, mid + 4); const views = getViews(v); ctx.fillStyle = "#777777"; ctx.font = "12px sans-serif"; ctx.fillText(views ? `🎀 ${views} views` : dur ? `⏱ ${dur}` : "", textX, mid + 20); if (i < pageResults.length - 1) { ctx.strokeStyle = "#2a2a2a"; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(PADDING + 30, y + ROW_H); ctx.lineTo(W - PADDING, y + ROW_H); ctx.stroke(); } } return canvas.toBuffer("image/jpeg", { quality: 0.92 }); } async function sendPage(api, event, allResults, page, query, type, apiUrl, commandName, getLang) { await fs.ensureDir(TMP_DIR); const start = (page - 1) * PER_PAGE; const end = start + PER_PAGE; const pageResults = allResults.slice(start, end); const maxPage = Math.ceil(allResults.length / PER_PAGE); const imgBuf = await generateSearchImage(pageResults, query, page, maxPage); const tmpImg = path.join(os.tmpdir(), `yt_search_${Date.now()}.jpg`); fs.writeFileSync(tmpImg, imgBuf); return new Promise((resolve) => { api.sendMessage( { body: getLang("choose", `𝐘𝐨𝐮𝐓𝐮𝐛𝐞 𝐫𝐞𝐬𝐮𝐥𝐭𝐬 𝐟𝐨𝐫 "${query}" — page ${page}/${maxPage}\n`), attachment: fs.createReadStream(tmpImg) }, event.threadID, (err, info) => { setTimeout(() => { try { fs.unlinkSync(tmpImg); } catch (_) {} }, 20000); if (err) { console.error("Send page error:", err); return resolve(); } global.GoatBot.onReply.set(info.messageID, { commandName, author: event.senderID, results: allResults, query, page, type, apiUrl, menuMessageID: info.messageID }); resolve(); }, event.messageID ); }); } module.exports = { config: { name: "yt", aliases: [], version: "3.0", author: "Arafat", countDown: 10, role: 0, description: { vi: "Tải video, audio hoặc xem thông tin video trên YouTube", en: "Download video, audio or view video information on YouTube" }, category: "media", guide: { vi: " {pn} [video|-v] [|]: dùng để tải video từ youtube." + "\n {pn} [audio|-a] [|]: dùng để tải audio từ youtube" + "\n {pn} [info|-i] [|]: dùng để xem thông tin video từ youtube" + "\n Ví dụ:" + "\n {pn} -v Mood Lo-Fi" + "\n {pn} -a Mood Lo-Fi" + "\n {pn} -i Mood Lo-Fi", en: " {pn} [video|-v] [