const fs = require("fs"); const path = require("path"); const os = require("os"); const axios = require("axios"); const API_BASE = "https://yt-dl-new-vshv.onrender.com"; module.exports = { config: { name: "autolink", version: "2.0", author: "EryXenX", countDown: 5, role: 0, shortDescription: "Auto-download & send videos silently (no messages)", category: "media", }, onStart: async function () {}, onChat: async function ({ api, event }) { const threadID = event.threadID; const messageID = event.messageID; const message = event.body || ""; const linkMatches = message.match(/(https?:\/\/[^\s]+)/g); if (!linkMatches || linkMatches.length === 0) return; const uniqueLinks = [...new Set(linkMatches)]; const supportedLinks = uniqueLinks.filter(detectPlatform); if (supportedLinks.length === 0) return; api.setMessageReaction("โณ", messageID, () => {}, true); let successCount = 0; let failCount = 0; for (const url of supportedLinks) { const platform = detectPlatform(url); const filePath = path.join(os.tmpdir(), `autolink_${Date.now()}_${Math.floor(Math.random() * 1e6)}.mp4`); try { const { data } = await axios.get(`${API_BASE}/api/download`, { params: { url }, timeout: 60000 }); if (!data || !data.status) { console.error(`[autolink] API returned failure for ${platform}: ${JSON.stringify(data)}`); throw new Error("api_failure"); } const candidates = collectUrlCandidates(data.result); if (candidates.length === 0) { console.error(`[autolink] No video URL candidates for ${platform}: ${JSON.stringify(data.result)}`); throw new Error("extract_failure"); } let downloaded = false; let lastErr = null; for (const videoUrl of candidates) { try { const response = await axios.get(videoUrl, { responseType: "stream", timeout: 120000, headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", "Referer": getReferer(platform) } }); await new Promise((resolve, reject) => { const writer = fs.createWriteStream(filePath); response.data.pipe(writer); writer.on("finish", resolve); writer.on("error", reject); }); const stats = fs.statSync(filePath); if (stats.size < 50 * 1024) { fs.unlinkSync(filePath); console.error(`[autolink] Candidate too small for ${platform} (${videoUrl}): ${stats.size} bytes`); continue; } downloaded = true; break; } catch (err) { lastErr = err; console.error(`[autolink] Candidate failed for ${platform} (${videoUrl}): ${err.message}`); if (fs.existsSync(filePath)) fs.unlinkSync(filePath); } } if (!downloaded) { throw lastErr || new Error("all_candidates_failed"); } const stats = fs.statSync(filePath); const fileSizeInMB = stats.size / (1024 * 1024); if (fileSizeInMB > 25) { fs.unlinkSync(filePath); failCount++; continue; } const title = extractTitle(data.result); await api.sendMessage( { body: `๐Ÿ“ฅ แด ษชแด…แด‡แด แด…แดแดกษดสŸแดแด€แด…แด‡แด… โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” ๐ŸŽฌ แด›ษชแด›สŸแด‡: ${title || "Video File"} ๐Ÿ“ฆ sษชแดขแด‡: ${fileSizeInMB.toFixed(2)} MB โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”`, attachment: fs.createReadStream(filePath) }, threadID, () => fs.unlinkSync(filePath) ); successCount++; } catch (err) { console.error(`[autolink] Failed for ${platform} (${url}): ${err.message}`); if (fs.existsSync(filePath)) fs.unlinkSync(filePath); failCount++; } } const finalReaction = successCount > 0 && failCount === 0 ? "โœ…" : successCount > 0 ? "โš ๏ธ" : "โŒ"; api.setMessageReaction(finalReaction, messageID, () => {}, true); } }; function getReferer(platform) { if (platform === "tiktok") return "https://www.tiktok.com/"; if (platform === "instagram") return "https://www.instagram.com/"; if (platform === "facebook") return "https://www.facebook.com/"; if (platform === "youtube") return "https://www.youtube.com/"; return ""; } function detectPlatform(url) { if (/instagram\.com/i.test(url)) return "instagram"; if (/tiktok\.com/i.test(url)) return "tiktok"; if (/facebook\.com|fb\.watch/i.test(url)) return "facebook"; if (/youtube\.com|youtu\.be/i.test(url)) return "youtube"; return null; } const EXCLUDE_KEY_RE = /thumbnail|thumb|cover|music|audio|avatar|image|photo/i; const HIGH_PRIORITY_KEY_RE = /^(hd|nowm|no_watermark|normal_video|video_hd|download|downloadurl|play|playaddr)$/i; const MID_PRIORITY_KEY_RE = /^(video|sd|url|link)$/i; function collectUrlCandidates(result) { const found = []; function visit(node, key) { if (node === null || node === undefined) return; if (typeof node === "string") { if (/^https?:\/\//.test(node) && !(key && EXCLUDE_KEY_RE.test(key))) { let rank = 2; if (key && HIGH_PRIORITY_KEY_RE.test(key)) rank = 0; else if (key && MID_PRIORITY_KEY_RE.test(key)) rank = 1; found.push({ url: node, rank }); } return; } if (Array.isArray(node)) { for (const item of node) visit(item, key); return; } if (typeof node === "object") { for (const childKey of Object.keys(node)) { if (EXCLUDE_KEY_RE.test(childKey)) continue; visit(node[childKey], childKey); } } } visit(result, null); found.sort((a, b) => a.rank - b.rank); const seen = new Set(); const unique = []; for (const item of found) { if (!seen.has(item.url)) { seen.add(item.url); unique.push(item.url); } } return unique; } function extractTitle(result) { function walk(node) { if (!node) return null; if (Array.isArray(node)) { for (const item of node) { const found = walk(item); if (found) return found; } return null; } if (typeof node === "object") { if (typeof node.title === "string") return node.title; if (typeof node.caption === "string") return node.caption; for (const key of Object.keys(node)) { const found = walk(node[key]); if (found) return found; } } return null; } return walk(result); }