您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Custom Functions for Kamikaze's Scripts
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greatest.deepsurf.us/scripts/455253/1561858/Kamikaze%27%20Script%20Utils.js
// ==UserScript== // @name Kamikaze' Script Utils // @namespace https://greatest.deepsurf.us/users/928242 // @description Custom Functions for Kamikaze's Scripts // @version 1.1.11 // @author Kamikaze (https://github.com/Kamiikaze) // @license MIT // ==/UserScript== /* jshint esversion: 11 */ /* global Toastify */ // https://stackoverflow.com/questions/61964265/getting-error-this-document-requires-trustedhtml-assignment-in-chrome if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) { window.trustedTypes.createPolicy('default', { createHTML: string => string // Optional, only needed for script (url) tags //,createScriptURL: string => string //,createScript: string => string, }); } /** * @description Custom Logger */ class Logger { /** * @param {string} prefix - Prefix for the log output * @param {number} logLevel - 0: disable, 1: info, 2: debug, 3: warn, 4: all */ constructor(prefix, logLevel = 1) { this.prefix = prefix; // Name of Script this.logLevel = logLevel; this.defaultStyle = "background: #44adf3; color: #000; font-weight: bold; padding: 5px 15px; border-radius: 10px" this.resetStyle = "background: unset; color: unest" this.prefixStyle = this.setPrefixStyle(prefix); this.info(`Logger initialized with prefix "${prefix}" and logLevel ${logLevel}`) } /* * @param {number} logLevel - 0: disable, 1: info, 2: debug, 3: warn, 4: all */ setLogLevel(logLevel) { this.logLevel = logLevel } setPrefixStyle(prefix) { switch (prefix) { case "sto": return "background: #000; color: #fff" default: return this.defaultStyle } } formattedOutput(...args) { const argsArray = Array.from(args).map(arg => { if (typeof arg === "object") return JSON.stringify(arg, null, 4) return arg }) return `%c${this.prefix}%c ` + argsArray.join(", ") } info(...args) { if (this.logLevel >= 1) console.info(this.formattedOutput(...args), this.prefixStyle, this.resetStyle) } debug(...args) { if (this.logLevel >= 2) console.debug(this.formattedOutput(...args), this.prefixStyle, this.resetStyle) } warn(...args) { if (this.logLevel >= 3) console.warn(this.formattedOutput(...args), this.prefixStyle, this.resetStyle) } error(...args) { if (this.logLevel > 0) console.error(this.formattedOutput(...args), this.prefixStyle, this.resetStyle) } } /** * @param {string} text - Notification Text * @param {number} [duration=5000] - Duration for which the toast should be displayed. * -1 for permanent toast * @param {string} [type="default"] - Classname for Notification type * @description Sending Toast Notification */ function notify(text, duration = 5000, type = "default") { Toastify({ text: text, duration: duration, close: true, gravity: "top", // `top` or `bottom` position: "right", // `left`, `center` or `right` offset: { x: 0, // horizontal axis - can be a number or a string indicating unity. eg: '2em' y: 70 // vertical axis - can be a number or a string indicating unity. eg: '2em' }, className: type, stopOnFocus: true, // Prevents dismissing of toast on hover }).showToast(); } /** * @param {string} css - CSS String * @param {boolean} important - Add !important to all rules * @description Adds CSS to the head of the document */ function addGlobalStyle(css, important = true) { let head, style; head = document.getElementsByTagName('head')[0]; if (!head) return; style = document.createElement('style'); (important) ? style.innerHTML = css.replace(/;/g, ' !important;') : style.innerHTML = css; head.appendChild(style); } /** * @param {string} selector - CSS Selector * @param {HTMLElement|Document} parent - Parent Element * @description Waits for an element to be present in the DOM * @returns {Element | null} */ function waitForElm(selector, parent = document) { return new Promise((resolve) => { if (parent.querySelector(selector)) { log.debug("Element found", selector) return resolve(parent.querySelector(selector)); } const observer = new MutationObserver(() => { if (parent.querySelector(selector)) { log.debug("Element found", selector) resolve(parent.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { console.error("Element not found", selector) return resolve(null) }, 1000) }); } /** * @description Get current Hostname, Season and Episode * @returns {{host: string, season: (string|number), episode: (string|number)}} */ function getStreamPageLocation() { const url = window.location; const host = url.host; const path = url.pathname.split("/").slice(3); return { host: host, season: path[1]?.split("-")[1] || 0, episode: path[2]?.split("-")[1] || 0, } } /** * @param {HTMLElement} seasonListEl - Season List Element * @description Check if the Stream has Movies * @returns {boolean} - True if the list contains a "Filme" entry */ function checkHasMovies(seasonListEl) { if (!seasonListEl) return false const seasonList = seasonListEl.children for (let i = 0; i < seasonList.length; i++) if (seasonList[i].textContent.trim() === "Filme") { log.debug("Found Movies") return true } return false } /** * @description Get Stream Details from the Stream Page * @returns {Promise<{seasonsCount: number, hasMovies: boolean, episodesCount: number, episodeTitle: {de: string, en: string}, title: string}>} */ async function getStreamDetails() { const titleEl = await waitForElm(".series-title > h1 > span") const seasonListEl = await waitForElm("#stream > ul:nth-child(1)") const episodeListEl = await waitForElm("#stream > ul:nth-child(4)") const episodeTitleEl = await waitForElm(".hosterSiteTitle h2") const episodeTitle = getEpisodeTitle(episodeTitleEl) const hasMovies = checkHasMovies(seasonListEl) const seasonsCount = seasonListEl?.childElementCount - 1 - (hasMovies ? 1 : 0) || 0 const episodesCount = episodeListEl?.childElementCount - 1 || 0 log.debug("Elements", titleEl, seasonListEl, episodeListEl) log.debug("Count", seasonsCount, episodesCount) return { title: titleEl?.textContent.trim() || "", seasonsCount: seasonsCount, episodesCount: episodesCount, episodeTitle: { de: episodeTitle.de, en: episodeTitle.en, }, hasMovies: hasMovies, } } /** * @description Parsing title for both languages * @returns {{de: string, en: string}} */ function getEpisodeTitle(episodeTitleEl) { let titleDE = "" let titleEN = "" if (episodeTitleEl) { const [episodeTitleDE, episodeTitleEN] = episodeTitleEl.children titleDE = episodeTitleDE.textContent.trim() titleEN = episodeTitleEN?.textContent.trim() || "" } return {de: titleDE, en: titleEN} } /** * @description Return Stream Data from the Stream Page * @returns {Promise<{seasonsCount: number, currentSeason: number, host: string, hasMovies: boolean, episodesCount: number, title: string, currentEpisode: number}>} */ async function getStreamData() { const streamLocation = getStreamPageLocation() const streamDetails = await getStreamDetails() const data = { host: streamLocation.host, title: streamDetails.title, currentSeason: parseInt(streamLocation.season), seasonsCount: parseInt(streamDetails.seasonsCount), currentEpisode: parseInt(streamLocation.episode), episodesCount: parseInt(streamDetails.episodesCount), episodeTitle: streamDetails.episodeTitle, hasMovies: streamDetails.hasMovies, } log.debug("StreamData", data) return data } addGlobalStyle(` .toastify { background: #243743; border: 2px solid #637cf9; border-radius: 50px; box-shadow: 0px 0px 20px 0px #0f1620; } .toastify.error { background: #9c0000; border: 2px solid #f96363; } `, true)