您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add original video preview.
当前为
// ==UserScript== // @name SUKEBEI PLUS // @namespace Violentmonkey Scripts // @match *://sukebei.nyaa.si/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @version 1.0.5 // @author Chaewon // @description Add original video preview. // @license Unlicense // @icon https://sukebei.nyaa.si/static/favicon.png // ==/UserScript== (function () { "use strict"; const stylesheet = ` .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; } .overlay-video { width: auto; max-width: 90%; min-height: 60%; height: auto; max-height: 90%; border: 1px solid #919191; background: #000; } .close-button { position: absolute; padding: 6px 14px; top: 10px; right: 10px; background: none; border: none; color: white; cursor: pointer; border: 1px solid #333; } .settings-container { position: fixed; right: 1em; top: 2em; max-width: 400px; 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 { color: black; }`; GM_addStyle(stylesheet); GM_registerMenuCommand("Config", openSettings); let userConfig; const fetchConfig = GM_getValue("config"); if (fetchConfig) { userConfig = fetchConfig; } else { console.log("[US:DEBUG] : ", "No local config found. Uses default."); userConfig = { darkTheme: false, playerAutoplay: false, playerVolume: 0.1, playerLoop: true, playerClickAnywhereToClose: false, previewQuality: 5, }; } console.log("[US:DEBUG] : ", "CONFIG: ", userConfig); // Manually added. Not all code added nor tested. const studios = { general: ["PFES"], das: ["DASD", "DASS", "DAZD", "PLA", "PLB"], sOne: ["SSIS", "SOE", "SNIS", "SSNI", "OFJE", "SONE", "ONSD"], wanz: [ "WAAA", "BMW", "WANZ", "WNZ", "PPS", "WNZS", "WAB", "WFS", "SWF", ], eBody: ["EBOD", "EBWH", "MKCK", "EYAN"], oppai: ["PPPD", "PPPE", "PPBD", "PPMD", "PPSD"], aurora: [ "APNS", "APGH", "APAK", "APAA", "APGG", "APAE", "APKH", "APNH", ], bibian: ["BBAN", "BBSS"], dahlia: ["DLDSS"], faleno: ["FSDSS", "FCDSS", "FTBLD", "FTKD", "FTHTD", "MGOLD"], hunter: ["HUNTC", "HUBLK", "HHF", "HUNTA", "HUNBL", "HUNTB"], kawaii: ["CAWD", "KAWD", "KWBD", "KANE", "KAPD"], moodyz: [ "MIDV", "MIMK", "MIFD", "MIAB", "MIZD", "MIRD", "MIAA", "MIDE", "KMIDE", "MIAE", "MIGD", "MIAD", "MIBD", "MIDD", "MDED", "MDLD", "MDUD", ], madonna: [ "JUQ", "JUY", "ROE", "JUMS", "ACHJ", "URE", "JUSD", "JUL", "OBA", ], oneMore: ["ONEX"], //also has ONEZ - Prestige is bigger therefor priortized over this studio. premium: ["PRED", "PBD", "PRWF", "PRST", "PGD", "PXD", "PJD", "PID"], jetEizou: ["NGOD", "NKKD", "NDRA", "NBES", "LLAN"], prestige: [ "ABF", "ABP", "YRK", "FIG", "SHF", "HHE", "AAS", "THU", "PPX", "HEO", "PXM", "VPC", "ONEZ", "PPT", "ICHK", "ABW", "AFS", "DCX", "CPDE", "MGT", "KUM", "TRE", //"MBMH", "FIR", "DNW", "DOCP", "TEN", "KBI", "GOAL", "MFCC", "GNAB", //"MBM", "GZAP", "JNT", "CHN", "JAC", "FIV", "TUS", "BGN", "ASI", "PXH", "MTM", //"DCV", "GGEN", "DTT", "CPDE", "KPB", "DIC", "AMA", "YRH", "CDC", "SOUD", "NAM", "DCX", "SIM", ], attackers: [ "ADN", "ATK", "ATID", "SAME", "YUJ", "SSPD", "ATKD", "JBD", "ATAD", "RBK", "SHKD", ], sodCreate: [ "STAR", "STARS", "START", "STARTS", "SDNM", "SDMM", "SDMF", "SDDE", "SDAM", "SETM", "SDMUA", "SDJS", "FTAV", "SUWK", "MOGI", "KKBT", "SDMU", "KIRE", "KMHRS", "SDTH", "KUSE", "MSFH", "SDNT", "SDEN", ], aliceJapan: ["DVAJ", "DV"], ideaPocket: [ "IPX", "IPZ", "IPZZ", "IPTD", "IDBD", "IPIT", "SUPD", "IPSE", ], nagaeStyle: ["NSFS"], naturalHigh: ["NHDTB", "NHDTA", "NHDT"], tameikeiGoro: ["MEYD", "MDYD", "MBYD"], sQute: ["SQTE", "KRAY"], royal: ["ROYD"], materiall: ["MTATA", "MTALL", "MTABS"], honnaka: ["MIH", "HMN", "HNDB", "HNDS", "HND", "KRND"], lunatics: ["LULU"], nampaJapan: ["NPJS", "NNPJ", "NPJB", "TNB"], serebuNoTomo: ["CEMD", "CEAD", "CESD", "CETD"], barutan: [ "BASJ", "BASB", "BAGR", "BACJ", "BAHP", "BAEM", "BACN", "BAVC", "BADA", ], guppi: ["GUPP"], shiroutoKissa: ["HAZU"], cineMagic: ["CNY", "CMC", "CMN", "CMA", "CMV", "CMF"], globalMediaAnnex: ["GMA", "GARA", "GAJK", "GMAB"], takaraEizou: [ "MOND", "ALDN", "NTRD", "MGHT", "SPRD", "AVOP", "RADC", "CEMN", "DTKM", ], fairAndWay: ["FWAY"], hsoda: ["HSODA", "PFES"], ieEnergy: ["IESP", "IENF", "IESM"], kmProduce: [ "SCOP", "REAL", "MKMP", "MDTM", "MDBK", "BOKD", "BAZX", "UMSO", "SCPX", "OKAX", "ENKI", "BAZE", "MDTE", "NANX", ], kaguyaHimePtMousozoku: ["KPIE", "KAGP", "MASM", "MKON", "BABM"], hotEntertainment: [ "HEZ", "SHE", "HOC", "HFF", "HJT", "HZM", "HVY", "HNU", "HNK", ], crystalEizou: ["NITR", "EKDV", "MADV", "CADV", "MADM", "GEKI"], rocket: ["RCTD", "RCT"], }; const studiosInDMM = [ ...studios.general, ...studios.sOne, ...studios.ideaPocket, ...studios.wanz, ...studios.attackers, ...studios.kawaii, ...studios.madonna, ...studios.hunter, ...studios.jetEizou, ...studios.bibian, ...studios.das, ...studios.aurora, ...studios.eBody, ...studios.tameikeiGoro, ...studios.nagaeStyle, ...studios.moodyz, ...studios.oppai, ...studios.premium, ...studios.aliceJapan, ...studios.sQute, ...studios.royal, ...studios.honnaka, ...studios.lunatics, ...studios.nampaJapan, ...studios.serebuNoTomo, ...studios.barutan, ...studios.guppi, ...studios.shiroutoKissa, ...studios.cineMagic, ...studios.globalMediaAnnex, ...studios.takaraEizou, ...studios.fairAndWay, ...studios.hsoda, ...studios.kmProduce, ...studios.kaguyaHimePtMousozoku, ], plusOne = [ ...studios.faleno, ...studios.dahlia, ...studios.naturalHigh, ...studios.sodCreate, ...studios.materiall, ...studios.ieEnergy, ...studios.rocket, ], prestigeStudios = [...studios.prestige]; const regexPatterns = { dmm: new RegExp(`\\b(?:${studiosInDMM.join("|")})(?:-)?\\d{3}\\b`, "i"), plusOne: new RegExp(`\\b(?:${plusOne.join("|")})(?:-)?\\d{3}\\b`, "i"), prestige: new RegExp( `\\b(?:${prestigeStudios.join("|")})(?:-)?\\d{3}\\b`, "i" ), oneMore: new RegExp( `\\b(?:${studios.oneMore.join("|")})(?:-)?\\d{3}\\b`, "i" ), hotEntertainment: new RegExp( `\\b(?:${studios.hotEntertainment.join("|")})(?:-)?\\d{3}\\b`, "i" ), //faleno: new RegExp(`\\b(?:${faleno.join('|')})(?:-)?\\d{3}\\b`, 'i'), //dahlia: new RegExp(`\\b(?:${dahlia.join('|')})(?:-)?\\d{3}\\b`, 'i') }; //SECTION - MAIN if (window.location.pathname.startsWith("/view/")) { console.log("[US:DEBUG] : ", "Torrent Detail Page"); const titlePanel = document.getElementsByClassName("panel-title"); if ( titlePanel[0].parentElement.nextElementSibling.classList.contains( "panel-body" ) && titlePanel[0].parentElement.nextElementSibling.children[0].classList.contains( "row" ) ) { //Append Button const code = detectCode(titlePanel[0].innerText, regexPatterns); if (code && code.match) { console.log("[US:DEBUG] : ", "DMM studios detected: ", code); let codeId = code.match.toLowerCase(); const buttonDom = document.getElementsByClassName( "panel-footer clearfix" )[0]; const newButton = document.createElement("a"); newButton.setAttribute("href", "?preview=" + codeId); newButton.setAttribute( "title", "Original DVD Preview (Not Torrent Preview!)" ); newButton.innerHTML = `<i class="fa fa-video-camera"></i> Preview`; const buttonDivider = document.createTextNode(" ・"); buttonDom.insertBefore(newButton, buttonDom.firstChild); buttonDom.insertBefore( buttonDivider, buttonDom.firstChild.nextSibling ); newButton.addEventListener("click", function (event) { event.preventDefault(); openPreview(codeId, code.cdn); }); } else { return; } } else { return; } } else { console.log("[US:DEBUG] : ", "Non Torrent Detail Page"); const rows = document.querySelectorAll("tr"); //Append Button //TODO - Only check if AV category? rows.forEach((row, index) => { if (!row || index === 0) return; const categoryCell = row.querySelector("td:nth-child(1) a")["title"]; const titleCell = row.querySelector("td:nth-child(2)"); const linkCell = row.querySelector("td:nth-child(3)"); if (categoryCell === "Real Life - Videos") { const code = detectCode(titleCell.innerText, regexPatterns); if (code && code.match) { console.log( "[US:DEBUG] : ", "DMM studios detected: ", code ); let codeId = code.match.toLowerCase(); //const buttonDom = // titleCell.parentElement.querySelector("td:nth-child(3)"); const newButton = document.createElement("a"); newButton.setAttribute("href", "?preview=" + codeId); newButton.setAttribute( "title", "Original DVD Preview (Not Torrent Preview!)" ); newButton.innerHTML = `<i class="fa fa-video-camera"></i>`; linkCell.appendChild(newButton); newButton.addEventListener("click", function (event) { event.preventDefault(); openPreview(codeId, code.cdn); }); } else { return; } } }); } 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 fetchData(id, cdn, callback) { //NOTE PER WEBSITE METHOD // switch (cdn) { // case 'dmm': // GM_xmlhttpRequest({ // method: "GET", // url: 'https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=' + id.toLowerCase(), // onload: function (response) { // if (response.status === 200) { // let scriptData, scriptObj; // let scripts = response.responseXML.scripts; // 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) // } // } else { // console.error("Request failed with status:", response.status); // } // } // }); // break; // case 'faleno': // GM_xmlhttpRequest({ // method: "GET", // url: 'https://faleno.jp/top/works/' + id.toLowerCase() + '/', // onload: function (response) { // if (response.status === 200) { // const links = response.responseXML.links; // const findPreview = (links) => { // for (let i = 0; i < links.length; i++) { // const link = links[i]; // if ( // link.classList.contains('pop_sample') && // link.href.toLowerCase().replace(/\s+/g, '').replace(/[^\w]/g, '').includes(id.toLowerCase()) && // link.href.endsWith('.mp4') // ) { // return link.href; // } // } // return null; // }; // const result = findPreview(links); // callback(result) // } else { // console.error("Request failed with status:", response.status); // } // } // }); // break; // default: // console.log("[US:DEBUG] : ", `No CDN found: ${expr}.`); // } //NOTE DMM ONLY METHOD let dvdId = id; //if (cdn === 'faleno' || cdn === 'dahlia') { if (cdn === "plusOne") { dvdId = "1" + dvdId; } if (cdn === "prestige") { dvdId = "118" + dvdId; } if (cdn === "oneMore") { dvdId = "h_1674" + dvdId; } if (cdn === "hotEntertainment") { dvdId = "59" + 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; 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( "Request failed with status:", response.status ); } }, }); } 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 openPreview(code, cdn) { code = code.replace(/\s+/g, "").replace(/[^\w]/g, ""); fetchData(code, cdn, (response) => { //if (!response) return; //Player const video = document.createElement("video"); video.src = qualitySelector(userConfig.previewQuality, qualitySorter(response)); 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(() => { videoContainer.remove(); }, 3000); }); const closeButton = document.createElement("button"); closeButton.textContent = "Close"; closeButton.classList.add("close-button"); closeButton.addEventListener("click", () => { videoContainer.remove(); }); const videoContainer = document.createElement("div"); videoContainer.classList.add("overlay-video-container"); videoContainer.classList.add(`${code}`); videoContainer.appendChild(video); videoContainer.appendChild(closeButton); document.body.appendChild(videoContainer); if (userConfig.playerClickAnywhereToClose) { videoContainer.addEventListener("click", (event) => { if (event.target !== video) { videoContainer.remove(); } }); } }); } //!SECTION // SECTION - Settings //const footer = document.querySelector('footer'); const footerLink = document.querySelector("footer p"); const settingButton = document.createElement("a"); const buttonDivider = document.createTextNode("・"); settingButton.setAttribute("href", "?setting"); settingButton.setAttribute("title", "Settings"); settingButton.innerHTML = `<i class="fa fa-cogs"></i> Settings`; footerLink.insertBefore(settingButton, footerLink.firstChild); footerLink.insertBefore(buttonDivider, footerLink.firstChild.nextSibling); settingButton.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"); // Parse config const parsePlayerAutoplay = userConfig.playerAutoplay ? "checked" : ""; const parsePlayerLoop = userConfig.playerLoop ? "checked" : ""; const parsePlayerClickAnywhereToClose = userConfig.playerClickAnywhereToClose ? "checked" : ""; const parseDarkTheme = userConfig.darkTheme ? "checked" : ""; container.innerHTML = ` <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 last"> <label disabled> <input type="checkbox" id="darktheme" name="darktheme" ${parseDarkTheme} disabled> Auto Dark Theme </label> </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>`; // Append the container to the DOM document.body.appendChild(container); // Access the quality select element and set its value const preQuality = document.getElementById("quality"); if (preQuality) { preQuality.value = userConfig.previewQuality || 5; } // Add event listeners for buttons let saveButton = document.getElementById("saveButton"); saveButton.addEventListener("click", 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"); let finalVolume; if ( newVolume.value !== null && !isNaN(newVolume.value) && newVolume.value >= 0 && newVolume.value <= 1 ) { finalVolume = parseFloat(newVolume.value); } const newUserConfig = { darkTheme: darktheme.checked, playerAutoplay: autoplay.checked, playerVolume: finalVolume, playerLoop: loop.checked, playerClickAnywhereToClose: clickanywhere.checked, previewQuality: preQuality.value, }; GM_setValue("config", newUserConfig); location.reload(); } } //!SECTION })();