您需要先安装一个扩展,例如 篡改猴、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 // @version 2.0.1 // @author Chaewon // @description Add original video preview. // @license Unlicense // @icon https://sukebei.nyaa.si/static/favicon.png // ==/UserScript== (function () { "use strict"; const css = String.raw; const html = String.raw; 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: 400px; max-height: 92vh; 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; overflow: auto; } .settings-container .settings-item:not(:last-of-type) { margin-bottom: 1rem; } .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, .settings-container #languages { 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; } table.torrent-list tbody .comments { margin-left: 8px; } .btn-translate { margin-left: 8px; position: relative; float: right; border: 1px solid #d7d7d7; border-top-color: rgb(215, 215, 215); border-right-color: rgb(215, 215, 215); border-bottom-color: rgb(215, 215, 215); border-left-color: rgb(215, 215, 215); border-radius: 3px; color: #383838; padding: 0 5px; font-size: small; background-color: #ffffff; } .btn-translate { color: #337ab7; } body.dark .btn-translate { border-color: #212121; color: #247fcc; background-color: #2f2c2c; } .translatedText { display: block; word-break: break-word; white-space: normal; font-size: 0.9em; font-style: italic; } .languages { padding-top: 2px; float: right; position: relative; font-size: 0.9em; font-style: italic; } `; GM_addStyle(stylesheet); const defaultConfig = { darkTheme: false, playerAutoplay: true, playerVolume: 0.1, playerLoop: true, playerClickAnywhereToClose: false, previewQuality: 2, lastUpdated: 0, devMode: false, translate: false, languages: "en", 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", }, }, //Basic data, incase of API failure on first run url: "https://api.jsonsilo.com/public/445432dd-f613-4528-a45d-71a1efacee95", }; const mergedConfig = { ...defaultConfig, ...GM_getValue("config") }; const userConfig = mergedConfig; console.log("[US:DEBUG] : ", `CONFIG: `, userConfig); 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() { 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 (userConfig.darkTheme && localStorage.getItem("theme") !== "dark") { localStorage.setItem("theme", "dark"); location.reload(); } 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); if (window.location.pathname.startsWith("/view/")) { console.log("[US:DEBUG] : ", "Torrent Detail Page"); const titlePanel = document.getElementsByClassName("panel-title"); const titleCell = document.getElementsByClassName("panel-heading"); if (userConfig.translate) { const trButton = document.createElement("a"); trButton.classList.add("btn", "btn-translate"); trButton.setAttribute("href", `?translate=${titleCell[0].innerText}`); trButton.setAttribute("title", "Translate"); trButton.innerHTML = `<i class="fa fa-globe" aria-hidden="true"></i>`; titleCell[0].prepend(trButton); trButton.addEventListener("click", async function (event) { event.preventDefault(); trButton.setAttribute("disabled", ""); trButton.removeAttribute("title"); if (!titleCell[0].getAttribute("translated")) { titleCell[0].setAttribute("translated", "true"); try { const response = await gTranslate(titleCell[0].innerText); if (response.length <= 0) { showToast("Translation Service Unavailable"); return; } const node = document.createElement("span"); node.innerText = response; node.classList.add("translatedText"); node.setAttribute("title", response); titleCell[0].appendChild(node); } catch (error) { console.error("Translation failed:", error); showToast("Translation Service Unavailable"); } } }); } if ( titlePanel[0].parentElement.nextElementSibling.classList.contains("panel-body") && titlePanel[0].parentElement.nextElementSibling.children[0].classList.contains("row") ) { const code = detectCode(titlePanel[0].innerText, regexMap); if (code && code.match) { console.log("[US:DEBUG] : ", "Valid 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", "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 { console.log("[US:DEBUG] : ", "Non Torrent Detail Page"); const rows = document.querySelectorAll("tr"); 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)"); // Translate if (userConfig.translate) { const trButton = document.createElement("a"); trButton.classList.add("btn", "btn-translate"); trButton.setAttribute("href", `?translate=${titleCell.innerText}`); trButton.setAttribute("title", "Translate"); trButton.innerHTML = `<i class="fa fa-globe" aria-hidden="true"></i>`; titleCell.prepend(trButton); trButton.addEventListener("click", async function (event) { event.preventDefault(); trButton.setAttribute("disabled", ""); trButton.removeAttribute("title"); if (!titleCell.getAttribute("translated")) { titleCell.setAttribute("translated", "true"); try { const response = await gTranslate(titleCell.innerText); if (response.length <= 0) { showToast("Translation Service Unavailable"); return; } const node = document.createElement("span"); node.innerText = response; node.classList.add("translatedText"); node.setAttribute("title", response); titleCell.appendChild(node); } catch (error) { console.error("Translation failed:", error); showToast("Translation Service Unavailable"); } } }); } //Preview Button if (categoryCell === "Real Life - Videos" || categoryCell === "Art - Pictures") { const code = detectCode(titleCell.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.setAttribute("href", "?preview=" + codeId); newButton.setAttribute("title", "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 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("No Preview Available!"); return; } const video = document.createElement("video"); video.src = qualitySelector(userConfig.previewQuality, qualitySorter(response)); //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; 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 gTranslate(string) { const languages = userConfig.languages || defaultConfig.languages || "en"; return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${languages}&dt=t&q=${encodeURIComponent( string, )}`, responseType: "json", onload: function (response) { if (response.status === 200) { let answer = JSON.parse(response.responseText); const finalAnswer = answer[0]?.map((segment) => segment[0]).join(""); resolve(finalAnswer); } else { reject(`Request failed with status: ${response.status}`); } }, }); }); } function showToast(string) { const toast = document.createElement("div"); toast.className = "toast"; toast.textContent = string; document.body.appendChild(toast); setTimeout(() => { toast.classList.add("show"); }, 0); setTimeout(() => { toast.classList.remove("show"); setTimeout(() => { document.body.removeChild(toast); }, 500); }, 2000); } 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(); }); const menubar = document.querySelector(".navbar-right .dropdown-menu"); const newLi = document.createElement("li"); const newLink = document.createElement("a"); newLink.setAttribute("href", "?setting"); newLink.setAttribute("title", "Settings"); newLink.innerHTML = `<i class="fa fa-cogs"></i> Settings`; newLi.appendChild(newLink); menubar.appendChild(newLi); newLink.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 devmode = userConfig.devmode ? "checked" : ""; const translate = userConfig.translate ? "checked" : ""; const parsePlayerWidth = null; const parsePlayerVolume = userConfig.playerVolume; const parseApiUrl = userConfig.url; 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="${parsePlayerWidth}"> </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="${parsePlayerVolume}" 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 disabled> <input type="checkbox" id="darktheme" name="darktheme" ${parseDarkTheme}> Auto Dark Theme </label> </div> <div class="settings-item"> <label disabled> <input type="checkbox" id="translate" name="translate" ${translate}> Translate Button </label> </div> <div class="settings-item"> <label for="languages">Translate to:</label> <select id="languages" name="languages"> <option value="af">Afrikaans</option> <option value="sq">Albanian</option> <option value="am">Amharic</option> <option value="ar">Arabic</option> <option value="hy">Armenian</option> <option value="az">Azerbaijani</option> <option value="eu">Basque</option> <option value="be">Belarusian</option> <option value="bn">Bengali</option> <option value="bs">Bosnian</option> <option value="bg">Bulgarian</option> <option value="ca">Catalan</option> <option value="ceb">Cebuano</option> <option value="ny">Chichewa</option> <option value="zh-CN">Chinese (Simplified)</option> <option value="zh-TW">Chinese (Traditional)</option> <option value="co">Corsican</option> <option value="hr">Croatian</option> <option value="cs">Czech</option> <option value="da">Danish</option> <option value="nl">Dutch</option> <option value="en">English</option> <option value="eo">Esperanto</option> <option value="et">Estonian</option> <option value="tl">Filipino</option> <option value="fi">Finnish</option> <option value="fr">French</option> <option value="fy">Frisian</option> <option value="gl">Galician</option> <option value="ka">Georgian</option> <option value="de">German</option> <option value="el">Greek</option> <option value="gu">Gujarati</option> <option value="ht">Haitian Creole</option> <option value="ha">Hausa</option> <option value="haw">Hawaiian</option> <option value="iw">Hebrew</option> <option value="hi">Hindi</option> <option value="hmn">Hmong</option> <option value="hu">Hungarian</option> <option value="is">Icelandic</option> <option value="ig">Igbo</option> <option value="id">Indonesian</option> <option value="ga">Irish</option> <option value="it">Italian</option> <option value="ja">Japanese</option> <option value="jw">Javanese</option> <option value="kn">Kannada</option> <option value="kk">Kazakh</option> <option value="km">Khmer</option> <option value="rw">Kinyarwanda</option> <option value="ko">Korean</option> <option value="ku">Kurdish (Kurmanji)</option> <option value="ky">Kyrgyz</option> <option value="lo">Lao</option> <option value="la">Latin</option> <option value="lv">Latvian</option> <option value="lt">Lithuanian</option> <option value="lb">Luxembourgish</option> <option value="mk">Macedonian</option> <option value="mg">Malagasy</option> <option value="ms">Malay</option> <option value="ml">Malayalam</option> <option value="mt">Maltese</option> <option value="mi">Maori</option> <option value="mr">Marathi</option> <option value="mn">Mongolian</option> <option value="my">Myanmar (Burmese)</option> <option value="ne">Nepali</option> <option value="no">Norwegian</option> <option value="or">Odia (Oriya)</option> <option value="ps">Pashto</option> <option value="fa">Persian</option> <option value="pl">Polish</option> <option value="pt">Portuguese</option> <option value="pa">Punjabi</option> <option value="ro">Romanian</option> <option value="ru">Russian</option> <option value="sm">Samoan</option> <option value="gd">Scots Gaelic</option> <option value="sr">Serbian</option> <option value="st">Sesotho</option> <option value="sn">Shona</option> <option value="sd">Sindhi</option> <option value="si">Sinhala</option> <option value="sk">Slovak</option> <option value="sl">Slovenian</option> <option value="so">Somali</option> <option value="es">Spanish</option> <option value="su">Sundanese</option> <option value="sw">Swahili</option> <option value="sv">Swedish</option> <option value="tg">Tajik</option> <option value="ta">Tamil</option> <option value="tt">Tatar</option> <option value="te">Telugu</option> <option value="th">Thai</option> <option value="tr">Turkish</option> <option value="tk">Turkmen</option> <option value="uk">Ukrainian</option> <option value="ur">Urdu</option> <option value="ug">Uyghur</option> <option value="uz">Uzbek</option> <option value="vi">Vietnamese</option> <option value="cy">Welsh</option> <option value="xh">Xhosa</option> <option value="yi">Yiddish</option> <option value="yo">Yoruba</option> <option value="zu">Zulu</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="${parseApiUrl}"></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> `; document.body.appendChild(container); const preQuality = document.getElementById("quality"); if (preQuality) { preQuality.value = userConfig.previewQuality || 2; } const preLanguages = document.getElementById("languages"); if (preLanguages) { preLanguages.value = userConfig.languages || "en"; } 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 devMode = document.getElementById("devmode"); const apiUrl = document.getElementById("apiUrl"); const languages = document.getElementById("languages"); const translate = document.getElementById("translate"); 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.devMode = devMode.checked; userConfig.url = apiUrl.value; userConfig.languages = languages.value; userConfig.translate = translate.checked; GM_setValue("config", userConfig); if (userConfig.darkTheme) { localStorage.setItem("theme", "dark"); } location.reload(); } } } main(); })();