paseri3739 / ZXDL

// ==UserScript==
// @name         ZXDL
// @namespace    http://zoox18.com/
// @version      1.5.3
// @description  View and download private videos from ZX18. refactored and bug fixed.
// @author       Narkuh, refactored by PSR
// @license      MIT
// @match        http*://*.zoox18.com/*
// @connect      pastebin.com
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @require      http://code.jquery.com/jquery-3.4.1.min.js
// @require      https://cdn.plyr.io/3.5.6/plyr.js
// ==/UserScript==

/***********************************************************************/
/* This program is free software. It comes without any warranty, to     /
/* the extent permitted by applicable law. You can redistribute it      /
/* and/or modify it under the terms of the Do What The Fuck You Want    /
/* To Public License, Version 2.                                        /
/* See http://www.wtfpl.net/ for more details.                          /
/***********************************************************************/

// Avoid Global Scope and var with IIFE
(function ($) {
    "use strict";

    // key0, key1, key2 are derived from http request header of video. you can research with developer tool to watch network request.
    // These are used for sending GET request of video. Direct URL access is banned but through xmlhttp request works.
    const zxdl = {
        VIDEO_ID: window.location.pathname.split("/")[2],
        key0: "",
        key1: "",
        key2: "",
        paths: [],
        isDownloading: false,
        isVideoFound: false,
        isPrivateWindow: false,
        videoUrl: "",
        videoUploader: "Unknown",
        videoTitle: "Unknown",
        videoDescription: "None",
        linkCheckThisSession: false,
    };

    function removeAnnoyance() {
        document.querySelectorAll(".img-private").forEach((element) => (element.style.filter = "brightness(1)"));
        document.querySelectorAll(".label-private").forEach((element) => (element.style.filter = "opacity(0.5)"));
    }

    function makeNavigationBar() {
        $(".navbar").after('<div class="container" id="rip-div" style="width: 560px;"></div>');
        $(".top-menu > .pull-left").append(
            '<li id="zxdl-header"><span id="zx-ver">ZXDL 1.5.3</span><span><a data-toggle="modal" href="#zxdl-modal"><span class="caret"></span></span></a></li>'
        );
        $("body").append(
            '<div class="modal fade in" id="zxdl-modal"><div class="modal-dialog zxdl-modal"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">テ�</button> <h4 class="modal-title">ZXDL</h4> </div> <div class="modal-body">Version 1.5.3 | by Lowfo and Narkuh<br><span class="link-status"></span></div></div></div></div>'
        );
    }

    // Functions
    function formatBytes(bytes, decimalPlaces = 2) {
        if (bytes === 0) return "0 bytes";
        const dp = 0 > decimalPlaces ? 0 : decimalPlaces,
            sizeIndex = Math.floor(Math.log(bytes) / Math.log(1024));
        return parseFloat((bytes / Math.pow(1024, sizeIndex)).toFixed(dp)) + " " + ["bytes", "KB", "MB", "GB"][sizeIndex];
    }

    function calculateAndShowDownloadProgress(xmlHttpRequestProgressEvent) {
        if (xmlHttpRequestProgressEvent.lengthComputable === false) return;
        $("#dl-data").html(
            formatBytes(xmlHttpRequestProgressEvent.loaded) + " / " + formatBytes(xmlHttpRequestProgressEvent.total)
        );
        $("#dl-bar").attr(
            "aria-valuenow",
            Math.floor((xmlHttpRequestProgressEvent.loaded / xmlHttpRequestProgressEvent.total) * 100)
        );
        $("#dl-bar").css(
            "width",
            Math.floor((xmlHttpRequestProgressEvent.loaded / xmlHttpRequestProgressEvent.total) * 100) + "%"
        );
        $("#dl-bar").html(Math.floor((xmlHttpRequestProgressEvent.loaded / xmlHttpRequestProgressEvent.total) * 100) + "%");
    }

    function showDownloadSuccess(xmlhttpRequestProgressEvent) {
        if (xmlhttpRequestProgressEvent.lengthComputable === false) return;
        $("#dl-data").html("Complete!");
        $("#dl-bar").addClass("progress-bar-success");
    }

    function showDownloadError(xmlhttpRequestProgressEvent) {
        if (xmlhttpRequestProgressEvent.lengthComputable === false) return;
        $("#dl-data").html("Oops, there was an error. Refresh page to try again");
        $("#dl-bar").addClass("progress-bar-danger");
    }

    function sendGetRequestWithCallback(url, callback) {
        $.ajax({
            url: url,
            dataType: "jsonp",
            type: "GET",
            complete: function (xhr) {
                if (typeof callback === "function") {
                    callback.apply(this, [xhr.status]);
                }
            },
        });
    }

    function checkMenuLinksStatus() {
        const OK = 200;
        if (!zxdl.linkCheckThisSession) {
            zxdl.linkCheckThisSession = true;
            for (let i = 0; i < zxdl.paths.length; i++) {
                let baseUrl = zxdl.paths[i].substring(0, zxdl.paths[i].lastIndexOf("/"));
                $("#zxdl-modal .link-status").append('<div class="link-' + i + '">Link ' + (i + 1) + ":</div>");
                sendGetRequestWithCallback(baseUrl, function (status) {
                    if (status === OK) {
                        $("#zxdl-modal .link-" + i).append(' <i class="fa fa-check"></i>');
                    } else {
                        $("#zxdl-modal .link-" + i).append(' <i class="fa fa-times"></i>');
                    }
                });
            }
        }
    }

    function scanAndLoadVideoFromUrl(url) {
        if (zxdl.isVideoFound == false) {
            let videoElement = document.createElement("VIDEO");
            videoElement.addEventListener("loadeddata", function () {
                // If video found
                console.log("ZXDL: Video found! " + url);
                zxdl.isVideoFound = true;
                zxdl.videoUrl = url;

                if (zxdl.isPrivateWindow) {
                    $("#rip-div").html(
                        "<h1>" +
                            zxdl.videoTitle +
                            '</h1><p>Uploaded by <a href="https://zoox18.com/user/' +
                            zxdl.videoUploader +
                            '">' +
                            zxdl.videoUploader +
                            '</a></p><p style="opacity:0.5">"' +
                            zxdl.videoDescription +
                            '"</p><p style="font-size:12px">(' +
                            zxdl.videoUrl +
                            ')</p><link rel="stylesheet" href="https://cdn.plyr.io/3.5.6/plyr.css" /><video style="width: 100%; height: 100%;" poster="https://www.zoox18.com/media/videos/tmb1/' +
                            zxdl.VIDEO_ID +
                            '/default.jpg" id="rippedvid" playsinline controls><source src="' +
                            zxdl.videoUrl +
                            '" type="video/mp4" /></video><div><hr><button id="zxdl_favorite" class="btn btn-primary"><i class="glyphicon glyphicon-heart"></i> Favorite</button> <button id="zxdl_download" class="btn btn-primary"><i class="glyphicon glyphicon-download"></i> Download</button><p id="status"></p><div id="dl-progress" class="well" style="display: none"></div></div>'
                    );

                    $("#zxdl_favorite").click(function () {
                        // Favorite button for private videos
                        $("#status").html("Please wait...");
                        const http = new XMLHttpRequest();
                        const favoriteVideoBaseUrl = "https://www.zoox18.com/ajax/favorite_video";
                        const form = "video_id=" + zxdl.VIDEO_ID;
                        http.open("POST", favoriteVideoBaseUrl, true);
                        http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                        http.onreadystatechange = function () {
                            if (http.readyState == 4 && http.status == 200) {
                                const response = http.responseText;
                                if (response.includes("alert-danger")) {
                                    $("#status").html(
                                        "Couldn't favorite video. Are you logged in? Is this video already in your favorites?"
                                    );
                                } else if (response.includes("alert-success")) {
                                    $("#status").html('<span style="color:#77b300">Added to favorites!</span>');
                                } else {
                                    $("#status").html("The site returned unknown data.");
                                }
                            }
                        };
                        http.send(form);
                    });
                } else {
                    // Replace download button on public videos
                    $("div#share_video").append(
                        '<button id="zxdl_download" class="btn btn-primary m-l-5"><i class="glyphicon glyphicon-download"></i></button><p id="status"></p>'
                    );
                    $("#response_message").after('<div id="dl-progress" class="well" style="display: none"></div>');
                    $("button.btn.btn-default.dropdown-toggle").remove();
                }

                // Initialize Download
                $("#zxdl_download").click(function () {
                    if (zxdl.isDownloading === false) {
                        zxdl.isDownloading = true;
                        $("#dl-progress").css("display", "block");
                        $("#dl-progress").html(
                            '<h4>Progress</h4><span id="dl-data">Loading...</span> <div class="progress"><div id="dl-bar" class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%"></div></div>'
                        );
                        GM_download({
                            url: url,
                            name: zxdl.VIDEO_ID + ".mp4",
                            onprogress: calculateAndShowDownloadProgress,
                            onload: showDownloadSuccess,
                            onerror: showDownloadError,
                        });
                    } else {
                        alert("You've already initiated a download. Refresh the page to try again.");
                    }
                });
            });
            videoElement.src = url;
        }
    }

    async function makeAllUrlListwithKey(getLinksToo) {
        return new Promise((resolve) => {
            try {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: "https://pastebin.com/raw/pnEwmQ2f",
                    headers: {
                        "Cache-Control": "max-age=1200, must-revalidate",
                    },
                    onload: function (response) {
                        let json = JSON.parse(response.responseText);
                        if (getLinksToo) {
                            zxdl.key0 = json.key0;
                            zxdl.key1 = json.key1;
                            zxdl.key2 = json.key2;
                            zxdl.paths = [
                                atob(json.host0) + zxdl.key0 + "/media/videos/h264/" + zxdl.VIDEO_ID + "_SD.mp4",
                                atob(json.host1) + zxdl.key1 + "/media/videos/h264/" + zxdl.VIDEO_ID + "_SD.mp4",
                                atob(json.host2) + zxdl.key2 + "/media/videos/iphone/" + zxdl.VIDEO_ID + ".mp4",
                                atob(json.host2) + zxdl.key2 + "/media/videos/h264/" + zxdl.VIDEO_ID + "_SD.mp4",
                            ];
                            resolve();
                        }
                        if (json.revision) {
                            $("#zx-ver").append("." + json.revision);
                        }
                        if (json.announcement) {
                            $("#zxdl-header").append(
                                '<div style="display:inline; width: 100%;font-size:11px;margin-left:10px;background: #252e51;padding: 2px 5px;color: white;"><i class="fa fa-info-circle"></i> ' +
                                    json.announcement +
                                    "</div>"
                            );
                        }
                    },
                });
            } catch (err) {
                alert(
                    "ZXDL: There was an error retrieving links for this session. Try refreshing the page, otherwise if you keep receiving this message, please contact us"
                );
            }
        });
    }

    function isPublicWindowActive() {
        return $("#wrapper .container .row .col-md-8 .vcontainer ").length > 0;
    }

    function isPrivateWindowActive() {
        return $("#wrapper .container .row .col-xs-12 .text-danger").length > 0;
    }

    function isVideoUrl() {
        return window.location.pathname.split("/")[1] == "video";
    }

    // On load
    async function main() {
        if (isPrivateWindowActive()) {
            zxdl.isPrivateWindow = true;
            zxdl.videoUploader = $(".text-danger a").text(); // You must be friends with...
            zxdl.videoTitle = $("meta[property='og:title']").attr("content"); // Metatag still have title
            zxdl.videoDescription = $("meta[property='og:description']").attr("content"); // Metatag still have discription
            if (isVideoUrl()) {
                $(".well.well-sm").remove(); // Remove notice
                $(".well.ad-body").remove(); // Remove sponsor block for non-ad-blockers
                $("#rip-div").html("<h1>Scanning for video " + zxdl.VIDEO_ID + "...</h1><p>This can take up to a minute.</p>");
                await makeAllUrlListwithKey(true);
                zxdl.paths.forEach(scanAndLoadVideoFromUrl);
            }
        } else if (isPublicWindowActive()) {
            await makeAllUrlListwithKey(true);
            zxdl.paths.forEach(scanAndLoadVideoFromUrl);
        } else {
            await makeAllUrlListwithKey(false);
        }
    }

    // run
    makeNavigationBar();
    removeAnnoyance();
    $("a[href='#zxdl-modal']").click(function () {
        checkMenuLinksStatus();
    });
    window.onload = main();
})(jQuery);