您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enables playback of unavailable Vbox7 (vbox7.com) videos if they're archived on the Wayback Machine.
// ==UserScript== // @name Restore Unavailable Vbox7 Videos // @namespace https://github.com/Vankata453 // @version 2025.08.18 // @description Enables playback of unavailable Vbox7 (vbox7.com) videos if they're archived on the Wayback Machine. // @license MIT // @author provigz (Vankata453) // @match https://www.vbox7.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=vbox7.com // @grant GM_xmlhttpRequest // @connect web.archive.org // ==/UserScript== /* jshint esversion: 11 */ (function() { 'use strict'; /* REDIRECT TO PLAY PAGE FOR AGE-RESTRICTED VIDEOS */ if (location.pathname.startsWith("/login") && document.referrer.includes("vbox7.com/play:")) { const targetVideoID = document.referrer.split("/play:")[1]; // Use a video taken down for copyright issues as a placeholder, since the page only contains the player for such videos. location.replace(`https://www.vbox7.com/play:a8d6999f0e?ageGatedVideoOverride=${targetVideoID}`); return; } /* UTILITIES */ function replaceLastPartOfURL(url, newEnding) { const urlObj = new URL(url); const parts = urlObj.pathname.split("/"); parts[parts.length - 1] = newEnding; urlObj.pathname = parts.join("/"); return urlObj.toString(); } async function getEarliestWaybackSnapshotURL(targetUrl) { return await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://web.archive.org/cdx/search/cdx?url=${encodeURIComponent(targetUrl)}&limit=1&closest=20060101000000&output=json`, onload: (response) => { try { const data = JSON.parse(response.responseText); if (data.length > 1) { const timestamp = data[1][1]; resolve({ timestamp, url: `https://web.archive.org/web/${timestamp}id_/${targetUrl}` }); } else { resolve(null); } } catch (err) { reject(err); } }, onerror: (err) => reject(err) }); }); } function convertWaybackTimestampToDate(timestamp) { const year = parseInt(timestamp.slice(0, 4), 10); const month = parseInt(timestamp.slice(4, 6), 10) - 1; const day = parseInt(timestamp.slice(6, 8), 10); const hour = parseInt(timestamp.slice(8, 10), 10); const minute = parseInt(timestamp.slice(10, 12), 10); const second = parseInt(timestamp.slice(12, 14), 10); return new Date(Date.UTC(year, month, day, hour, minute, second)); } function dateToVboxString(date) { const dd = String(date.getUTCDate()).padStart(2, '0'); const mm = String(date.getUTCMonth() + 1).padStart(2, '0'); const yyyy = date.getUTCFullYear(); return `${dd}.${mm}.${yyyy}`; } async function getBestMPDFormats(videoSrc) { return await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://web.archive.org/20060101000000/${videoSrc}`, onload: async function(response) { try { const mpdTree = new DOMParser().parseFromString(response.responseText, "application/xml"); const mpd = mpdTree.getElementsByTagName("MPD")[0]; if (!mpd) throw new Error('No element with name "MPD" in MPD tree!'); const period = mpd.getElementsByTagName("Period")[0]; if (!period) throw new Error('No element with name "Period" in MPD tree!'); let bestVideoRep, bestAudioRep; const adaptationSets = period.getElementsByTagName("AdaptationSet"); for (let adaptation of adaptationSets) { const representations = adaptation.getElementsByTagName("Representation"); for (const rep of representations) { const mimeType = rep.getAttribute("mimeType"); const bandwidth = parseInt(rep.getAttribute("bandwidth") || "0", 10); const type = mimeType?.startsWith("video") ? "video" : (mimeType?.startsWith("audio") ? "audio" : null); if (!type) continue; const repInfo = { bandwidth, file: rep.querySelector("BaseURL")?.textContent || null }; if (!repInfo.file) continue; if (type === "video" && (!bestVideoRep || bandwidth > bestVideoRep.bandwidth)) { const repSnapshot = await getEarliestWaybackSnapshotURL(replaceLastPartOfURL(videoSrc, repInfo.file)); if (repSnapshot) { repInfo.url = repSnapshot.url; bestVideoRep = repInfo; } } else if (type === "audio" && (!bestAudioRep || bandwidth > bestAudioRep.bandwidth)) { const repSnapshot = await getEarliestWaybackSnapshotURL(replaceLastPartOfURL(videoSrc, repInfo.file)); if (repSnapshot) { repInfo.url = repSnapshot.url; bestAudioRep = repInfo; } } } } if (!bestVideoRep && !bestAudioRep) { throw new Error("Not archived: No valid video or audio representations found in MPD!"); } resolve({ videoURL: bestVideoRep?.url || null, audioURL: bestAudioRep?.url || null }); } catch (error) { reject(error); } }, onerror: error => reject(error) }); }); } /* VIDEO FETCHING/STREAMING */ // Manual code for video fetching/streaming is required, since we need to use // GM_xmlhttpRequest to fetch data from the Wayback Machine, due to CORS restrictions. // Fetching is used for legacy video+audio videos, since they do not support streaming // and their file size is usually tiny due to low resolutions (max. 720p, most often 480p). async function getVideoFetchBlob(videoURL) { return await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: videoURL, responseType: "blob", onload: (response) => resolve(URL.createObjectURL(response.response)), onerror: (err) => reject(err) }); }); } function getVideoStreamBlob(videoURL) { const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'; // Codec for most .mp4 videos on Wayback Machine if (!MediaSource.isTypeSupported(mimeCodec)) { throw new Error("UNSUPPORTED MIME TYPE OR CODEC:", mimeCodec); } const chunkSize = 6 * 1024 * 1024; // Stream video in 6MB chunks const mediaSource = new MediaSource(); mediaSource.addEventListener("sourceopen", () => { const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); let start = 0; let isFetching = false; let isEnded = false; function fetchChunk() { if (isFetching || isEnded) return; isFetching = true; GM_xmlhttpRequest({ method: "GET", url: videoURL, headers: { Range: `bytes=${start}-${start + chunkSize - 1}` }, responseType: "arraybuffer", onload: (res) => { const contentRange = res.responseHeaders.match(/Content-Range: bytes (\d+)-(\d+)\/(\d+)/i); const totalSize = contentRange ? parseInt(contentRange[3]) : null; const chunk = new Uint8Array(res.response); sourceBuffer.appendBuffer(chunk); sourceBuffer.addEventListener("updateend", function handler() { sourceBuffer.removeEventListener("updateend", handler); start += chunk.length; isFetching = false; if (totalSize && start >= totalSize) { isEnded = true; mediaSource.endOfStream(); } else { fetchChunk(); } }); }, onerror: (err) => { console.err("VIDEO CHUNK FETCH FAILED:", err); isEnded = true; mediaSource.endOfStream(); } }); } fetchChunk(); }); return URL.createObjectURL(mediaSource); } /* AUDIO PLAYER */ function getAudioPlayer() { return document.querySelector("audio#customhtml5audio"); } function setAudioPlayerSource(audioURL, visible = false) { getAudioPlayer()?.remove(); if (!audioURL) return; let audioPlayer = document.createElement("audio"); audioPlayer.id = "customhtml5audio"; audioPlayer.style.display = visible ? "block" : "none"; if (visible) { audioPlayer.style.position = "absolute"; audioPlayer.style.left = "38%"; audioPlayer.style.top = "55.5%"; audioPlayer.style.zIndex = 101; audioPlayer.setAttribute("controls", ""); } document.querySelector("div.vbox-player").appendChild(audioPlayer); let audioSource = document.createElement("source"); audioSource.setAttribute("type", "audio/mp4"); audioSource.setAttribute("src", audioURL); audioPlayer.appendChild(audioSource); } /* VIDEO PLAYER <-> AUDIO PLAYER */ function linkVideoAudioPlayer() { let videoPlayer = getVideoPlayer(); if (videoPlayer._audioHandlersActive) return; if (!videoPlayer._audioHandlers) { videoPlayer._audioHandlers = { playing: () => { let audioPlayer = getAudioPlayer(); if (audioPlayer.readyState >= 1) { audioPlayer.currentTime = videoPlayer.currentTime; if (!videoPlayer.paused) { audioPlayer.play(); } } else { audioPlayer.addEventListener('canplay', () => { audioPlayer.currentTime = videoPlayer.currentTime; if (!videoPlayer.paused) { audioPlayer.play(); } }, { once: true }); } }, pause: () => { getAudioPlayer()?.pause(); }, waiting: () => { getAudioPlayer()?.pause(); }, ratechange: () => { let audioPlayer = getAudioPlayer(); if (audioPlayer) { audioPlayer.playbackRate = videoPlayer.playbackRate; } }, volumechange: () => { let audioPlayer = getAudioPlayer(); if (audioPlayer) { audioPlayer.volume = videoPlayer.volume; audioPlayer.muted = videoPlayer.muted; } }, seeked: () => { let audioPlayer = getAudioPlayer(); if (audioPlayer) { audioPlayer.currentTime = videoPlayer.currentTime; } } }; } // Sync video player with an audio player, if one exists videoPlayer.addEventListener('playing', videoPlayer._audioHandlers.playing, false); videoPlayer.addEventListener('pause', videoPlayer._audioHandlers.pause, false); videoPlayer.addEventListener('waiting', videoPlayer._audioHandlers.waiting, false); videoPlayer.addEventListener('ratechange', videoPlayer._audioHandlers.ratechange, false); videoPlayer.addEventListener('volumechange', videoPlayer._audioHandlers.volumechange, false); videoPlayer.addEventListener('seeked', videoPlayer._audioHandlers.seeked, false); videoPlayer._audioHandlersActive = true; } function unlinkVideoAudioPlayer() { let videoPlayer = getVideoPlayer(); if (!videoPlayer._audioHandlersActive) return; // Desync video player from audio player videoPlayer.removeEventListener('playing', videoPlayer._audioHandlers.playing); videoPlayer.removeEventListener('pause', videoPlayer._audioHandlers.pause); videoPlayer.removeEventListener('waiting', videoPlayer._audioHandlers.waiting); videoPlayer.removeEventListener('ratechange', videoPlayer._audioHandlers.ratechange); videoPlayer.removeEventListener('volumechange', videoPlayer._audioHandlers.volumechange); videoPlayer.removeEventListener('seeked', videoPlayer._audioHandlers.seeked); videoPlayer._audioHandlersActive = false; } /* VIDEO PLAYER */ function getVideoPlayer() { return document.querySelector("video#html5player"); } function setPlayerLoading(visible) { let loadingMediaBox = document.querySelector("div.loading-media"); loadingMediaBox.style.visibility = visible ? "visible" : "hidden"; if (visible) { loadingMediaBox.style.width = "160px"; loadingMediaBox.style.height = "94px"; loadingMediaBox.style.marginLeft = "-80px"; loadingMediaBox.style.marginTop = "-46px"; let loadingText = loadingMediaBox.querySelector("p"); if (!loadingText) { loadingText = document.createElement("p"); loadingText.textContent = "Loading archive..."; loadingMediaBox.appendChild(loadingText); } loadingText.style.marginLeft = "3.8px"; loadingText.style.marginTop = "65%"; loadingText.style.color = "#FFF"; loadingText.style.background = "rgba(0,0,0,.7)"; } } async function setPlayerVideo(videoID, videoSrc) { let videoPlayer = getVideoPlayer(); videoPlayer.setAttribute("poster", `https://i49.vbox7.com/i/${videoID.substr(0, 3)}/${videoID}6.jpg`); videoPlayer.style.objectFit = "cover"; if (videoSrc.endsWith(".mp4")) { // MP4 (legacy video+audio): Fetch the full video directly const videoSnapshot = await getEarliestWaybackSnapshotURL(videoSrc); if (videoSnapshot) { videoPlayer.setAttribute("src", await getVideoFetchBlob(videoSnapshot.url)); videoPlayer.setAttribute("data-src", videoSnapshot.url); addPlayerOpenRawButtons(videoSnapshot.url); } else { setPlayerError("The video file has not been archived!"); console.warn('THE VIDEO FILE HAS NOT BEEN ARCHIVED!'); } getAudioPlayer()?.remove(); } else if (videoSrc.endsWith(".mpd")) { // MPD: Stream the best available video/audio formats try { const bestFormats = await getBestMPDFormats(videoSrc); if (bestFormats.videoURL) { videoPlayer.setAttribute("src", getVideoStreamBlob(bestFormats.videoURL)); videoPlayer.setAttribute("data-src", `https://web.archive.org/20060101000000/${videoSrc}`); linkVideoAudioPlayer(); addPlayerOpenRawButtons(bestFormats.videoURL, bestFormats.audioURL); } else { setPlayerError(`No available video formats! Audio only.`); console.warn('FAILED TO GET VIDEO FORMATS FROM MPD!'); unlinkVideoAudioPlayer(); } setAudioPlayerSource(bestFormats.audioURL, !bestFormats.videoURL); } catch (error) { setPlayerError(`Failed to get/set archived video/audio formats: ${error}`); console.warn('FAILED TO GET/SET VIDEO/AUDIO FORMATS FROM MPD:', error); return; } } else { setPlayerError(`Unknown video file type!`); console.warn(`UNKNOWN VIDEO FILE TYPE! FULL "videoSrc" URL: ${videoSrc}`); return; } setPlayerLoading(false); document.querySelector("div.vbox-msgcontainer").style.visibility = "hidden"; const styleNoStatic = document.createElement('style'); styleNoStatic.textContent = ` .static-on { display: none !important; }`; document.head.appendChild(styleNoStatic); videoPlayer.play(); // Autoplay } function setPlayerError(error) { setPlayerLoading(false); let errorMsgEl = document.createElement("div"); errorMsgEl.classList.add("vbox-msgcontainer"); errorMsgEl.id = "#playerErrorMsg"; errorMsgEl.style.display = "block"; errorMsgEl.style.marginTop = "20%"; errorMsgEl.style.fontSize = "150%"; errorMsgEl.textContent = error; document.querySelector("div.vbox-msgcontainer").insertAdjacentElement("afterend", errorMsgEl); } function addPlayerOpenRawButtons(videoURL, audioURL = null) { const controlsDiv = document.querySelector("div.vbox-controls"); controlsDiv.querySelector("button.vbox-biggen-button").style.marginLeft = "10px"; if (audioURL) { const audioRawButton = document.createElement("button"); audioRawButton.classList.add("vbox-biggen-button"); audioRawButton.setAttribute("title", "Open raw audio"); const audioRawIcon = document.createElement("img"); audioRawIcon.setAttribute("src", "https://static.thenounproject.com/png/headset-icon-6996835-512.png"); audioRawIcon.setAttribute("width", "24px"); audioRawIcon.setAttribute("height", "24px"); audioRawIcon.style.filter = "invert(1)"; audioRawButton.appendChild(audioRawIcon); controlsDiv.appendChild(audioRawButton); audioRawButton.addEventListener("click", function() { window.open(audioURL); }); } const videoRawButton = document.createElement("button"); videoRawButton.classList.add("vbox-biggen-button"); videoRawButton.setAttribute("title", `Open raw video${audioURL ? " (no audio)" : ""}`); const videoRawIcon = document.createElement("img"); videoRawIcon.setAttribute("src", "https://static.thenounproject.com/png/video-icon-1863915-512.png"); videoRawIcon.setAttribute("width", "24px"); videoRawIcon.setAttribute("height", "24px"); videoRawIcon.style.filter = "invert(1)"; videoRawButton.appendChild(videoRawIcon); controlsDiv.appendChild(videoRawButton); videoRawButton.addEventListener("click", function() { window.open(videoURL); }); } /* MAIN */ async function restoreVideo(videoID, infoFillNeeded = false) { setPlayerLoading(true); let videoDataSnapshot; try { videoDataSnapshot = await getEarliestWaybackSnapshotURL(`https://www.vbox7.com/aj/player/item/options?vid=${videoID}`); } catch (error) { setPlayerError("Not archived: HTTP error fetching archived video info!"); console.error('HTTP ERROR FETCHING VIDEO OPTIONS:', error); return; } if (!videoDataSnapshot) { setPlayerError("Not archived: No available archives of this video!"); console.warn('NO AVAILABLE ARCHIVES OF THIS VIDEO!'); return; } GM_xmlhttpRequest({ method: 'GET', url: videoDataSnapshot.url, responseType: 'json', onload: function(response) { const data = response.response; if (!data || !data.success) { setPlayerError("Not archived: Invalid archive data!"); console.warn('INVALID ARCHIVE DATA: "SUCCESS" IS FALSE!'); return; } const opt = data.options; if (!opt || !opt.src || opt.src == "blank") { setPlayerError("Not archived: Archive is empty!"); console.warn('ARCHIVE IS EMPTY!'); return; } setPlayerVideo(videoID, opt.src); if (infoFillNeeded) { document.title = `${opt.title} - Vbox7`; const channelOpt = document.createElement("div"); channelOpt.className = "channel-opt"; const channelLink = document.createElement("a"); channelLink.className = "channel-name"; channelLink.href = `/user:${opt.uploader}`; const nameSpan = document.createElement("span"); nameSpan.textContent = opt.uploader; channelLink.appendChild(nameSpan); channelOpt.appendChild(channelLink); const title = document.createElement("h1"); title.textContent = opt.title; const defCont = document.createElement("div"); defCont.id = "def-cont"; defCont.className = "det-info collapsed-toggle mb-tgl-only"; const videoStat = document.createElement("div"); videoStat.className = "video-stat"; let uploadDate = convertWaybackTimestampToDate(videoDataSnapshot.timestamp); uploadDate.setUTCSeconds(uploadDate.getUTCSeconds() - opt.ago); const dateSpan = document.createElement("span"); dateSpan.textContent = dateToVboxString(uploadDate); videoStat.appendChild(dateSpan); const videoOptions = document.createElement("div"); videoOptions.className = "drop-wrap video-options"; defCont.append(videoStat, videoOptions); document.querySelector("div.video-info").append(channelOpt, title, defCont); } }, onerror: function(error) { setPlayerError("Not archived: HTTP error fetching archived video info!"); console.error('HTTP ERROR FETCHING VIDEO OPTIONS:', error); } }); } /* INACCESSIBLE VIDEO DETECTION */ const ageGatedVideoOverrideID = (new URLSearchParams(location.search)).get("ageGatedVideoOverride"); if (ageGatedVideoOverrideID) { const contentWrapDiv = document.querySelector("section.play-grid div.item-content-wrap"); // Remove traces of placeholder video info document.title = "Vbox7"; document.querySelector("div.vbox-msgcontainer").style.visibility = "hidden"; contentWrapDiv.querySelector("div.player.area").innerHTML = ""; // Add video info <div> let videoInfoDiv = document.createElement("div"); videoInfoDiv.classList.add("video-info"); contentWrapDiv.appendChild(videoInfoDiv); // Include a warning under age-restricted videos let ageGateWarning = document.createElement("h3"); ageGateWarning.textContent = "This video may be inappropriate for some users!"; ageGateWarning.style.textAlign = "center"; ageGateWarning.style.color = "red"; videoInfoDiv.appendChild(ageGateWarning); restoreVideo(ageGatedVideoOverrideID, true); } else { const originalOpen = XMLHttpRequest.prototype.open; const originalSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url, async, user, password) { this._isPlayerOptions = url.startsWith("/aj/player/item/options?vid="); if (this._isPlayerOptions) { this._videoID = url.split('=')[1]; } return originalOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(body) { if (this._isPlayerOptions) { this.addEventListener('readystatechange', function() { if (this.readyState !== 4) return; if (this.status < 200 || this.status >= 300) { console.error("UNSUCCESSFUL PLAYER OPTIONS REQUEST! WILL TRY RESTORING FROM ARCHIVE!"); restoreVideo(this._videoID); return; } try { const data = JSON.parse(this.responseText); if (!data.success) { console.error("UNSUCCESSFUL PLAYER OPTIONS REQUEST RESPONSE! WILL TRY RESTORING FROM ARCHIVE!"); restoreVideo(this._videoID); return; } if (!data.options || !data.options.src || data.options.src == "blank") { console.warn("VIDEO NOT AVAILABLE! HAVE TO RESTORE FROM ARCHIVE!"); restoreVideo(this._videoID); } } catch (error) { console.error('FAILED TO PARSE PLAYER OPTIONS REQUEST JSON RESPONSE! WILL TRY RESTORING FROM ARCHIVE!', error); restoreVideo(this._videoID); } }); } return originalSend.apply(this, arguments); }; } /* HANDLE PLAY PAGE REFRESH */ // The page should refresh going from one play page to another, // as the video player doesn't properly refresh going from inaccessible to accessible video. const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(this, args); setTimeout(onRouteChange, 0); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); setTimeout(onRouteChange, 0); }; window.addEventListener('popstate', onRouteChange); // Navigation back/forward let lastPath = location.pathname; function onRouteChange() { if (location.pathname !== lastPath && lastPath.startsWith('/play:') && location.pathname.startsWith('/play:')) { location.reload(); } lastPath = location.pathname; } })();