// ==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;
}
})();