Před instalací, Sleazy Fork chce abyste věděli, že tento skript obsahuje "antifunkce", což jsou věci, které spíše přispívají autorovi skriptu, než vám.
Autor skriptu vysvětluje:
VPN referral links.
Add video preview.
// ==UserScript== // @name JAVBUS PLUS // @namespace Violentmonkey Scripts // @match *://*.javbus.com/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @version 1.4.4 // @author Chaewon // @description Add video preview. // @license Unlicense // @antifeature referral-link VPN referral links. // @icon https://www.google.com/s2/favicons?sz=256&domain_url=https%3A%2F%2Fwww.javbus.com%2Fen // ==/UserScript== //document.cookie='dv=1;path=/;expires=Tue, 01 Jan 2030 00:00:00 GMT;domain=javbus.com;'; (function () { "use strict"; const css = String.raw; const html = String.raw; const defaultConfig = { // darkTheme: false, playerAutoplay: true, playerVolume: 0.1, playerLoop: true, playerClickAnywhereToClose: false, previewQuality: 2, lastUpdated: 0, languages: "en", devmode: false, data: { studios: { general: ["PFES", "AVOP"], sOne: ["SSIS", "SOE", "SNIS", "SSNI", "OFJE", "SONE", "ONSD"], faleno: ["FSDSS", "FCDSS", "FTBLD", "FTKD", "FTHTD", "MGOLD"], }, prefixMap: { general: ["general"], nmixx: ["sOne"], plusOne: ["faleno"], }, prefixMapData: { plusOne: "1", }, }, url: "https://api.jsonsilo.com/public/445432dd-f613-4528-a45d-71a1efacee95", }; const userConfig = GM_getValue("config", defaultConfig); console.log("[US:DEBUG] : ", `CONFIG: `, userConfig); const stylesheet = css` .overlay-video-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.9); display: flex; justify-content: center; align-items: center; z-index: 9999; overflow: hidden; } .video-container { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; max-width: 90%; max-height: 90%; position: relative; } .overlay-video { width: 100%; height: auto; max-width: 100%; max-height: 100%; border: 1px solid #262626; background: #000; } .close-button { position: absolute; padding: 6px 14px; top: -40px; right: 0; background: none; color: white; cursor: pointer; border: 1px solid #333; } .close-button:hover { background: #333; } .settings-container { position: fixed; right: 1em; top: 2em; max-width: 500px; width: 100%; padding: 10px 20px; border: 1px solid #ddd; border-radius: 8px; background-color: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 9999; } .settings-container .settings-item:not(:last-of-type) { margin-bottom: 20px; } .settings-container label { display: block; margin-bottom: 8px; font-weight: 600; color: #333; } .settings-container input[type="text"], .settings-container input[type="range"] { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } .settings-container input[type="checkbox"] { margin-right: 10px; } .settings-container .button { display: inline-block; padding: 10px 15px; color: #fff; background-color: #007bff; border: none; border-radius: 4px; cursor: pointer; text-align: center; transition: background-color 0.3s ease; } .settings-container .button:hover { background-color: #0056b3; } .settings-container range-label { margin-bottom: 5px; color: #333; } .settings-container #quality { color: black; } .settings-container option, .settings-container input { color: black; } .settings-container #resetApiData { width: 100%; margin: 4px 0; } .toast { font-weight: bolder; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); padding: 10px 20px; background-color: #913030; color: #fff; border-radius: 4px; opacity: 0; visibility: hidden; transition: opacity 0.5s, visibility 0.5s; z-index: 1000; border: 2px solid #fff; } .toast.show { opacity: 1; visibility: visible; } .preview-btn { padding: 4px 10px; font-size: 0.8em; width: 100%; display: block; border: 1px solid #d59c9c; border-radius: 4px; text-align: center; background: #e1d2d2; color: #5b1818; margin: 0 0 10px 0; } .preview-btn:hover { background: #d59c9c; color: #5b1818; } #star-div { display: unset !important; } .notice { border: 1px solid #333; border-radius: 4px; padding: 1rem; background-color: #fff9c8; margin-bottom: 1rem; } `; GM_addStyle(stylesheet); function createCookie(name, value, days, domain) { let expires; if (days) { const date = new Date(); date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); expires = "; expires=" + date.toGMTString(); } else { expires = ""; } document.cookie = name + "=" + value + expires + "; path=/" + "; domain=" + domain + ";"; } function getCookie(name) { const nameKey = encodeURIComponent(name) + "="; const cookies = document.cookie.split(";"); for (let i = 0; i < cookies.length; i++) { let c = cookies[i].trim(); if (c.indexOf(nameKey) === 0) { return decodeURIComponent(c.substring(nameKey.length)); } } return null; } async function fetchDataFromAPI() { try { const response = await fetch(userConfig.url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.log("[US:DEBUG] : ", " Error fetching data:", error); } } async function main() { // COOKIE + SEARCH BYPASS const cookieDomain = location.hostname; const referer = new URLSearchParams(window.location.search).get("referer"); const cookies = { dv: getCookie("dv"), age: getCookie("age"), }; if (!cookies.dv || !cookies.age) { if (!cookies.dv) { createCookie("dv", "1", 365, cookieDomain); } if (!cookies.age) { createCookie("age", "verified", 365, cookieDomain); } location.assign(referer || window.location.origin); return; } else { console.log("Cookies DV and Age already set."); } if (window.location.href.includes("doc/driver-verify")) { window.location.href = window.location.origin; } //Search if (typeof unsafeWindow.searchs === "function") { const originalSearchs = unsafeWindow.searchs; unsafeWindow.searchs = function () { const searchValue = document.getElementById("search-input").value; if (searchValue) { window.location.pathname = `${userConfig.languages}/search/${searchValue}`; } }; } //Languages const languagePrefixes = ["en", "ja", "ko"]; const pathSegments = window.location.pathname.split("/").filter(Boolean); if (languagePrefixes.includes(pathSegments[0])) { if (pathSegments[0] !== userConfig.languages) { pathSegments[0] = userConfig.languages; window.location.pathname = "/" + pathSegments.join("/"); } } else { if (userConfig.languages !== "") { window.location.pathname = userConfig.languages + window.location.pathname; } } // PREVIEW GM_registerMenuCommand("Config", openSettings); let studios, prefixMap, prefixMapData; const lastFetchTime = parseInt(userConfig.lastUpdated) || 0; const oneWeekInMilliseconds = 7 * 24 * 60 * 60 * 1000; const now = new Date().getTime(); const isDataOld = now - lastFetchTime >= oneWeekInMilliseconds; if (isDataOld) { console.log( "[US:DEBUG] : ", `\n -- TIMESTAMP:\t\t${new Date().getTime()} \n -- LAST UPDATED:\t${lastFetchTime} \n -- EXPIRED:\t\t${isDataOld} \n -- Data is not available or old. Fetching new data.`, ); const response = await fetchDataFromAPI(); if (response) { userConfig.lastUpdated = now; userConfig.data = response; studios = response.studios; prefixMap = response.prefixMap; prefixMapData = response.prefixMapData; GM_setValue("config", userConfig); } else { console.log("[US:DEBUG] : ", "No Response from API. Will try again later."); userConfig.lastUpdated = new Date().getTime() - 6 * 24 * 60 * 60 * 1000; studios = userConfig.data.studios; prefixMap = userConfig.data.prefixMap; prefixMapData = userConfig.data.prefixMapData; GM_setValue("config", userConfig); } } else { console.log( "[US:DEBUG] : ", `\n -- TIMESTAMP:\t\t${new Date().getTime()} \n -- LAST UPDATED:\t${lastFetchTime} \n -- EXPIRED:\t\t${isDataOld} \n -- Data is still fresh. Skipping API call.`, ); studios = userConfig.data.studios; prefixMap = userConfig.data.prefixMap; prefixMapData = userConfig.data.prefixMapData; } function mergeStudiosByPrefix(studios, prefixes) { const result = {}; Object.keys(prefixes).forEach((prefix) => { result[prefix] = []; prefixes[prefix].forEach((key) => { if (studios[key]) { result[prefix] = [...result[prefix], ...studios[key]]; } }); }); return result; } function valuesToRegex(object) { return Object.fromEntries( Object.entries(object).map(([object, values]) => [ object, new RegExp(`\\b(?:${values.join("|")})(?:-)?\\d{3,5}(?:.)?\\b`, "i"), ]), ); } const studioMergedForPrefix = mergeStudiosByPrefix(studios, prefixMap); const regexMap = valuesToRegex(studioMergedForPrefix); //Default Pages const waterfall = document.querySelector("#waterfall"); const waterfallStyle = css` #waterfall { display: flex; flex-wrap: wrap; justify-content: center; } #waterfall .item { display: flex; margin: 10px; padding: 0; align-items: stretch; } .row:not(.footer-bar) { display: flex !important; flex-direction: column; } .movie-box { width: 250px !important; } .photo-frame { height: auto !important; } .movie-box img { height: auto !important; } `; //Disable masonry on resize if (jQuery.Mason) { console.log("[US:DEBUG] : Masonry Disabled"); jQuery.Mason.prototype.resize = function () { // Do nothing }; } // Main if (waterfall) { if (!window.location.href.includes("actresses")) { const waterfall = document.querySelector("#waterfall"); waterfall.style = ""; GM_addStyle(waterfallStyle); } const rows = document.querySelectorAll(".item"); rows.forEach((row, index) => { row.style.position = "relative"; row.style.removeProperty("top"); row.style.removeProperty("left"); // //if masonry js is not disabled, this reset the masonry positioning on resize. // let resizeTimeout; // window.addEventListener("resize", function () { // clearTimeout(resizeTimeout); // resizeTimeout = setTimeout(function () { // row.style.position = "relative"; // row.style.removeProperty("top"); // row.style.removeProperty("left"); // }, 200); // }); if (!row) return; const categoryCell = row.querySelector("date:first-of-type"); if (!categoryCell) return; const code = detectCode(categoryCell.innerText, regexMap); if (code && code.match) { console.log("[US:DEBUG] : ", "Valid studios detected: ", code); let codeId = code.match.toLowerCase(); const buttonDom = row.querySelector(".photo-info"); const newButton = document.createElement("a"); newButton.classList.add("preview-btn"); newButton.setAttribute("href", "?preview=" + codeId); newButton.setAttribute("title", "Original DVD Preview (Not Torrent Preview!)"); newButton.innerHTML = `Preview`; //buttonDom.appendChild(newButton); buttonDom.prepend(newButton); newButton.addEventListener("click", function (event) { event.preventDefault(); openPreview(codeId, code.cdn); }); } else { return; } }); } // Movie Page const moviePage = document.querySelector(".row.movie"); if (moviePage) { const code = detectCode( //document.querySelector('.col-md-3.info p span[style="color:#CC0000;"]').innerText, document.querySelector(".col-md-3.info p span:not(.header)").innerText, regexMap, ); if (code && code.match) { console.log("[US:DEBUG] : ", "Valid studios detected: ", code); let codeId = code.match.toLowerCase(); const newButton = document.createElement("a"); newButton.classList.add("preview-btn"); newButton.setAttribute("href", "?preview=" + codeId); newButton.setAttribute("title", "Original DVD Preview (Not Torrent Preview!)"); newButton.innerHTML = `Preview`; moviePage.querySelector(".col-md-3.info").prepend(newButton); newButton.addEventListener("click", function (event) { event.preventDefault(); openPreview(codeId, code.cdn); }); } } function detectCode(title, patterns) { for (const [key, pattern] of Object.entries(patterns)) { const match = title.match(pattern); if (match) { return { match: match[0], cdn: key, }; } } return { match: null, cdn: null }; } function openPreview(code, cdn) { code = cdn === "nmixx" ? code.replace(/-?(?<!^)(\d+)/, "00$1") : code; code = code.replace(/\s+/g, "").replace(/[^\w]/g, ""); fetchData(code, cdn, (response) => { if (response.length <= 0) { showToast(); return; } const video = document.createElement("video"); video.src = qualitySelector(userConfig.previewQuality, qualitySorter(response)) // Fixes //.replace(/\.co\.jp/g, ".com") //.replace(/cc3001/g, "pv3001") ; //DEV if (userConfig.devmode) { if (!prefixMapData[cdn]) prefixMapData[cdn] = ""; const finalCode = prefixMapData[cdn] + code; console.log(`[US:DEBUG] : ${code}, ${finalCode}, ${cdn}, ${video.src}`); if (!video.src.includes(finalCode)) { alert(`DEVMODE: CODE PREFIX MISMATCH : ${code}, ${finalCode}, ${cdn}, ${video.src}`); } } //DEV END video.autoplay = userConfig.playerAutoplay; video.volume = userConfig.playerVolume; video.loop = userConfig.playerLoop; video.controls = true; video.classList.add("overlay-video"); video.addEventListener("error", function (event) { console.error("Video failed to load:"); videoContainer.innerHTML = `<p style="color: white; margin: 0; text-align: center;">Preview Not Available.<br>This window will auto close in 3 seconds</p>`; setTimeout(() => { videoContainerOverlay.remove(); }, 3000); }); const closeButton = document.createElement("button"); closeButton.textContent = "Close"; closeButton.classList.add("close-button"); closeButton.addEventListener("click", () => { videoContainerOverlay.remove(); }); const videoContainerOverlay = document.createElement("div"); const videoContainer = document.createElement("div"); videoContainerOverlay.classList.add("overlay-video-container"); videoContainerOverlay.classList.add(`${code}`); videoContainer.classList.add("video-container"); videoContainer.appendChild(video); videoContainer.appendChild(closeButton); videoContainerOverlay.appendChild(videoContainer); document.body.appendChild(videoContainerOverlay); if (userConfig.playerClickAnywhereToClose) { videoContainerOverlay.addEventListener("click", (event) => { if (event.target !== video) { videoContainerOverlay.remove(); } }); } }); } function fetchData(id, cdn, callback) { let dvdId = id; const prefix = prefixMapData[cdn]; if (prefix) { dvdId = prefix + dvdId; } GM_xmlhttpRequest({ method: "GET", url: "https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=" + dvdId.toLowerCase(), onload: function (response) { if (response.status === 200) { let scriptData = "", scriptObj; let scripts = response.responseXML.scripts; if (scripts.length > 0) { for (let i = 0; i < scripts.length; i++) { const script = scripts[i]; if (script.textContent.includes("dmm")) { scriptData = script.textContent; } } const regex = /const args = ({.*?});/s; const match = scriptData.match(regex); if (match) { const jsonString = match[1]; try { scriptObj = JSON.parse(jsonString.replace(/\\/g, "")); } catch (error) { console.error("Error parsing JSON:", error); } //callback(scriptObj.src); callback(scriptObj.bitrates); } else { console.error("No match found in script data."); callback(""); } } else { console.error("Request failed with status:", response.status); callback(""); } } else { console.error("Request failed with status:", response.status); callback(""); } }, }); } function qualitySorter(items) { const qualityTiers = { 5: { min: 1440, max: 2160 }, 4: { min: 1080, max: 1439 }, 3: { min: 720, max: 1079 }, 2: { min: 480, max: 719 }, 1: { min: 0, max: 479 }, }; const extractResolution = (bitrate) => { const match = bitrate.match(/\((\d+)p\)/); return match ? parseInt(match[1], 10) : null; }; const getQualityTier = (resolution) => { for (const [tier, { min, max }] of Object.entries(qualityTiers)) { if (resolution >= min && resolution <= max) { return tier; } } return "Unknown"; }; const tierMap = new Map(); items.forEach((item) => { const resolution = extractResolution(item.bitrate); if (resolution) { const qualityTier = getQualityTier(resolution); if (!tierMap.has(qualityTier)) { tierMap.set(qualityTier, { quality: qualityTier, bitrate: item.bitrate, src: item.src, }); } } }); return Array.from(tierMap.values()); } function qualitySelector(desiredQuality, array) { const sortedArray = array.slice().sort((a, b) => b.quality - a.quality); for (const item of sortedArray) { if (item.quality <= desiredQuality) { return item.src; } } return null; } function showToast() { const toast = document.createElement("div"); toast.className = "toast"; toast.textContent = "No Preview Available!"; document.body.appendChild(toast); setTimeout(() => { toast.classList.add("show"); }, 0); setTimeout(() => { toast.classList.remove("show"); setTimeout(() => { document.body.removeChild(toast); }, 500); }, 2000); } const menubar = document.querySelector("#navbar"); const newLi = document.createElement("ul"); newLi.classList.add("nav"); newLi.classList.add("navbar-nav"); newLi.classList.add("navbar-right"); newLi.innerHTML = `<li><i class="fa fa-cogs"></i><a href="?setting" title="Settings">Settings</a></li>`; menubar.appendChild(newLi); newLi.addEventListener("click", function (event) { event.preventDefault(); openSettings(); }); function openSettings() { const isExist = document.querySelector(".settings-container"); if (isExist) return; const container = document.createElement("div"); container.classList.add("settings-container"); container.setAttribute("aria-label", "Settings"); const parsePlayerAutoplay = userConfig.playerAutoplay ? "checked" : ""; const parsePlayerLoop = userConfig.playerLoop ? "checked" : ""; const parsePlayerClickAnywhereToClose = userConfig.playerClickAnywhereToClose ? "checked" : ""; //const parseDarkTheme = userConfig.darkTheme ? "checked" : ""; const alwaysEnglish = userConfig.alwaysEnglish ? "checked" : ""; const devmode = userConfig.devmode ? "checked" : ""; container.innerHTML = html` <div class="settings-item"> <!-- <label for="video-width">Video Player Width (px) <i>[Default: 320]</i></label> <input type="text" id="video-width" name="video-width" placeholder="Enter width in px" value="${userConfig.playerWidth}"> </div> --> <div class="settings-item"> <label for="volume-slider" class="range-label">Volume:</label> <input type="range" id="volume-slider" name="volume-slider" min="0" max="1" value="${userConfig.playerVolume}" step="0.01"> </div> <div class="settings-item"> <label for="quality">Video Quality (if available):</label> <select name="qualities" id="quality"> <option value="5">QHD (2K/1440p)</option> <option value="4">FHD (1080p)</option> <option value="3">HD (720p)</option> <option value="2">Medium</option> <option value="1">Low</option> </select> </div> <div class="settings-item"> <label> <input type="checkbox" id="autoplay" name="autoplay" ${parsePlayerAutoplay}> Video Autoplay </label> </div> <div class="settings-item"> <label> <input type="checkbox" id="loop" name="loop" ${parsePlayerLoop}> Video Loop </label> </div> <div class="settings-item"> <label> <input type="checkbox" id="clickanywhere" name="clickanywhere" ${parsePlayerClickAnywhereToClose}> Click Anywhere To Close Player </label> </div> <div class="settings-item"> <label for="languages">Auto Select Language:</label> <select name="languages" id="languages"> <option value="">中文</option> <option value="ko">한국의</option> <option value="ja">日本语</option> <option value="en">English</option> </select> </div> <div class="settings-item"> <label disabled> <input type="checkbox" id="devmode" name="devmode" ${devmode}> Devmode </label> </div> <div class="settings-item last"> <label for="apiUrl">API Url:</label> <input type="text" id="apiUrl" name="apiUrl" value="${userConfig.url}"></input> <button id="resetApiData" class="button" type="button">Force Refresh API Data</button> </div> <div class="settings-item"> <button id="saveButton" class="button" type="button">Save Settings</button> <button id="closeButton" class="button" type="button">Close</button> </div> <div class="notice"> <p> <strong>3rd Dec 2024:</strong> DMM has started restricting preview access from outside of Japan. Alternative methods or bypasses will be added if found, but they are actively patching these measures as of now, so they may stop working at any time. For reliable access, consider using a VPN. </p> <p><strong>Known Bypass (If needed):</strong> Change your request header's User-Agent for DMM urls to Google Bot (<a href="https://i.postimg.cc/Vkc30r7B/image.png" target="_blank" rel="noopener noreferrer">Example</a>). If it's not working anymore, it means they've patched it.</p> <p> <strong>Recommended VPN:</strong> <a href="https://windscribe.com/yo/br5vlcwg" target="_blank" rel="noopener noreferrer">Windscribe</a> (Affiliate link). You can get as low as $3/month from their build-a-plan feature. They also often have $29/year offers throughout the year. </p> </div> `; document.body.appendChild(container); const preQuality = document.getElementById("quality"); if (preQuality) { preQuality.value = userConfig.previewQuality || 2; } const languages = document.getElementById("languages"); if (languages) { languages.value = userConfig.languages || "cn"; } let saveButton = document.getElementById("saveButton"); saveButton.addEventListener("click", saveConfigMenu); let apiButton = document.getElementById("resetApiData"); apiButton.addEventListener("click", () => { userConfig.lastUpdated = 0; saveConfigMenu(); }); let closeButton = document.getElementById("closeButton"); closeButton.addEventListener("click", () => { const toRemove = document.querySelector(".settings-container"); if (toRemove) { toRemove.remove(); } else { console.log("[US:DEBUG] : ", "Bruh."); } }); function saveConfigMenu() { const preQuality = document.getElementById("quality"); const newVolume = document.getElementById("volume-slider"); const autoplay = document.getElementById("autoplay"); const loop = document.getElementById("loop"); //const darktheme = document.getElementById("darktheme"); const clickanywhere = document.getElementById("clickanywhere"); const languages = document.getElementById("languages"); const apiUrl = document.getElementById("apiUrl"); const devmode = document.getElementById("devmode"); let finalVolume; if ( newVolume.value !== null && !isNaN(newVolume.value) && newVolume.value >= 0 && newVolume.value <= 1 ) { finalVolume = parseFloat(newVolume.value); } //userConfig.darkTheme = darktheme.checked; userConfig.playerAutoplay = autoplay.checked; userConfig.playerVolume = finalVolume; userConfig.playerLoop = loop.checked; userConfig.playerClickAnywhereToClose = clickanywhere.checked; userConfig.previewQuality = preQuality.value; userConfig.languages = languages.value; userConfig.url = apiUrl.value; userConfig.alwaysEnglish = alwaysEnglish.checked; userConfig.devmode = devmode.checked; GM_setValue("config", userConfig); if (userConfig.darkTheme) { localStorage.setItem("theme", "dark"); } location.reload(); } } } main(); })();