Greasy Fork is available in English.

Kamikaze' Script Utils

Custom Functions for Kamikaze's Scripts

このスクリプトは単体で利用できません。右のようなメタデータを含むスクリプトから、ライブラリとして読み込まれます: // @require https://update.greatest.deepsurf.us/scripts/455253/1561858/Kamikaze%27%20Script%20Utils.js

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
  1. // ==UserScript==
  2. // @name Kamikaze' Script Utils
  3. // @namespace https://greatest.deepsurf.us/users/928242
  4. // @description Custom Functions for Kamikaze's Scripts
  5. // @version 1.1.11
  6. // @author Kamikaze (https://github.com/Kamiikaze)
  7. // @license MIT
  8. // ==/UserScript==
  9.  
  10. /* jshint esversion: 11 */
  11.  
  12. /* global Toastify */
  13.  
  14. // https://stackoverflow.com/questions/61964265/getting-error-this-document-requires-trustedhtml-assignment-in-chrome
  15. if (window.trustedTypes && window.trustedTypes.createPolicy && !window.trustedTypes.defaultPolicy) {
  16. window.trustedTypes.createPolicy('default', {
  17. createHTML: string => string
  18. // Optional, only needed for script (url) tags
  19. //,createScriptURL: string => string
  20. //,createScript: string => string,
  21. });
  22. }
  23.  
  24. /**
  25. * @description Custom Logger
  26. */
  27. class Logger {
  28.  
  29. /**
  30. * @param {string} prefix - Prefix for the log output
  31. * @param {number} logLevel - 0: disable, 1: info, 2: debug, 3: warn, 4: all
  32. */
  33. constructor(prefix, logLevel = 1) {
  34. this.prefix = prefix; // Name of Script
  35. this.logLevel = logLevel;
  36. this.defaultStyle = "background: #44adf3; color: #000; font-weight: bold; padding: 5px 15px; border-radius: 10px"
  37. this.resetStyle = "background: unset; color: unest"
  38. this.prefixStyle = this.setPrefixStyle(prefix);
  39.  
  40. this.info(`Logger initialized with prefix "${prefix}" and logLevel ${logLevel}`)
  41. }
  42.  
  43. /*
  44. * @param {number} logLevel - 0: disable, 1: info, 2: debug, 3: warn, 4: all
  45. */
  46. setLogLevel(logLevel) {
  47. this.logLevel = logLevel
  48. }
  49.  
  50. setPrefixStyle(prefix) {
  51. switch (prefix) {
  52. case "sto":
  53. return "background: #000; color: #fff"
  54. default:
  55. return this.defaultStyle
  56. }
  57. }
  58.  
  59. formattedOutput(...args) {
  60. const argsArray = Array.from(args).map(arg => {
  61. if (typeof arg === "object") return JSON.stringify(arg, null, 4)
  62. return arg
  63. })
  64. return `%c${this.prefix}%c ` + argsArray.join(", ")
  65. }
  66.  
  67. info(...args) {
  68. if (this.logLevel >= 1) console.info(this.formattedOutput(...args), this.prefixStyle, this.resetStyle)
  69. }
  70.  
  71. debug(...args) {
  72. if (this.logLevel >= 2) console.debug(this.formattedOutput(...args), this.prefixStyle, this.resetStyle)
  73. }
  74.  
  75. warn(...args) {
  76. if (this.logLevel >= 3) console.warn(this.formattedOutput(...args), this.prefixStyle, this.resetStyle)
  77. }
  78.  
  79. error(...args) {
  80. if (this.logLevel > 0) console.error(this.formattedOutput(...args), this.prefixStyle, this.resetStyle)
  81. }
  82.  
  83. }
  84.  
  85. /**
  86. * @param {string} text - Notification Text
  87. * @param {number} [duration=5000] - Duration for which the toast should be displayed.
  88. * -1 for permanent toast
  89. * @param {string} [type="default"] - Classname for Notification type
  90. * @description Sending Toast Notification
  91. */
  92. function notify(text, duration = 5000, type = "default") {
  93. Toastify({
  94. text: text,
  95. duration: duration,
  96. close: true,
  97. gravity: "top", // `top` or `bottom`
  98. position: "right", // `left`, `center` or `right`
  99. offset: {
  100. x: 0, // horizontal axis - can be a number or a string indicating unity. eg: '2em'
  101. y: 70 // vertical axis - can be a number or a string indicating unity. eg: '2em'
  102. },
  103. className: type,
  104. stopOnFocus: true, // Prevents dismissing of toast on hover
  105. }).showToast();
  106. }
  107.  
  108. /**
  109. * @param {string} css - CSS String
  110. * @param {boolean} important - Add !important to all rules
  111. * @description Adds CSS to the head of the document
  112. */
  113. function addGlobalStyle(css, important = true) {
  114. let head, style;
  115. head = document.getElementsByTagName('head')[0];
  116. if (!head) return;
  117. style = document.createElement('style');
  118. (important) ? style.innerHTML = css.replace(/;/g, ' !important;') : style.innerHTML = css;
  119. head.appendChild(style);
  120. }
  121.  
  122. /**
  123. * @param {string} selector - CSS Selector
  124. * @param {HTMLElement|Document} parent - Parent Element
  125. * @description Waits for an element to be present in the DOM
  126. * @returns {Element | null}
  127. */
  128. function waitForElm(selector, parent = document) {
  129. return new Promise((resolve) => {
  130. if (parent.querySelector(selector)) {
  131. log.debug("Element found", selector)
  132. return resolve(parent.querySelector(selector));
  133. }
  134.  
  135. const observer = new MutationObserver(() => {
  136. if (parent.querySelector(selector)) {
  137. log.debug("Element found", selector)
  138. resolve(parent.querySelector(selector));
  139. observer.disconnect();
  140. }
  141. });
  142.  
  143. observer.observe(document.body, {
  144. childList: true,
  145. subtree: true
  146. });
  147.  
  148. setTimeout(() => {
  149. console.error("Element not found", selector)
  150. return resolve(null)
  151. }, 1000)
  152. });
  153. }
  154.  
  155. /**
  156. * @description Get current Hostname, Season and Episode
  157. * @returns {{host: string, season: (string|number), episode: (string|number)}}
  158. */
  159. function getStreamPageLocation() {
  160. const url = window.location;
  161. const host = url.host;
  162. const path = url.pathname.split("/").slice(3);
  163.  
  164. return {
  165. host: host,
  166. season: path[1]?.split("-")[1] || 0,
  167. episode: path[2]?.split("-")[1] || 0,
  168. }
  169. }
  170.  
  171. /**
  172. * @param {HTMLElement} seasonListEl - Season List Element
  173. * @description Check if the Stream has Movies
  174. * @returns {boolean} - True if the list contains a "Filme" entry
  175. */
  176. function checkHasMovies(seasonListEl) {
  177. if (!seasonListEl) return false
  178. const seasonList = seasonListEl.children
  179. for (let i = 0; i < seasonList.length; i++)
  180. if (seasonList[i].textContent.trim() === "Filme") {
  181. log.debug("Found Movies")
  182. return true
  183. }
  184. return false
  185. }
  186.  
  187. /**
  188. * @description Get Stream Details from the Stream Page
  189. * @returns {Promise<{seasonsCount: number, hasMovies: boolean, episodesCount: number, episodeTitle: {de: string, en: string}, title: string}>}
  190. */
  191. async function getStreamDetails() {
  192. const titleEl = await waitForElm(".series-title > h1 > span")
  193. const seasonListEl = await waitForElm("#stream > ul:nth-child(1)")
  194. const episodeListEl = await waitForElm("#stream > ul:nth-child(4)")
  195. const episodeTitleEl = await waitForElm(".hosterSiteTitle h2")
  196. const episodeTitle = getEpisodeTitle(episodeTitleEl)
  197.  
  198. const hasMovies = checkHasMovies(seasonListEl)
  199.  
  200. const seasonsCount = seasonListEl?.childElementCount - 1 - (hasMovies ? 1 : 0) || 0
  201. const episodesCount = episodeListEl?.childElementCount - 1 || 0
  202.  
  203. log.debug("Elements", titleEl, seasonListEl, episodeListEl)
  204. log.debug("Count", seasonsCount, episodesCount)
  205.  
  206. return {
  207. title: titleEl?.textContent.trim() || "",
  208. seasonsCount: seasonsCount,
  209. episodesCount: episodesCount,
  210. episodeTitle: {
  211. de: episodeTitle.de,
  212. en: episodeTitle.en,
  213. },
  214. hasMovies: hasMovies,
  215. }
  216. }
  217.  
  218. /**
  219. * @description Parsing title for both languages
  220. * @returns {{de: string, en: string}}
  221. */
  222. function getEpisodeTitle(episodeTitleEl) {
  223. let titleDE = ""
  224. let titleEN = ""
  225. if (episodeTitleEl) {
  226. const [episodeTitleDE, episodeTitleEN] = episodeTitleEl.children
  227. titleDE = episodeTitleDE.textContent.trim()
  228. titleEN = episodeTitleEN?.textContent.trim() || ""
  229. }
  230.  
  231. return {de: titleDE, en: titleEN}
  232. }
  233.  
  234. /**
  235. * @description Return Stream Data from the Stream Page
  236. * @returns {Promise<{seasonsCount: number, currentSeason: number, host: string, hasMovies: boolean, episodesCount: number, title: string, currentEpisode: number}>}
  237. */
  238. async function getStreamData() {
  239. const streamLocation = getStreamPageLocation()
  240. const streamDetails = await getStreamDetails()
  241.  
  242. const data = {
  243. host: streamLocation.host,
  244. title: streamDetails.title,
  245. currentSeason: parseInt(streamLocation.season),
  246. seasonsCount: parseInt(streamDetails.seasonsCount),
  247. currentEpisode: parseInt(streamLocation.episode),
  248. episodesCount: parseInt(streamDetails.episodesCount),
  249. episodeTitle: streamDetails.episodeTitle,
  250. hasMovies: streamDetails.hasMovies,
  251. }
  252.  
  253. log.debug("StreamData", data)
  254.  
  255. return data
  256. }
  257.  
  258.  
  259. addGlobalStyle(`
  260. .toastify {
  261. background: #243743;
  262. border: 2px solid #637cf9;
  263. border-radius: 50px;
  264. box-shadow: 0px 0px 20px 0px #0f1620;
  265. }
  266. .toastify.error {
  267. background: #9c0000;
  268. border: 2px solid #f96363;
  269. }
  270. `, true)