Video Auto Loader & Downloader (XHR Intercept)

Wait for the .ts link to be generated via XHR, retrieve the segments, and merge them into an .mp4 file

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Video Auto Loader & Downloader (XHR Intercept)
// @namespace    https://beerscloud.com/
// @version      1.0
// @description  Wait for the .ts link to be generated via XHR, retrieve the segments, and merge them into an .mp4 file
// @author       ChatGPT
// @match        *://beerscloud.com/iframe/*
// @match        *://sharecloudy.com/iframe/*
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @run-at       document-end
// ==/UserScript==

(function () {
    // URL du fichier version.json sur GitHub
    const versionUrl = "https://raw.githubusercontent.com/ProbablyXS/auto-video-downloader-ts-merge/main/version.json";

    // Fonction pour vérifier si une mise à jour est disponible
    async function checkForUpdates() {
        try {
            let response = await fetch(versionUrl);
            if (response.ok) {
                let data = await response.json();
                const remoteVersion = data.version;
                const updateUrl = data.url;

                // Vérifie si la version distante est plus récente
                if (remoteVersion !== localVersion) {
                    notifyUserOfUpdate(remoteVersion, updateUrl);
                }
            }
        } catch (error) {
            console.error('Erreur lors de la vérification des mises à jour:', error);
        }
    }

    // Fonction pour afficher une notification à l'utilisateur
    function notifyUserOfUpdate(remoteVersion, updateUrl) {
        GM_notification({
            title: "Nouvelle mise à jour disponible!",
            text: `Une nouvelle version (${remoteVersion}) est disponible pour ce script. Cliquez ici pour voir les détails.`,
            highlight: true,
            onclick: function () {
                window.open(updateUrl, '_blank');
            }
        });
    }

    // Vérifie les mises à jour au démarrage du script
    checkForUpdates();

    'use strict';

    let tsLink = null;  // To store the .ts file link
    let tsLinks = new Set();  // To store the .ts video segments
    let isTsLinkAvailable = false; // Flag to check if the .ts link is detected
    let foundSegments = 0;  // Counter for the found segments
    let downloadedSegments = 0;  // Counter for the downloaded segments
    let startTime = 0;  // Start time for the download
    let statusBox = null;  // Status box for tracking information
    let timeRemainingText = null;  // Display for the remaining time

    // Add a button to manually start the process
    function createDownloadButton() {
        let button = document.createElement("button");
        button.textContent = "Start Download";
        button.style.position = "fixed";
        button.style.top = "20px";
        button.style.left = "20px";
        button.style.zIndex = "9999";
        button.style.padding = "10px 20px";
        button.style.backgroundColor = "#28a745";
        button.style.color = "#fff";
        button.style.border = "none";
        button.style.borderRadius = "5px";
        button.style.cursor = "pointer";
        button.addEventListener("click", function () {
            showStatusBox(); // Show the status box
            checkTsLinkAvailability(); // Start the link checking and downloading
            button.remove(); // Remove the button after it's clicked
        });
        document.body.appendChild(button);
    }

    // Show the status box
    function showStatusBox() {
        if (!statusBox) {
            statusBox = document.createElement("div");
            statusBox.style.position = "fixed";
            statusBox.style.top = "60px";
            statusBox.style.left = "20px";
            statusBox.style.zIndex = "9999";
            statusBox.style.padding = "10px";
            statusBox.style.backgroundColor = "black";
            statusBox.style.color = "white";
            statusBox.style.fontSize = "20px";  // Enlarged font size
            statusBox.style.borderRadius = "5px";
            document.body.appendChild(statusBox);
        }
        updateStatusBox(); // Update the status
    }

    // Display the remaining time in minutes
    function updateTimeRemaining() {
        if (!timeRemainingText) {
            timeRemainingText = document.createElement("div");
            timeRemainingText.style.position = "fixed";
            timeRemainingText.style.top = "110px";
            timeRemainingText.style.left = "20px";
            timeRemainingText.style.zIndex = "9999";
            timeRemainingText.style.padding = "10px";
            timeRemainingText.style.backgroundColor = "black";
            timeRemainingText.style.color = "white";
            timeRemainingText.style.fontSize = "16px";  // Font size
            timeRemainingText.style.borderRadius = "5px";
            document.body.appendChild(timeRemainingText);
        }

        const elapsedTime = (Date.now() - startTime) / 1000; // Elapsed time in seconds
        const remainingSegments = foundSegments - downloadedSegments; // Remaining segments
        const downloadSpeed = downloadedSegments / elapsedTime; // Download speed in segments per second

        if (downloadSpeed > 0) {
            const estimatedTimeRemaining = (remainingSegments / downloadSpeed / 60).toFixed(2); // Estimate of remaining time in minutes
            timeRemainingText.textContent = `Estimated remaining time: ${estimatedTimeRemaining} minutes`;
        } else {
            timeRemainingText.textContent = `Calculating remaining time...`;
        }
    }

    // Update the status box content
    function updateStatusBox() {
        statusBox.textContent = `Segments found: ${foundSegments} | Downloaded: ${downloadedSegments}`;
        updateTimeRemaining(); // Update the remaining time
    }

    // Intercept all XHR requests
    (function interceptXHR() {
        const originalSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function (...args) {
            this.addEventListener('load', function () {
                if (this.responseURL && this.responseURL.endsWith('.ts')) {
                    tsLink = this.responseURL; // Capture the .ts link
                    isTsLinkAvailable = true;
                }
            });
            originalSend.apply(this, args);
        };
    })();

    // Function to check the validity of a .ts segment (sends a HEAD request)
    async function checkTSUrl(url) {
        try {
            let response = await fetch(url, { method: "HEAD" });
            return response.ok;
        } catch (error) {
            return false;
        }
    }

    // Function to download and merge the .ts segments
    async function downloadAndMergeTS() {
        if (!tsLink) {
            alert("❌ No .ts link found!");
            return;
        }

        let maxConcurrentDownloads = 10;
        let activeDownloads = 0;
        let mergedBlobParts = [];  // List to store the segment data

        let tsUrlBase = tsLink.replace(/(\d+)\.ts$/, '');  // Extract the base URL of the .ts link
        let i = 1;
        let segmentUrl;
        let isValid = true;

        // Start the timer
        startTime = Date.now();

        while (isValid) {
            segmentUrl = `${tsUrlBase}${i}.ts`;  // Generate the URL with the index
            isValid = await checkTSUrl(segmentUrl);  // Check if the segment is valid

            if (isValid) {
                tsLinks.add(segmentUrl);  // Add to the list of segments to download
                foundSegments++;  // Increment the found segments counter
                updateStatusBox();  // Update the status display
                i++;
            } else {
                break;  // Stop when an invalid segment is encountered
            }
        }

        if (tsLinks.size === 0) return;

        // Function to download a segment
        const downloadSegment = (tsUrl) => {
            activeDownloads++;
            GM_xmlhttpRequest({
                method: "GET",
                url: tsUrl,
                responseType: "arraybuffer",
                onload: (response) => {
                    if (response.status === 200) {
                        mergedBlobParts.push(response.response);  // Add the segment to the Blob
                        downloadedSegments++;  // Increment the downloaded segments counter
                        updateStatusBox();  // Update the status display
                    }
                    activeDownloads--;
                    processQueue();  // Check if more downloads can start
                },
                onerror: () => {
                    activeDownloads--;
                    processQueue();  // Check if more downloads can start
                }
            });
        };

        // Function to manage the download queue
        const processQueue = () => {
            if (activeDownloads < maxConcurrentDownloads && tsLinks.size > 0) {
                const tsUrl = tsLinks.values().next().value;  // Get the next .ts link
                tsLinks.delete(tsUrl);  // Remove the segment from the list
                downloadSegment(tsUrl);  // Start downloading the segment
            }
        };

        processQueue();  // Start downloading the segments

        // Wait for all downloads to finish
        while (activeDownloads > 0) {
            await new Promise(resolve => setTimeout(resolve, 50));
        }

        // Merge the downloaded segments into a single MP4 file
        let finalBlob = new Blob(mergedBlobParts, { type: "video/mp4" });
        let finalUrl = URL.createObjectURL(finalBlob);

        let a = document.createElement("a");
        a.href = finalUrl;
        a.download = "video_merged.mp4";
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);

        alert("📂 Download complete!");
    }

    // Continuously check the availability of the .ts link
    function checkTsLinkAvailability() {
        foundSegments = 0;  // Reset the counters
        downloadedSegments = 0;
        updateStatusBox();  // Update the status display
        if (isTsLinkAvailable) {
            downloadAndMergeTS();  // Start the download if the .ts link is available
        } else {
            setTimeout(checkTsLinkAvailability, 2000); // Check every 2 seconds
        }
    }

    // Create the download button
    createDownloadButton();

})();