| const getLanguage = (code) => { |
| const lang = new Intl.DisplayNames(["en"], { type: "language" }); |
| return lang.of(code); |
| }; |
|
|
| let settings = { |
| enableProxy: false, |
| proxyUrl: "", |
| enableProwlarr: false, |
| prowlarrHost: "", |
| prowlarrApiKey: "", |
| enableJackett: false, |
| jackettHost: "", |
| jackettApiKey: "", |
| }; |
|
|
| const searchWrapper = document.querySelector("#search-wrapper"); |
| var player = null; |
|
|
| function doubleTapFF(options) { |
| var videoElement = this |
| var videoElementId = this.id(); |
| document.getElementById(videoElementId).addEventListener("touchstart", tapHandler); |
| var tapedTwice = false; |
| function tapHandler(e) { |
| if (!videoElement.paused()) { |
|
|
| if (!tapedTwice) { |
| tapedTwice = true; |
| setTimeout(function () { |
| tapedTwice = false; |
| }, 300); |
| return false; |
| } |
| e.preventDefault(); |
| var br = document.getElementById(videoElementId).getBoundingClientRect(); |
|
|
|
|
| var x = e.touches[0].clientX - br.left; |
| var y = e.touches[0].clientY - br.top; |
|
|
| if (x <= br.width / 2) { |
| videoElement.currentTime(player.currentTime() - 10) |
| } else { |
| videoElement.currentTime(player.currentTime() + 10) |
|
|
| } |
| } |
|
|
|
|
| } |
| } |
| videojs.registerPlugin('doubleTapFF', doubleTapFF); |
|
|
| (async function ($) { |
| |
| const toggleDarkMode = () => { |
| const html = document.querySelector("html"); |
| html.classList.toggle("dark"); |
| localStorage.setItem( |
| "theme", |
| html.classList.contains("dark") ? "dark" : "light" |
| ); |
| }; |
| const toggleDarkModeButton = document.querySelector("#toggle_theme"); |
| toggleDarkModeButton.addEventListener("click", toggleDarkMode); |
|
|
| |
| const pastButton = document.querySelector("#copy_magnet"); |
| pastButton.addEventListener("click", async () => { |
| navigator.clipboard.readText().then((text) => { |
| document.getElementById("magnet").value = text; |
| }); |
| }); |
|
|
| |
| const demoButton = document.querySelector("#demo_torrent"); |
| demoButton.addEventListener("click", async () => { |
| document.getElementById("magnet").value = |
| "magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F&xs=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2Fsintel.torrent"; |
|
|
| document |
| .querySelector("#torrent-form") |
| .dispatchEvent(new Event("submit")); |
| }); |
|
|
| const form = document.querySelector("#torrent-form"); |
| form.addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| const magnet = document.querySelector("#magnet").value; |
|
|
| if (!magnet) { |
| butterup.toast({ |
| message: "Please enter a magnet link", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| return; |
| } |
|
|
| |
| if (player) { |
| player.dispose(); |
| player = null; |
| const vidElm = document.createElement("video"); |
| vidElm.setAttribute("id", "video-player"); |
| vidElm.setAttribute("class", "video-js mt-10 w-full"); |
|
|
| document.querySelector("main").appendChild(vidElm); |
| } |
|
|
| form |
| .querySelector("button[type=submit]") |
| .setAttribute("disabled", "disabled"); |
| form.querySelector("button[type=submit]").innerHTML = ""; |
| form.querySelector("button[type=submit]").classList.add("loader"); |
|
|
| const res = await fetch("/api/v1/torrent/add", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ magnet }), |
| }); |
|
|
| if (!res.ok) { |
| const err = await res.json(); |
| butterup.toast({ |
| message: err.error || "Something went wrong", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| form.querySelector("button[type=submit]").removeAttribute("disabled"); |
| form.querySelector("button[type=submit]").innerHTML = "Play Now"; |
| form.querySelector("button[type=submit]").classList.remove("loader"); |
| searchResults.querySelectorAll("#play-torrent").forEach((el) => { |
| el.removeAttribute("disabled"); |
| el.innerHTML = "Watch"; |
| el.classList.remove("loader"); |
| }); |
| return; |
| } |
|
|
| const { sessionId } = await res.json(); |
| const filesRes = await fetch("/api/v1/torrent/" + sessionId); |
|
|
| if (!filesRes.ok) { |
| const err = await filesRes.json(); |
| butterup.toast({ |
| message: err.error || "Something went wrong", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| form.querySelector("button[type=submit]").removeAttribute("disabled"); |
| form.querySelector("button[type=submit]").innerHTML = "Play Now"; |
| form.querySelector("button[type=submit]").classList.remove("loader"); |
| document.querySelectorAll("#play-torrent").forEach((el) => { |
| el.removeAttribute("disabled"); |
| el.innerHTML = "Watch"; |
| el.classList.remove("loader"); |
| }); |
| return; |
| } |
|
|
| const files = await filesRes.json(); |
|
|
| |
| const videoFiles = files.filter((f) => |
| f.name.match(/\.(mp4|mkv|webm|avi)$/i) |
| ); |
|
|
| if (!videoFiles.length) { |
| butterup.toast({ |
| message: "No video file found", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| form.querySelector("button[type=submit]").removeAttribute("disabled"); |
| form.querySelector("button[type=submit]").innerHTML = "Play Now"; |
| form.querySelector("button[type=submit]").classList.remove("loader"); |
| document.querySelectorAll("#play-torrent").forEach((el) => { |
| el.removeAttribute("disabled"); |
| el.innerHTML = "Watch"; |
| el.classList.remove("loader"); |
| }); |
| return; |
| } |
|
|
| const subtitleFiles = files.filter((f) => |
| f.name.match(/\.(srt|vtt|sub)$/i) |
| ); |
|
|
| const videoUrls = videoFiles.map((file) => { |
| return { |
| src: "/api/v1/torrent/" + sessionId + "/stream/" + file.index, |
| title: file.name, |
| type: "video/mp4", |
| }; |
| }); |
|
|
| let subtitles = []; |
| if (subtitleFiles.length) { |
| subtitles = subtitleFiles.map((subFile) => { |
| let language = "en"; |
| let langName = "English"; |
|
|
| |
| console.log(subFile.name); |
| const langMatch = subFile.name.match(/\.([a-z]{2,3})\.(srt|vtt|sub)$/i); |
| if (langMatch) { |
| language = langMatch[1]; |
| langName = getLanguage(language); |
| } |
|
|
| return { |
| src: |
| "/api/v1/torrent/" + |
| sessionId + |
| "/stream/" + |
| subFile.index + |
| ".vtt?format=vtt", |
| srclang: language, |
| label: langName, |
| kind: "subtitles", |
| type: "vtt", |
| }; |
| }); |
| } |
| player = videojs( |
| "video-player", |
| { |
| fluid: true, |
| controls: true, |
| autoplay: true, |
| preload: "auto", |
| sources: [{ |
| src: videoUrls[0].src, |
| type: videoUrls[0].type, |
| label: videoUrls[0].title, |
| }], |
| tracks: subtitles, |
| html5: { |
| nativeTextTracks: false |
| }, |
| plugins: { |
| hotkeys: { |
| volumeStep: 0.1, |
| seekStep: 5, |
| enableModifiersForNumbers: false, |
| enableVolumeScroll: false, |
| }, |
| }, |
| }, |
| function () { |
| player = this; |
| player.on("error", (e) => { |
| console.error(e); |
| butterup.toast({ |
| message: "Something went wrong", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| }); |
| } |
| ); |
| player.doubleTapFF(); |
|
|
| document.querySelector("#video-player").style.display = "block"; |
| |
| setTimeout(() => { |
| window.scrollTo({ |
| top: document.body.scrollHeight, |
| behavior: "smooth", |
| }); |
|
|
| if (videoUrls.length > 1) { |
| const videoSelect = document.createElement("select"); |
| videoSelect.setAttribute("id", "video-select"); |
| videoSelect.setAttribute("class", "video-select"); |
| videoSelect.setAttribute("aria-label", "Select video"); |
| videoUrls.forEach((video) => { |
| const option = document.createElement("option"); |
| option.setAttribute("value", video.src); |
| option.innerHTML = video.title; |
| videoSelect.appendChild(option); |
| }); |
| videoSelect.addEventListener("change", (e) => { |
| const selectedSrc = e.target.value; |
| player.src({ |
| src: selectedSrc, |
| type: "video/mp4", |
| }); |
| player.play(); |
| }); |
| document.querySelector("#video-player").appendChild(videoSelect); |
| } |
| player.play() |
| }, 300); |
|
|
| form.querySelector("button[type=submit]").removeAttribute("disabled"); |
| form.querySelector("button[type=submit]").innerHTML = "Play Now"; |
| form.querySelector("button[type=submit]").classList.remove("loader"); |
| document.querySelectorAll("#play-torrent").forEach((el) => { |
| el.removeAttribute("disabled"); |
| el.innerHTML = "Watch"; |
| el.classList.remove("loader"); |
| }); |
| }); |
|
|
| |
| const switchInputs = document.querySelectorAll("#switchInput"); |
| switchInputs.forEach((input) => { |
| input.querySelector("input").addEventListener("change", (e) => { |
| const dot = e.target.parentElement.querySelector(".dot"); |
| const wrapper = e.target.parentElement.querySelector(".switch-wrapper"); |
| if (e.target.checked) { |
| dot.classList.add("translate-x-full", "!bg-muted"); |
| wrapper.classList.add("bg-primary"); |
| } else { |
| dot.classList.remove("translate-x-full", "!bg-muted"); |
| wrapper.classList.remove("bg-primary"); |
| } |
| }); |
| }); |
|
|
| document.querySelector("#settings-btn").addEventListener("click", () => { |
| document.querySelector("#settings-model").classList.toggle("hidden"); |
| }); |
|
|
| document.querySelectorAll("#close-settings").forEach((el) => { |
| el.addEventListener("click", () => { |
| document.querySelector("#settings-model").classList.toggle("hidden"); |
| document.querySelector("#proxy-result").classList.remove("flex"); |
| document.querySelector("#proxy-result").classList.add("hidden"); |
| }); |
| }); |
|
|
| document.querySelectorAll(".tab-btn").forEach((el) => { |
| el.addEventListener("click", () => { |
| const tabIndex = el.getAttribute("data-index"); |
| document.querySelectorAll(".tab").forEach((tab) => { |
| const index = tab.getAttribute("data-tab"); |
| if (index === tabIndex) { |
| tab.classList.remove("hidden"); |
| document.querySelectorAll(".tab-btn").forEach((el) => { |
| el.classList.remove("bg-primary", "text-primary-foreground"); |
| el.classList.add("bg-muted"); |
| }); |
| el.classList.add("bg-primary", "text-primary-foreground"); |
| } else { |
| tab.classList.add("hidden"); |
| } |
| }); |
| }); |
| }); |
|
|
| function generatePagination(currentPage, pageSize, total, target) { |
| const pagination = document.querySelector(target); |
| if (!pagination) return; |
| pagination.classList.remove("hidden"); |
| pagination.innerHTML = ""; |
| const totalPages = Math.ceil(total / pageSize); |
| const startPage = Math.max(1, currentPage - 2); |
| const endPage = Math.min(totalPages, currentPage + 2); |
|
|
| for (let i = startPage; i <= endPage; i++) { |
| const pageButton = document.createElement("button"); |
| pageButton.textContent = i; |
| pageButton.classList.add("page-button"); |
| if (i === currentPage) { |
| pageButton.classList.add("active"); |
| } |
| pageButton.addEventListener("click", () => { |
| searchPage = i; |
| updateSearchResults(); |
| }); |
| pagination.appendChild(pageButton); |
| } |
| const prevButton = document.createElement("button"); |
| prevButton.innerHTML = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.72 9.47a.75.75 0 0 0 0 1.06l4.25 4.25a.75.75 0 1 0 1.06-1.06L6.31 10l3.72-3.72a.75.75 0 1 0-1.06-1.06L4.72 9.47Zm9.25-4.25L9.72 9.47a.75.75 0 0 0 0 1.06l4.25 4.25a.75.75 0 1 0 1.06-1.06L11.31 10l3.72-3.72a.75.75 0 0 0-1.06-1.06Z" clip-rule="evenodd"></path></svg>`; |
| prevButton.classList.add("page-button"); |
| prevButton.disabled = currentPage === 1; |
| prevButton.addEventListener("click", () => { |
| if (currentPage > 1) { |
| searchPage--; |
| updateSearchResults(); |
| } |
| }); |
| pagination.prepend(prevButton); |
| const nextButton = document.createElement("button"); |
| nextButton.innerHTML = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 20 20" aria-hidden="true" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.28 9.47a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 1 1-1.06-1.06L13.69 10 9.97 6.28a.75.75 0 0 1 1.06-1.06l4.25 4.25ZM6.03 5.22l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L8.69 10 4.97 6.28a.75.75 0 0 1 1.06-1.06Z" clip-rule="evenodd"></path></svg>`; |
| nextButton.classList.add("page-button"); |
| nextButton.disabled = currentPage === totalPages; |
| nextButton.addEventListener("click", () => { |
| if (currentPage < totalPages) { |
| searchPage++; |
| updateSearchResults(); |
| } |
| }); |
| pagination.appendChild(nextButton); |
| } |
|
|
| let searchData = []; |
| let searchPage = 1; |
| let searchPageSize = 5; |
|
|
| const updateSearchResults = () => { |
| const searchPagination = document.querySelector("#search-pagination"); |
| const searchResults = document.querySelector("#search-result"); |
| searchResults.classList.remove("hidden"); |
| searchResults.querySelector("tbody").innerHTML = ""; |
| searchResults.querySelector("tfoot").classList.add("hidden"); |
| if (searchData.length === 0) { |
| searchResults.querySelector("tfoot").classList.remove("hidden"); |
| return; |
| } |
|
|
| const start = (searchPage - 1) * searchPageSize; |
| const end = start + searchPageSize; |
| const results = searchData.slice(start, end); |
| results.forEach((result) => { |
| const resultDiv = document.createElement("tr"); |
| resultDiv.innerHTML = ` |
| <td>${result.title}</td> |
| <td>${result.indexer}</td> |
| <td>${result.size}</td> |
| <td>${result.leechers}/${result.seeders}</td> |
| <td><button id="play-torrent" type="button" class="btn small" data-magnet="${ |
| result.downloadUrl || result.magnetUrl |
| }">Watch</button></td> |
| `; |
| searchResults.querySelector("tbody").appendChild(resultDiv); |
| }); |
|
|
| |
| const totalResults = searchData.length; |
| const totalPages = Math.ceil(totalResults / searchPageSize); |
| generatePagination( |
| searchPage, |
| searchPageSize, |
| totalResults, |
| "#search-pagination" |
| ); |
|
|
| |
| searchResults.querySelectorAll("#play-torrent").forEach((el) => { |
| el.addEventListener("click", async (e) => { |
| const magnet = e.target.getAttribute("data-magnet"); |
| document.querySelector("#magnet").value = magnet; |
| document |
| .querySelector("#torrent-form") |
| .dispatchEvent(new Event("submit")); |
| e.target.setAttribute("disabled", "disabled"); |
| e.target.innerHTML = ""; |
| e.target.classList.add("loader"); |
| }); |
| }); |
| }; |
|
|
| document.querySelector("#search-form").addEventListener("submit", (e) => { |
| e.preventDefault(); |
| const query = e.target.querySelector("#search").value; |
| if (!query) { |
| butterup.toast({ |
| message: "Please enter a search query", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| return; |
| } |
|
|
| searchData = []; |
| searchPage = 1; |
|
|
| e.target |
| .querySelector("button[type=submit]") |
| .setAttribute("disabled", "disabled"); |
| e.target.querySelector("button[type=submit]").classList.add("loader"); |
| e.target.querySelector("button[type=submit]").innerHTML = ""; |
| const searchResults = document.querySelector("#search-result"); |
|
|
| searchResults.classList.add("hidden"); |
| document.querySelector("#search-pagination").classList.add("hidden"); |
|
|
| let apiUrl = "/api/v1/prowlarr/search"; |
|
|
| if ( |
| (!settings.prowlarrHost || !settings.prowlarrApiKey) && |
| settings.jackettHost && |
| settings.jackettApiKey |
| ) { |
| apiUrl = "/api/v1/jackett/search"; |
| } |
|
|
| fetch(`${apiUrl}?q=${query}`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| }) |
| .then(async (res) => { |
| if (!res.ok) { |
| const err = await res.json(); |
| throw new Error(res.error || "Failed to fetch search results"); |
| } |
| return res.json(); |
| }) |
| .then((data) => { |
| if (data && typeof data === "object") { |
| searchData = data; |
| } else { |
| searchData = []; |
| } |
|
|
| updateSearchResults(); |
| }) |
| .catch((error) => { |
| console.error("There was a problem with the fetch operation:", error); |
| butterup.toast({ |
| message: error.message || "Failed to fetch search results", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| }) |
| .finally(() => { |
| e.target |
| .querySelector("button[type=submit]") |
| .removeAttribute("disabled"); |
| e.target |
| .querySelector("button[type=submit]") |
| .classList.remove("loader"); |
| e.target.querySelector("button[type=submit]").innerHTML = "Search"; |
| }); |
| }); |
|
|
| const testProwlarrConfig = async () => { |
| const prowlarrHost = document.querySelector("#prowlarrHost").value; |
| const prowlarrApiKey = document.querySelector("#prowlarrApiKey").value; |
| const prowlarrTestBtn = document.querySelector("#test-prowlarr"); |
|
|
| if (!prowlarrHost || !prowlarrApiKey) { |
| butterup.toast({ |
| message: "Please enter Prowlarr host and API key", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| return false; |
| } |
|
|
| prowlarrTestBtn.setAttribute("disabled", "disabled"); |
| prowlarrTestBtn.querySelector("span").innerHTML = "Testing..."; |
| |
| const response = await fetch("/api/v1/prowlarr/test", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ prowlarrHost, prowlarrApiKey }), |
| }); |
|
|
| const data = await response.json(); |
| if (!response.ok) { |
| butterup.toast({ |
| message: data.error || "Failed to test Prowlarr connection", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| prowlarrTestBtn.removeAttribute("disabled"); |
| prowlarrTestBtn.querySelector("span").innerHTML = "Test Connection"; |
| return false; |
| } |
|
|
| butterup.toast({ |
| message: "Prowlarr settings are valid", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "success", |
| }); |
|
|
| prowlarrTestBtn.removeAttribute("disabled"); |
| prowlarrTestBtn.querySelector("span").innerHTML = "Test Connection"; |
|
|
| return true; |
| } |
|
|
| document.querySelector("#test-prowlarr").addEventListener("click", (e) => { |
| testProwlarrConfig(); |
| }); |
|
|
| const testJackettConfig = async () => { |
| const jackettHost = document.querySelector("#jackettHost").value; |
| const jackettApiKey = document.querySelector("#jackettApiKey").value; |
| const jackettTestBtn = document.querySelector("#test-jackett"); |
|
|
| if (!jackettHost || !jackettApiKey) { |
| butterup.toast({ |
| message: "Please enter Jackett host and API key", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| return false; |
| } |
|
|
| jackettTestBtn.setAttribute("disabled", "disabled"); |
| jackettTestBtn.querySelector("span").innerHTML = "Testing..."; |
| |
| const response = await fetch("/api/v1/jackett/test", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ jackettHost, jackettApiKey }), |
| }); |
|
|
| const data = await response.json(); |
| if (!response.ok) { |
| butterup.toast({ |
| message: data.error || "Failed to test Jackett connection", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| jackettTestBtn.removeAttribute("disabled"); |
| jackettTestBtn.querySelector("span").innerHTML = "Test Connection"; |
| return false; |
| } |
|
|
| butterup.toast({ |
| message: "Jackett settings are valid", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "success", |
| }); |
|
|
| jackettTestBtn.removeAttribute("disabled"); |
| jackettTestBtn.querySelector("span").innerHTML = "Test Connection"; |
|
|
| return true; |
| } |
|
|
| document.querySelector("#test-jackett").addEventListener("click", (e) => { |
| testJackettConfig(); |
| }); |
|
|
| const testProxy = async () => { |
| const proxyUrl = document.querySelector("#proxyUrl").value; |
| const proxyBtn = document.querySelector("#test-proxy"); |
|
|
| if (!proxyUrl) { |
| butterup.toast({ |
| message: "Please enter a proxy URL", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| return false; |
| } |
|
|
| proxyBtn.setAttribute("disabled", "disabled"); |
| proxyBtn.querySelector("span").innerHTML = "Testing..."; |
|
|
| const response = await fetch("/api/v1/proxy/test", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ proxyUrl }), |
| }); |
|
|
| const data = await response.json(); |
|
|
| if (!response.ok) { |
| butterup.toast({ |
| message: data.error || "Failed to test Proxy connection", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| proxyBtn.removeAttribute("disabled"); |
| proxyBtn.querySelector("span").innerHTML = "Test Proxy"; |
| return false; |
| } |
|
|
| butterup.toast({ |
| message: "Proxy url is valid", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "success", |
| }); |
|
|
| proxyBtn.removeAttribute("disabled"); |
| proxyBtn.querySelector("span").innerHTML = "Test Proxy"; |
|
|
| if (data?.origin) { |
| document.querySelector("#proxy-result").classList.remove("hidden"); |
| document.querySelector("#proxy-result").classList.add("flex"); |
| document.querySelector("#proxy-result .output-ip").innerHTML = data?.origin |
| } |
|
|
| return true; |
| } |
|
|
| document.querySelector("#test-proxy").addEventListener("click", () => { |
| testProxy(); |
| }); |
|
|
| document |
| .querySelector("#proxy-settings-form") |
| .addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| const enableProxy = e.target.querySelector("#enableProxy").checked; |
| const proxyUrl = e.target.querySelector("#proxyUrl").value; |
| const submitButton = e.target.querySelector("button[type=submit]"); |
|
|
| submitButton.setAttribute("disabled", "disabled"); |
|
|
| if (enableProxy) { |
| const isValid = await testProxy(); |
| if (!isValid) { |
| submitButton.removeAttribute("disabled"); |
| return; |
| } |
| } |
|
|
| submitButton.classList.add("loader"); |
| submitButton.innerHTML = "Saving..."; |
|
|
| const body = { |
| enableProxy, |
| proxyUrl, |
| }; |
|
|
| const response = await fetch("/api/v1/settings/proxy", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(body), |
| }) |
|
|
| const data = await response.json(); |
|
|
| if (!response.ok) { |
| butterup.toast({ |
| message: data.error || "Failed to save settings", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| } else { |
| butterup.toast({ |
| message: "Proxy settings saved successfully", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "success", |
| }); |
|
|
| settings = { |
| ...settings, |
| enableProxy: body.enableProxy, |
| proxyUrl: body.proxyUrl, |
| }; |
| } |
|
|
| submitButton.removeAttribute("disabled"); |
| submitButton.classList.remove("loader"); |
| submitButton.innerHTML = "Save Settings"; |
| }); |
|
|
| document |
| .querySelector("#prowlarr-settings-form") |
| .addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| const enableProwlarr = e.target.querySelector("#enableProwlarr").checked; |
| const prowlarrHost = e.target.querySelector("#prowlarrHost").value; |
| const prowlarrApiKey = e.target.querySelector("#prowlarrApiKey").value; |
| const submitButton = e.target.querySelector("button[type=submit]"); |
|
|
| submitButton.setAttribute("disabled", "disabled"); |
|
|
| if (enableProwlarr) { |
| const isValid = await testProwlarrConfig(); |
| if (!isValid) { |
| submitButton.removeAttribute("disabled"); |
| return; |
| } |
| } |
|
|
| submitButton.classList.add("loader"); |
| submitButton.innerHTML = "Saving..."; |
|
|
| const body = { |
| enableProwlarr, |
| prowlarrHost, |
| prowlarrApiKey, |
| }; |
|
|
| const response = await fetch("/api/v1/settings/prowlarr", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(body), |
| }) |
|
|
| const data = await response.json(); |
| if (!response.ok) { |
| butterup.toast({ |
| message: data.error || "Failed to save settings", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| } else { |
| butterup.toast({ |
| message: "Prowlarr settings saved successfully", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "success", |
| }); |
|
|
| settings = { |
| ...settings, |
| enableProwlarr: body.enableProwlarr, |
| prowlarrHost: body.prowlarrHost, |
| prowlarrApiKey: body.prowlarrApiKey, |
| }; |
|
|
| |
| if (body?.enableProwlarr || settings?.enableJackett) { |
| searchWrapper.classList.remove("hidden"); |
| } else { |
| searchWrapper.classList.add("hidden"); |
| } |
| } |
|
|
| submitButton.removeAttribute("disabled"); |
| submitButton.classList.remove("loader"); |
| submitButton.innerHTML = "Save Settings"; |
| }); |
|
|
| document |
| .querySelector("#jackett-settings-form") |
| .addEventListener("submit", async (e) => { |
| e.preventDefault(); |
| const enableJackett = e.target.querySelector("#enableJackett").checked; |
| const jackettHost = e.target.querySelector("#jackettHost").value; |
| const jackettApiKey = e.target.querySelector("#jackettApiKey").value; |
| const submitButton = e.target.querySelector("button[type=submit]"); |
|
|
| submitButton.setAttribute("disabled", "disabled"); |
|
|
| if (enableJackett) { |
| const isValid = await testJackettConfig(); |
| if (!isValid) { |
| submitButton.removeAttribute("disabled"); |
| return; |
| } |
| } |
|
|
| submitButton.classList.add("loader"); |
| submitButton.innerHTML = "Saving..."; |
|
|
| const body = { |
| enableJackett, |
| jackettHost, |
| jackettApiKey, |
| }; |
|
|
| const response = await fetch("/api/v1/settings/jackett", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify(body), |
| }) |
|
|
| const data = await response.json(); |
| if (!response.ok) { |
| butterup.toast({ |
| message: data.error || "Failed to save settings", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| } else { |
| butterup.toast({ |
| message: "Jackett settings saved successfully", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "success", |
| }); |
|
|
| settings = { |
| ...settings, |
| enableJackett: body.enableJackett, |
| jackettHost: body.jackettHost, |
| jackettApiKey: body.jackettApiKey, |
| }; |
|
|
| |
| if (body?.enableJackett || settings?.enableJackett) { |
| searchWrapper.classList.remove("hidden"); |
| } else { |
| searchWrapper.classList.add("hidden"); |
| } |
| } |
|
|
| submitButton.removeAttribute("disabled"); |
| submitButton.classList.remove("loader"); |
| submitButton.innerHTML = "Save Settings"; |
| }); |
|
|
| document.querySelector("#torrent_file").addEventListener("change", (e) => { |
| const file = e.target.files[0]; |
| if (file) { |
| const formData = new FormData(); |
| formData.append("torrent", file); |
|
|
| fetch("/api/v1/torrent/convert", { |
| method: "POST", |
| body: formData, |
| }) |
| .then(async (res) => { |
| if (!res.ok) { |
| const err = await res.json(); |
| throw new Error(err.error || "Failed to upload torrent file"); |
| } |
| return res.json(); |
| }) |
| .then((data) => { |
| document.querySelector("#magnet").value = data.magnet; |
| document |
| .querySelector("#torrent-form") |
| .dispatchEvent(new Event("submit")); |
| }) |
| .catch((error) => { |
| console.error("There was a problem with the fetch operation:", error); |
| butterup.toast({ |
| message: error.message || "Failed to upload torrent file", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| }); |
| } |
| }); |
|
|
| const torrentFileWrapper = document.querySelector("#torrent_file_wrapper"); |
| torrentFileWrapper.addEventListener("dragenter", (e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| torrentFileWrapper.classList.add("drag-over"); |
| }); |
| torrentFileWrapper.addEventListener("dragover", (e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| }); |
| torrentFileWrapper.addEventListener("dragleave", (e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| torrentFileWrapper.classList.remove("drag-over"); |
| }); |
| torrentFileWrapper.addEventListener("drop", (e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| torrentFileWrapper.classList.remove("drag-over"); |
| const files = e.dataTransfer.files; |
| if (files.length > 0) { |
| const file = files[0]; |
| if (file.name.endsWith(".torrent")) { |
| const formData = new FormData(); |
| formData.append("torrent", file); |
|
|
| fetch("/api/v1/torrent/convert", { |
| method: "POST", |
| body: formData, |
| }) |
| .then(async (res) => { |
| if (!res.ok) { |
| const err = await res.json(); |
| throw new Error(err.error || "Failed to upload torrent file"); |
| } |
| return res.json(); |
| }) |
| .then((data) => { |
| document.querySelector("#magnet").value = data.magnet; |
| document |
| .querySelector("#torrent-form") |
| .dispatchEvent(new Event("submit")); |
| }) |
| .catch((error) => { |
| console.error( |
| "There was a problem with the fetch operation:", |
| error |
| ); |
| butterup.toast({ |
| message: error.message || "Failed to upload torrent file", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| }); |
| } else { |
| butterup.toast({ |
| message: "Please drop a valid torrent file", |
| location: "top-right", |
| icon: true, |
| dismissable: true, |
| type: "error", |
| }); |
| } |
| } |
| }); |
|
|
| |
| fetch("/api/v1/settings") |
| .then((res) => { |
| if (!res.ok) { |
| throw new Error("Network response was not ok"); |
| } |
| return res.json(); |
| }) |
| .then((data) => { |
| settings = data; |
| document.querySelector("#enableProxy").checked = data.enableProxy; |
| document.querySelector("#proxyUrl").value = data.proxyUrl || ""; |
| document.querySelector("#enableProwlarr").checked = |
| data.enableProwlarr || false; |
| document.querySelector("#prowlarrHost").value = data.prowlarrHost || ""; |
| document.querySelector("#prowlarrApiKey").value = |
| data.prowlarrApiKey || ""; |
| document.querySelector("#enableJackett").checked = |
| data.enableJackett || false; |
| document.querySelector("#jackettHost").value = data.jackettHost || ""; |
| document.querySelector("#jackettApiKey").value = data.jackettApiKey || ""; |
|
|
| |
| const switchInputs = document.querySelectorAll("#switchInput"); |
| switchInputs.forEach((input) => { |
| const dot = input.querySelector(".dot"); |
| const wrapper = input.querySelector(".switch-wrapper"); |
| if (input.querySelector("input").checked) { |
| dot.classList.add("translate-x-full", "!bg-muted"); |
| wrapper.classList.add("bg-primary"); |
| } else { |
| dot.classList.remove("translate-x-full", "!bg-muted"); |
| wrapper.classList.remove("bg-primary"); |
| } |
| }); |
|
|
| |
| if (data?.enableProwlarr || data?.enableJackett) { |
| searchWrapper.classList.remove("hidden"); |
| } else { |
| searchWrapper.classList.add("hidden"); |
| } |
| }) |
| .catch((error) => { |
| console.error("There was a problem with the fetch operation:", error); |
| }); |
| })(); |
|
|