Greasy Fork

Greasy Fork is available in English.

Kamikaze' Script Utils

Custom Functions for Kamikaze's Scripts

当前为 2023-09-21 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/455253/1254021/Kamikaze%27%20Script%20Utils.js

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        	Kamikaze' Script Utils
// @namespace    	http://greasyfork.icu/users/928242
// @description  	Custom Functions for Kamikaze's Scripts
// @version    		1.0.4
// @author       	Kamikaze (https://github.com/Kamiikaze)
// @license     	MIT
// @grant       	none
// ==/UserScript==

/* jshint esversion: 11 */


/**
 * @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} 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
 */
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) {
	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()

	const hasMovies = checkHasMovies(seasonListEl)

	const seasonsCount = seasonListEl.childElementCount - 1 - (hasMovies ? 1 : 0)
	const episodesCount = episodeListEl.childElementCount - 1

	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(titleEl) {
	let titleDE = ""
	let titleEN = ""
	if (titleEl) {
		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
}