NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name ZXDL Master // @namespace http://zoox18.com/ // @version 1.6.0 // @description View and download private videos and pictures from ZX18. // @author Narkuh, refactored by MiloGShep // @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== (function ($) { "use strict"; 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(function(element) { element.style.filter = "brightness(1)"; }); document.querySelectorAll(".label-private").forEach(function(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>' ); } 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 () { 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 () { $("#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 { $("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(); } $("#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"; } 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); } } function processAlbumPage() { if (document.URL.startsWith("https://www.zoox18.com/album/")) { const textElement = document.querySelector("div.text-danger"); const parser = new DOMParser(); function log(message) { textElement.innerText += `${message}\n`; } async function fetchAlbums(startID, albumPath = []) { log(`Trying album ${startID}...`); try { const response = await fetch(`https://www.zoox18.com/album/${startID}`, { mode: "same-origin" }); if (!response.ok) return null; const responseText = await response.text(); if (responseText) { const doc = parser.parseFromString(responseText, "text/html"); const albumName = doc.querySelector(".col-md-8 .panel-heading .pull-left").innerText.trim(); const imgs = doc.querySelectorAll('img[id^="album_photo"]'); if (imgs.length > 0) { albumPath.push({ id: startID, name: albumName, lastPhotoID: Number(imgs[imgs.length - 1].id.split("_")[2]) }); return albumPath; } else { albumPath.push({ id: startID, name: albumName }); } } return fetchAlbums(startID - 1, albumPath); } catch (error) { console.error(error); log("Error fetching album."); return null; } } async function fetchAlbumInfo(albumPath, photoCount = 0) { for (let i = 0; i < albumPath.length; i++) { const album = albumPath[i]; log(`Trying to get info for ${album.id}...`); try { const response = await fetch(`https://www.zoox18.com/search/photos?search_query=${encodeURIComponent(album.name)}`, { mode: "same-origin" }); if (!response.ok) continue; const responseText = await response.text(); const doc = parser.parseFromString(responseText, "text/html"); const imgs = doc.querySelectorAll('img[src^="/media/albums"]'); imgs.forEach(img => { const id = Number(img.src.split("/")[5].split(".")[0]); if (id === album.id) { photoCount += Number(img.closest(".video-views").innerText.trim().split(" ")[0]); } }); } catch (error) { console.error(error); log(`Error fetching album info for ${album.id}.`); } } return photoCount; } async function findPrivatePhoto(closestPhotoID, privateAlbumName) { log(`Trying photo ${closestPhotoID}...`); try { const response = await fetch(`https://www.zoox18.com/photo/${closestPhotoID}`, { mode: "same-origin" }); if (!response.ok) return null; const responseText = await response.text(); const doc = parser.parseFromString(responseText, "text/html"); const albumTitle = doc.querySelector(".col-md-8 .panel-heading .pull-left").innerText.trim().substring(13); if (albumTitle === privateAlbumName) return closestPhotoID; return findPrivatePhoto(closestPhotoID + 1, privateAlbumName); } catch (error) { console.error(error); log(`Error finding private photo.`); return null; } } function displayPhotos(startID, photoCount) { const panelBody = textElement.parentNode; textElement.remove(); let html = '<div class="row">'; for (let i = 0; i < photoCount; i++) { const id = startID + i; html += ` <div class="col-sm-4 m-t-15"> <a href="/media/photos/${id}.jpg"> <img src="/media/photos/tmb/${id}.jpg" class="img-responsive"> </a> </div> `; } html += "</div>"; panelBody.insertAdjacentHTML("afterbegin", html); } async function init() { if (textElement && confirm("Get private images from this album? It can take a minute.")) { textElement.innerText = ""; const albumID = Number(document.URL.split("/")[4]); const albumName = document.querySelector(".col-md-8 .panel-heading .pull-left").innerText.trim(); try { const albumPath = await fetchAlbums(albumID - 1); let closestPhotoID = albumPath[albumPath.length - 1]?.lastPhotoID + 1 || albumID; albumPath.pop(); const photoCount = await fetchAlbumInfo(albumPath); closestPhotoID += photoCount; const privatePhotoID = await findPrivatePhoto(closestPhotoID, albumName); log("Got private ID, displaying photos..."); const finalPhotoCount = await fetchAlbumInfo([{ id: albumID, name: albumName }]) || 21; displayPhotos(privatePhotoID, finalPhotoCount); } catch (error) { console.error(error); log("Error during initialization."); } } } init(); } document.querySelectorAll(".img-private").forEach(img => img.classList.remove("img-private")); } processAlbumPage() makeNavigationBar(); removeAnnoyance(); $("a[href='#zxdl-modal']").click(function () { checkMenuLinksStatus(); }); window.onload = main(); })(jQuery);