← back to browse
goat command

autolink

Auto-download & send videos silently (no messages)

0
views
0
likes
0
installs
raw source
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);
}

View raw file