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