您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Minimizing page elements to fit smaller screens and adding some usability improvements.
// ==UserScript== // @name Advanced Streaming | aniworld.to & s.to // @name:de Erweitertes Streaming | aniworld.to & s.to // @namespace https://gf.qytechs.cn/users/928242 // @version 3.6.6 // @description Minimizing page elements to fit smaller screens and adding some usability improvements. // @description:de Minimierung der Seitenelemente zur Anpassung an kleinere Bildschirme und Verbesserung der Benutzerfreundlichkeit. // @author Kamikaze (https://github.com/Kamiikaze) // @supportURL https://github.com/Kamiikaze/Tampermonkey/issues // @iconURL https://s.to/favicon.ico // @match https://s.to/serie/stream/* // @match https://s.to/serienkalender* // @match https://s.to/serien* // @match https://s.to/genre* // @match https://s.to/account/subscribed // @match https://s.to/account/watchlist* // @match https://aniworld.to/anime/stream/* // @match https://aniworld.to/animekalender* // @match https://aniworld.to/animes* // @match https://aniworld.to/genre* // @match https://aniworld.to/account/subscribed // @match https://aniworld.to/account/watchlist* // @require https://gf.qytechs.cn/scripts/455253-kamikaze-script-utils/code/Kamikaze'%20Script%20Utils.js // @require https://cdnjs.cloudflare.com/ajax/libs/toastify-js/1.12.0/toastify.min.js // @resource toastifyCss https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css // @license MIT // @grant GM_getResourceText // @grant GM_addStyle // ==/UserScript== // Load Toastify CSS // # # # # # # // CONFIG // You can disable features by replacing the value true with false. // # # # # # # // Enables shorter Window Tab Title // Example: S3E8 - Black Clover | AniWorld.to const enableShortWindowTitle = true; // Hides the section of Season Suggestions below the video const enableHideSeasonSuggestions = true; // Closing the dropdown menu when mouse leaves (fix the perma-open menu) const enableCloseMenuOnHoverLeave = true; // Adding a Link below "Watch Trailer" to search for it on YT (Because sometimes there is a Homepage linked to the Anime) const enableAddTrailerSearchLink = true; // Adding a small box at bottom left to search the Anime on sites like MyAnimeList, Crunchyroll & more const enableAddAnimeSearchBox = true; // Enable/Disable search providers by changing the value either to true or false // If you want to add your own provider let me know const animeSearchProviderList = { Crunchyroll: false, aniSearch: false, AnimePlanet: false, Kitsu: true, MyAnimeList: true, "Amazon Video": true, }; // Adding a small box at bottom left to search the Series on sites like Amazon, Netflix & more const enableAddSeriesSearchBox = true; // Enable/Disable search providers by changing the value either to true or false // If you want to add your own provider let me know const seriesSearchProviderList = { "Amazon Video": true, Netflix: true, }; // Adding a small button at the right corner of the video frame to get to the next episode const enableEpisodeNavButtons = true; // Allows filtering the Series Calendar by subscribed series // To use this feature you need to go to https://s.to/account/subscribed and wait for the script to save the // subscribed series. After that you can go to https://s.to/serienkalender and use the filter. const enableFilterSeriesCalendar = true; // Adds a link to search series in the release calendar const enableAddCalendarSearch = true; // Enable improved Search Box // When pressing a key, search box will be automatically focused. Clicking the search box will select all input. // By clicking outside the search box and pressing a key, the search box will be focused and cleared for new input. const enableImprovedSearchBox = true; // Enables Notebox (Beta) // Allows you to save notes to each Series/Animes const enableNoteBox = false; // # # # # # # // Styling // Some adjustments to layout. // You can disable features by replacing the value true with false. // # # # # # # // Set the height of the video player. (in pixel) // Set to 0 to disabled it. Default: 480 const reducePlayerHeight = 150; // Hides the text to show/edit the description of the episode below episode title const hideDescriptionEdit = true; // Hides the language box above the video player const hideLanguageBox = true; // Hides seen episodes (marked green) from the Episode-List (You can still see them in the season overview const hideSeenEpisodes = true; // Use Scrollbar for Episode-List (good for seasons with a large amount of episodes) const useScrollbarForEpisodeList = true; /*** DO NOT CHANGE BELOW ***/ /* global Logger getStreamData waitForElm addGlobalStyle searchSeries GM_getResourceText */ const log = new Logger("Advanced Streaming"); let streamData = null; let streamDetails = null; (async () => { generateStyles(); await getSubscribedSeries(); hideSeen(); sortWatchlist(); if (enableFilterSeriesCalendar) filterSeriesCalendar(); if (enableImprovedSearchBox) improvedSearchBox(); streamData = await getStreamData(); // streamDetails = await getStreamDetails() await toggleSubscribedSeries(); if (hideSeenEpisodes) { if (streamData.currentEpisode !== 0) { addGlobalStyle(` #stream > ul:nth-child(4) li .seen { display: none; } `); } } /** { "host": "aniworld.to", "title": "Komi Can’t Communicate", "currentSeason": 2, "seasonsCount": 2, "currentEpisode": 1, "episodesCount": 12, "episodeTitle": { "de": "Es ist nur der Winteranfang. Und mehr.", "en": "It's just the arrival of winter. Plus more." }, "hasMovies": false } **/ console.log("streamData:", streamData); /** { "title": "Komi Can’t Communicate", "seasonsCount": 2, "episodesCount": 12, "episodeTitle": { "de": "Es ist nur der Winteranfang. Und mehr.", "en": "It's just the arrival of winter. Plus more." }, "hasMovies": false } **/ console.log("streamDetails:", streamDetails); // Features if (enableShortWindowTitle) shortWindowTitle(); if (enableHideSeasonSuggestions) hideSeasonSuggestions(); if (enableCloseMenuOnHoverLeave) closeMenuOnHoverLeave(); if (enableAddTrailerSearchLink) addTrailerSearchLink(); if (enableAddAnimeSearchBox) addAnimeSearchBox(); if (enableAddSeriesSearchBox) addSeriesSearchBox(); if (enableEpisodeNavButtons) addEpisodeNavButtons(); if (enableAddCalendarSearch) addCalendarSearch(); if (enableNoteBox) addNotesBox(); fixAnimeTrailerWatchButton(); })(); function hideSeen() { const subscribedSeries = localStorage.subscribedSeries; let animeList = document.querySelector(".seriesListContainer"); if (!animeList || !subscribedSeries) return; animeList = animeList.children; for (let i = 0; i < animeList.length; i++) { let anime = animeList[i]; let title = anime.querySelector("h3")?.innerText; if (subscribedSeries.includes(title)) { log.debug(title, "found"); anime.querySelector("a").classList.add("subbed"); } } addGlobalStyle(` .seriesListContainer a.subbed {filter: blur(1px) grayscale(1) opacity(0.5);} .seriesListContainer a.subbed:hover {filter: unset;} .seriesListContainer>div>a:hover h3 {white-space: break-spaces;} `); } async function addNotesBox() { //const container = await waitForElm("#series > section > div.container.row") const container = document.querySelector( "#series > section > div.container.row", ); const notesVisible = localStorage.getItem(`notes-visible`) === "true"; console.error("notesVisible", notesVisible); if (!container) return; const notesEl = document.createElement("div"); notesEl.id = "notes-box"; notesEl.innerHTML = ` <div id="notes-toolbar"> <button id="notes-save">Save Notes</button> <button id="notes-toggle">${notesVisible ? "Show" : "Hide"} Notes</button> </div> <textarea id="notes-text" placeholder="Save Notes for this Anime" class="${notesVisible ? "seen" : "hidden"}" ></textarea> `; container.append(notesEl); addGlobalStyle( ` #notes-box { display: flex; flex-direction: column; position: relative; width: 100%; } #notes-box.hidden { display: none; } #notes-toolbar { display: flex; flex-direction: row; flex-wrap: nowrap; justify-content: flex-end; align-items: center; padding-top: 10px; } #notes-toolbar > button { font-size: 13px; text-align: center; cursor: pointer; display: block; color: #fff; background: #637cf9; border-radius: 3px; padding: 10px; font-weight: 600; text-transform: uppercase; margin-left: 10px; } `, false, ); const title = window.location.pathname.split("/")[3]; const notesText = document.getElementById("notes-text"); notesText.value = localStorage.getItem(`notes-${title}`); const saveBtn = document.getElementById("notes-save"); saveBtn.addEventListener("click", () => { localStorage.setItem(`notes-${title}`, notesText.value); saveBtn.innerHTML = "Saved!"; saveBtn.style.backgroundColor = "green"; setTimeout(() => { saveBtn.innerHTML = "Save Notes"; saveBtn.style.backgroundColor = ""; }, 2000); notify("Notes saved"); }); const toggleBtn = document.getElementById("notes-toggle"); toggleBtn.addEventListener("click", () => { const notes = document.getElementById("notes-text"); const hidden = notes.classList.toggle("hidden"); hidden ? (toggleBtn.innerHTML = "Show Notes") : (toggleBtn.innerHTML = "Hide Notes"); localStorage.setItem(`notes-visible`, !hidden); }); } async function sortWatchlist() { if (!window.location.pathname.includes("watchlist")) return; const nav = await waitForElm(".seriesListNavigation"); nav.innerHTML = nav.innerHTML.replace("oder", "|"); nav.style = `{ display: flex; flex-direction: row; flex-wrap: nowrap; justify-content: flex-start; gap: 10px; }`; const sortByGenre = document.createElement("a"); sortByGenre.href = "/account/watchlist/genre"; sortByGenre.innerText = "Genre"; nav.append(" | "); nav.append(sortByGenre); const sortOrder = window.location.pathname.split("/")[3]; if (!sortOrder) return; const sortReset = document.createElement("a"); sortReset.href = "/account/watchlist"; sortReset.innerText = "Zurücksetzen"; sortReset.style = "color: #cd3e3e;"; nav.append(" | "); nav.append(sortReset); const sortTag = sortOrder === "genre" ? "small" : "h3"; // Select the container const container = await waitForElm(".seriesListContainer"); // Convert HTMLCollection to an array const elementArray = Array.from(container.children); // Sort the array based on the text content of the h3 elements elementArray.sort((a, b) => { const titleA = a.querySelector(sortTag).textContent.trim(); const titleB = b.querySelector(sortTag).textContent.trim(); if (sortOrder === "asc" || sortOrder === "genre") return titleA.localeCompare(titleB); return titleB.localeCompare(titleA); }); // Re-append the sorted elements to the container elementArray.forEach((element) => container.appendChild(element)); } function generateStyles() { const toastifyCss = GM_getResourceText("toastifyCss"); GM_addStyle(toastifyCss); if (reducePlayerHeight > 0) { addGlobalStyle(` .inSiteWebStream, .inSiteWebStream iframe {height: ${reducePlayerHeight}px; } .hosterSiteTitle {padding: 5px 0 10px;} `); } if (hideDescriptionEdit) { addGlobalStyle(` .descriptionSpoilerLink, .descriptionSpoilerPlaceholder, .submitNewDescription, .submitNewTitle, .hosterSectionTitle { display: none; } `); } if (hideLanguageBox) { addGlobalStyle(` .changeLanguageBox { display: none; } `); } if (useScrollbarForEpisodeList) { addGlobalStyle(` #stream > ul:nth-child(4) { overflow-x: auto; display: flex; flex-direction: row; justify-content: flex-start; flex-wrap: nowrap; align-items: center; } #stream > ul:nth-child(4) li:nth-child(1) { position: absolute; } #stream > ul:nth-child(4) > li:nth-child(2) { margin-left: 119px; } /* ===== Scrollbar CSS ===== */ /* Firefox */ * { scrollbar-height: auto; scrollbar-color: #637cf9 #243743; } /* Chrome, Edge, and Safari */ #stream > ul:nth-child(4)::-webkit-scrollbar { height: 10px; } #stream > ul:nth-child(4)::-webkit-scrollbar-track { background: #243743; } #stream > ul:nth-child(4)::-webkit-scrollbar-thumb { background-color: #637cf9; border-radius: 10px; border: 1px solid #ffffff; } `); } // Header Section and Backdrop Image size addGlobalStyle(` section.title { min-height: 450px; } #series .backdrop { height: 100%; } `); // seasonEpisodesList addGlobalStyle(` .seasonEpisodesList .editFunctions a, .seasonEpisodesList td:nth-child(4) a, .seasonEpisodesList .editFunctions { display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; justify-content: center; } .seasonEpisodesList .editFunctions a .flag, .seasonEpisodesList .editFunctions img.flag, .seasonEpisodesList td:nth-child(4) a .icon { margin-right: 2px; } .seasonEpisodesList>tbody>tr>td { padding-right: 15px; } .seasonEpisodesList>tbody>tr>td:nth-child(1) { min-width: 110px; } `); } function shortWindowTitle() { if (!streamData.title) return; let pageTitle = ""; if (streamData.currentSeason > 0) pageTitle += "S" + streamData.currentSeason; if (streamData.currentEpisode > 0) pageTitle += "E" + streamData.currentEpisode; window.document.title = `${pageTitle.length > 1 ? pageTitle + " - " : ""}${streamData.title} | ${streamData.host}`; } async function hideSeasonSuggestions() { if (!window.location.pathname.includes("episode")) return; const container = await waitForElm(".ContentContainerBox"); if (!container) return; container.style = "display: none;"; log.info("Season suggestions hidden"); } async function closeMenuOnHoverLeave() { let menu = await waitForElm(".dd"); menu.replaceWith(menu.cloneNode(true)); menu = await waitForElm(".dd"); const modal = await waitForElm(".modal"); menu.addEventListener("mouseout", () => { modal.style = "display:none"; }); menu.addEventListener("mouseover", () => { modal.style = "display:block"; }); } async function addTrailerSearchLink() { const seriesTitle = streamData.title; const trailerBoxEl = await waitForElm(".add-series .collections"); const ytSearchLink = "https://www.youtube.com/results?search_query="; const searchTrailerEl = document.createElement("li"); searchTrailerEl.classList.add( "col-md-12", "col-sm-12", "col-xs-6", "buttonAction", ); searchTrailerEl.innerHTML = ` <div title="Deutschen Trailer von ${seriesTitle} bei YouTube suchen." itemprop="trailer" itemscope="" itemtype="http://schema.org/VideoObject"> <a itemprop="url" target="_blank" href="${ytSearchLink + seriesTitle} Trailer Deutsch"><i class="fas fa-external-link-alt"></i><span class="collection-name">Trailer suchen</span></a> <meta itemprop="name" content="${seriesTitle} Trailer"> <meta itemprop="description" content="Nach Offiziellen Trailer der TV-Serie ${seriesTitle} bei YouTube suchen."> <meta itemprop="thumbnailUrl" content="https://zrt5351b7er9.static-webarchive.org/img/facebook.jpg"> </div>`; increaseHeaderSize(); addLinkToList(trailerBoxEl, searchTrailerEl); } async function addCalendarSearch() { const seriesTitle = streamData.title; const trailerBoxEl = await waitForElm(".add-series .collections"); const calendarUrl = (() => { if (getStreamPageLocation().host === "s.to") { return "https://s.to/serienkalender?q=" + seriesTitle; } else if (getStreamPageLocation().host === "aniworld.to") { return "https://aniworld.to/animekalender?q=" + seriesTitle; } else { log.error("Host not supported"); } })(); const searchCalendarEl = document.createElement("li"); searchCalendarEl.classList.add( "col-md-12", "col-sm-12", "col-xs-6", "buttonAction", ); searchCalendarEl.innerHTML = ` <div title="Suche ${seriesTitle} im Release Kalender." itemprop="trailer" itemscope="" itemtype="http://schema.org/VideoObject"> <a itemprop="url" target="_blank" href="${calendarUrl}"><i class="fas fa-external-link-alt"></i><span class="collection-name">Im Kalender suchen</span></a> <meta itemprop="name" content="${seriesTitle} Trailer"> <meta itemprop="description" content="Suche ${seriesTitle} im Release Kalender."> <meta itemprop="thumbnailUrl" content="https://zrt5351b7er9.static-webarchive.org/img/facebook.jpg"> </div>`; increaseHeaderSize(); addLinkToList(trailerBoxEl, searchCalendarEl); } async function fixAnimeTrailerWatchButton() { const seriesTitle = streamData.title; const watchButton = await waitForElm(".trailerButton"); if (!watchButton) return; watchButton.style.display = "none"; if (!watchButton) return; const trailerBoxEl = await waitForElm(".add-series .collections"); const watchTrailerPlaceholder = trailerBoxEl.querySelector(`li:nth-child(3)`); watchTrailerPlaceholder.removeChild(watchTrailerPlaceholder.children[0]); const watchTrailerEl = document.createElement("div"); watchTrailerEl.innerHTML = ` <div title="Trailer von ${seriesTitle} ansehen." itemprop="trailer" itemscope="" itemtype="http://schema.org/VideoObject"> <a itemprop="url" target="_blank" href="${watchButton.href}"><i class="fas fa-external-link-alt"></i><span class="collection-name">Anime-Trailer</span></a> <meta itemprop="name" content="${seriesTitle} Trailer"> <meta itemprop="description" content="Offiziellen Trailer der TV-Serie ${seriesTitle} jetzt ansehen."> <meta itemprop="thumbnailUrl" content="https://zrt5351b7er9.static-webarchive.org/img/facebook.jpg"> </div>`; watchTrailerPlaceholder.append(watchTrailerEl); } function addLinkToList(parent, el) { if (!parent) return; const beforeElement = parent.querySelector( `li:nth-child(${parent.childElementCount})`, ); parent.insertBefore(el, beforeElement); } async function increaseHeaderSize() { /** * @type {HTMLElement} */ const header = await waitForElm("section.title"); if (!header) return; const headerHeight = header.offsetHeight; if (headerHeight === 0) { log.debug("Header is not visible. Waiting for header to be visible"); const observer = new MutationObserver(() => { if (header.offsetHeight > 0) { log.info("Header is visible. Increasing Header height"); setTimeout(() => { increaseHeaderSize(); }, 500); observer.disconnect(); } }); observer.observe(header, { attributes: true, attributeFilter: ["style"] }); } } async function addAnimeSearchBox() { if (window.location.hostname !== "aniworld.to") return; const rightColEl = await waitForElm(".add-series"); const seriesTitel = streamData.title; const searchBoxEl = document.createElement("div"); searchBoxEl.classList.add("anime-search"); const searchBoxTitel = document.createElement("p"); searchBoxTitel.innerText = "Anime suchen auf:"; if (!rightColEl) return; rightColEl.append(searchBoxEl); searchBoxEl.append(searchBoxTitel); const sites = [ { domain: "crunchyroll.com", searchUrl: "https://www.crunchyroll.com/de/search?q=#TITEL#", name: "Crunchyroll", }, { domain: "anisearch.de", searchUrl: "https://www.anisearch.de/anime/index?text=#TITEL#", name: "aniSearch", }, { domain: "anime-planet.com", searchUrl: "https://www.anime-planet.com/anime/all?name=#TITEL#", name: "AnimePlanet", }, { domain: "kitsu.io", searchUrl: "https://kitsu.io/anime?text=#TITEL#", name: "Kitsu", }, { domain: "myanimelist.net", searchUrl: "https://myanimelist.net/anime.php?q=#TITEL#&cat=anime", name: "MyAnimeList", }, { domain: "amazon.de", searchUrl: "https://www.amazon.de/s?k=#TITEL#&i=instant-video", name: "Amazon Video", }, ]; for (let i = 0; i < sites.length; i++) { const site = sites[i]; if (animeSearchProviderList[site.name]) { const siteElement = document.createElement("a"); siteElement.classList.add("sites"); siteElement.target = "_blank"; siteElement.href = site.searchUrl.replace("#TITEL#", seriesTitel); siteElement.innerHTML = `<img src="https://www.google.com/s2/favicons?sz=64&domain=${site.domain}" alt='${site.name} Logo Icon' />` + site.name; searchBoxEl.append(siteElement); } } } async function addSeriesSearchBox() { if (window.location.hostname !== "s.to") return; const rightColEl = await waitForElm(".add-series"); const seriesTitel = streamData.title; const searchBoxEl = document.createElement("div"); searchBoxEl.classList.add("anime-search"); const searchBoxTitel = document.createElement("p"); searchBoxTitel.innerText = "Serie suchen auf:"; rightColEl.append(searchBoxEl); searchBoxEl.append(searchBoxTitel); const sites = [ { domain: "amazon.de", searchUrl: "https://www.amazon.de/s?k=#TITEL#&i=instant-video", name: "Amazon Video", }, { domain: "netflix.com", searchUrl: "https://www.netflix.com/search?q=#TITEL#", name: "Netflix", }, ]; for (let i = 0; i < sites.length; i++) { const site = sites[i]; if (seriesSearchProviderList[site.name]) { const siteElement = document.createElement("a"); siteElement.classList.add("sites"); siteElement.target = "_blank"; siteElement.href = site.searchUrl.replace("#TITEL#", seriesTitel); siteElement.innerHTML = `<img src="https://www.google.com/s2/favicons?sz=64&domain=${site.domain}" alt='${site.name} Logo Icon' />` + site.name; searchBoxEl.append(siteElement); } } } addGlobalStyle(` .anime-search { display: flex; flex-direction: column; flex-wrap: nowrap; margin: 15px 5px; background: #313d4f; padding: 15px; border-radius: 3px; width: fit-content; position: fixed; left: 0; bottom: -8px; z-index: 99; } .anime-search .sites { padding: 5px 0; } .anime-search .sites img { max-width: 32px; width: 16px; margin-right: 5px; border-radius: 16px; } `); async function addEpisodeNavButtons() { if (!window.location.pathname.includes("episode")) return; const episodeControls = document.createElement("div"); episodeControls.id = "episodeControls"; const nextBtn = document.createElement("button"); nextBtn.classList.add("nextBtn"); nextBtn.innerText = "Next"; const currentSeason = streamData.currentSeason; const currentEpisode = streamData.currentEpisode; const maxSeasons = streamData.seasonsCount; const maxEpisodes = streamData.episodesCount; nextBtn.addEventListener("click", function () { nextEpisode(currentSeason, currentEpisode, maxSeasons, maxEpisodes); }); episodeControls.append(nextBtn); const videoContainer = await waitForElm(".hosterSiteVideo"); videoContainer.insertBefore( episodeControls, videoContainer.querySelector(".inSiteWebStream"), ); } function nextEpisode(currSeason, currEpisode, maxSeasons, maxEpisodes) { let nextEpisode = currEpisode + 1; let nextSeason = currSeason; log.debug({ currSeason, currEpisode, maxSeasons, maxEpisodes, nextEpisode, nextSeason, }); if (nextEpisode <= maxEpisodes) { log.info("Next Episode", nextEpisode); } if (nextEpisode > maxEpisodes) { nextSeason++; if (nextSeason <= maxSeasons) { log.info("Next Season", nextSeason); nextEpisode = 1; log.info("Next Episode", nextEpisode); } if (nextSeason > maxSeasons) { nextEpisode = false; notify("Last Episode of Last Season", undefined, "error"); } } if (!nextEpisode) { notify("Episode not found", undefined, "error"); return; } window.location.pathname = window.location.pathname.split("/").slice(0, 4).join("/") + `/staffel-${nextSeason}/episode-${nextEpisode}`; } addGlobalStyle( ` #episodeControls { width: 100%; height: 50px; display: flex; flex-direction: row; flex-wrap: nowrap; align-content: center; justify-content: flex-end; align-items: center; margin: 10px 0; } #episodeControls button { width: 120px; height: fit-content; position: relative; padding: 10px 20px; background: #4160f9; color: #fff; font-size: 13px; border: none; border-radius: 6px; cursor: pointer; } .nextBtn::after { content: ">"; padding-left: 10px; } `, false, ); async function filterSeriesCalendar() { if (!window.location.pathname.includes("kalender")) return; log.info("Calendar Filter enabled"); await getSubscribedSeries(); let onlySubbedEpisodes = false; const container = await waitForElm("#seriesContainer"); if (!container) throw new Error("Could not find seriesContainer"); const filterToggleContainer = document.createElement("div"); filterToggleContainer.id = "filterToggleContainer"; const filterToggle = document.createElement("button"); filterToggle.innerText = "Zeige nur Abonnierte Serien"; filterToggle.id = "filterToggleButton"; filterToggle.classList.add("button", "blue", "small"); filterToggle.addEventListener("click", function () { toggleAiringEpisodes() .then(() => { onlySubbedEpisodes = !onlySubbedEpisodes; filterToggle.innerText = onlySubbedEpisodes ? "Zeige alle Serien" : "Zeige nur Abonnierte Serien"; }) .catch((error) => { log.error(`An error occurred while toggling airing episodes: ${error}`); }); }); filterToggleContainer.prepend(filterToggle); container.prepend(filterToggleContainer); } async function toggleSubscribedSeries() { const subButton = await waitForElm(".series-add ul > li:nth-child(1)"); if (!subButton) return; subButton.addEventListener("click", () => { const isSubbed = subButton.classList.contains("true"); const subscribesSeries = JSON.parse( localStorage.getItem("subscribedSeries"), ); const title = streamData.title.trim(); if (isSubbed) { const index = subscribesSeries.indexOf(title); if (index === -1) return; subscribesSeries.splice(index, 1); } else { subscribesSeries.push(title); } localStorage.setItem("subscribedSeries", JSON.stringify(subscribesSeries)); }); } async function getSubscribedSeries() { if (!window.location.href.includes("subscribed")) return; log.info("Getting subscribed series..."); const container = await waitForElm(".seriesListContainer"); if (!container) throw new Error("Could not find seriesListContainer"); const subscsribedTitles = container.querySelectorAll("h3"); const titles = Array.from(subscsribedTitles).map( (title) => title.textContent?.trim() || "", ); if (titles.length > 0) { log.debug(`Found ${titles.length} subscribed series.`); localStorage.setItem("subscribedSeries", JSON.stringify(titles)); log.info(`Saved ${titles.length} subscribed series.`); notify(`Saved ${titles.length} subscribed series.`); } else { log.warn("No subscribed series found."); notify("No subscribed series found.", undefined, "error"); } return titles; } async function toggleAiringEpisodes() { log.info("Toggling airing episodes..."); const subscribedSeries = localStorage.getItem("subscribedSeries"); log.info(`Subscribed Series: ${subscribedSeries}`); if (!subscribedSeries || subscribedSeries.length === 0) { log.warn("No subscribed series found."); alert(` No subscribed series found. To use this feature you need to go to: https://s.to/account/subscribed and wait for the script to save the subscribed series. After that you can come back and use the filter.`); return; } const containers = document.querySelectorAll(".seriesListContainer"); if (!containers) throw new Error("Could not find seriesListContainer"); log.debug(`Found ${containers.length} containers`); containers.forEach((container) => { const episodes = container.querySelectorAll("div"); log.debug(`Found ${episodes.length} episodes`); episodes.forEach((episode) => { const title = episode.querySelector("h3")?.innerText; if (title && !subscribedSeries?.includes(title)) { const isHidden = episode.style.display === "none"; log.debug( `Hiding episode ${title} (${isHidden ? "hidden" : "visible"})`, ); if (!isHidden) { episode.style.display = "none"; } else { episode.style.display = "block"; } } }); }); } addGlobalStyle( ` div#filterToggleContainer { display: flex; flex-wrap: nowrap; justify-content: center; align-items: center; padding: 15px 0 0; } `, false, ); async function improvedSearchBox() { if ( !window.location.pathname.includes("animes") && !window.location.pathname.includes("serien") && !window.location.pathname.includes("kalender") ) return; let doNewSearch = false; const searchInput = await waitForElm("input#serInput"); if (!searchInput) return; searchInput.focus(); if (window.location.search.includes("q=")) { const searchQuery = window.location.search.split("q=")[1]; log.info(`Found search query: ${searchQuery}`); searchInput.value = decodeURI(searchQuery) .replaceAll("+", " ") .replaceAll("’", "'"); searchSeries(); // global function } document.addEventListener("keypress", () => { searchInput.focus(); if (doNewSearch) { searchInput.value = ""; doNewSearch = false; } }); searchInput.addEventListener("click", () => { searchInput.select(); }); document.addEventListener("focusout", function (event) { if (event.target.id === "serInput") { doNewSearch = true; } }); // Auto-Open Anime if only 1 result const genreList = document.getElementById("seriesContainer"); const activeGenres = Array.from(genreList.children).filter( (g) => g.style.display !== "none", ); console.log(activeGenres); if (activeGenres.length === 1) { const activeSeries = Array.from( activeGenres[0].querySelector("ul").children, ).filter((g) => g.style.display !== "none"); console.log(activeSeries); if (activeSeries.length === 1) { activeSeries[0].querySelector("a").click(); } } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址