您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Показывает продолжительность франшизы и аниме
// ==UserScript== // @name Anime Time Details // @icon https://www.google.com/s2/favicons?domain=shikimori.me // @namespace https://shikimori.one // @version 1.1 // @description Показывает продолжительность франшизы и аниме // @author LifeH // @match *://shikimori.org/* // @match *://shikimori.one/* // @match *://shikimori.me/* // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; function page() { return window.location.href.includes("/achievements/franchise/"); } function аnimPage() { return window.location.href.includes("/animes/"); } let time = 0; let watchTime = 0; let types = {}; function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function details(animeUrl, isWatched) { try { const animeIdMatch = animeUrl.match(/\/animes\/[a-z]?(\d+)/); if (!animeIdMatch) return; const animeId = animeIdMatch[1]; const apiUrl = `https://shikimori.one/api/animes/${animeId}`; const response = await fetch(apiUrl); const animeData = await response.json(); let episodes = animeData.episodes ? animeData.episodes : "1"; const duration = animeData.duration ? animeData.duration : "Неизвестно"; const animeType = animeData.kind ? formatTypes(animeData.kind) : "Неизвестно"; if (animeType === "Фильм") { episodes = 1; } if (types[animeType]) { types[animeType]++; } else { types[animeType] = 1; } const durationM = parseDur(`${duration} мин`); if (durationM && episodes !== "Неизвестно") { const totalTime = parseInt(episodes) * durationM; time += totalTime; if (isWatched) { watchTime += totalTime; } } updateInfo(); } catch (error) { //console.error(error); } } async function аnimDetails() { try { const typeElement = elementFinder(document, "Тип"); const episodesElement = elementFinder(document, "Эпизоды"); const durationElement = elementFinder(document, "Длительность эпизода"); if (!episodesElement || episodesElement.textContent.trim() === "") { return; } let episodes = episodesElement.textContent.trim(); const duration = durationElement? durationElement.textContent.trim(): "Неизвестно"; const duratonM = parseDur(duration); if (duratonM && episodes !== "Неизвестно") { const totalTime = parseInt(episodes) * duratonM; if (!document.querySelector(".time-block")) { const timeBlock = document.createElement("div"); timeBlock.classList.add("line", "time-block"); timeBlock.innerHTML = ` <div class="key">Время просмотра:</div> <div class="value"><span>${formatTime( totalTime )}</span></div> `; if (durationElement && durationElement.parentNode) { durationElement.parentNode.parentNode.appendChild(timeBlock); } } } } catch (error) { // console.error(error); } } function elementFinder(doc, keyText) { const lines = doc.querySelectorAll(".b-entry-info .line-container .line"); for (let line of lines) { const key = line.querySelector(".key"); if (key && key.textContent.includes(keyText)) { return line.querySelector(".value"); } } return null; } function animeUrls(articleElement) { const linkElement = articleElement.querySelector("a.cover"); if (linkElement) { const relativeUrl = linkElement.getAttribute("href"); return relativeUrl.startsWith("http") ? relativeUrl : `https://shikimori.one${relativeUrl}`; } return null; } function isCompleted(articleElement) { return articleElement.classList.contains("completed"); } function parseDur(durationText) { const hoursMatch = /(\d+)\s*час/.exec(durationText); const minsMatch = /(\d+)\s*мин/.exec(durationText); return (hoursMatch ? parseInt(hoursMatch[1]) * 60 : 0) + (minsMatch ? parseInt(minsMatch[1]) : 0); } function rotEbal(number, one, two, five) { const n1 = Math.abs(number) % 10; if (number > 10 && number < 20) return five; if (n1 > 1 && n1 < 5) return two; if (n1 === 1) return one; return five; } function formatTime(totalMins, zero = false) { const days = Math.floor(totalMins / (24 * 60)); const hours = Math.floor((totalMins % (24 * 60)) / 60); const mins = totalMins % 60; const dayText = rotEbal(days, "день", "дня", "дней"); const hourText = rotEbal(hours, "час", "часа", "часов"); const minsText = rotEbal(mins, "минута", "минуты", "минут"); if (zero) { return `${days} ${dayText}, ${hours} ${hourText}, ${mins} ${minsText}`; } else { let result = ""; if (days > 0) result += `${days} ${dayText}, `; if (hours > 0 || days > 0) result += `${hours} ${hourText}, `; result += `${mins} ${minsText}`; return result.trim(); } } function createInfBlock() { if (document.getElementById("info-block")) { return; } const header = document.querySelector(".head.misc"); const infoBlock = document.createElement("div"); infoBlock.id = "info-block"; infoBlock.style.padding = "20px"; infoBlock.style.backgroundColor = "#f0f0f0"; infoBlock.style.border = "1px solid #ccc"; infoBlock.style.marginTop = "10px"; const status = document.createElement("div"); status.id = "status"; status.textContent = "Обработка..."; status.style.fontWeight = "bold"; status.style.marginBottom = "10px"; infoBlock.appendChild(status); header.appendChild(infoBlock); return infoBlock; } function formatTypes(kind) { const form = { "tv": "TV Сериал", "tv_special": " TV Спецвыпуск", "movie": "Фильм", "ova": "OVA", "ona": "ONA", "special": "Спецвыпуск", }; return form[kind] || "Неизвестно"; } function updateInfo() { const infoBlock = document.getElementById("info-block"); const remainingTime = time - watchTime; infoBlock.innerHTML = ` <div id="status">Обработка...</div> <h3>Информация</h3> <p>Общее время просмотра: ${formatTime(time, true)}</p> <p>Осталось смотреть: ${formatTime(remainingTime, true)}</p> <h4>Количество типов:</h4> <ul> ${Object.entries(types) .map(([type, count]) => `<li>${type}: ${count}</li>`) .join("")} </ul> `; } function done() { const status = document.getElementById("status"); status.textContent = "Обработано"; status.style.color = "green"; } async function processAnimes() { time = 0; watchTime = 0; types = {}; const animeList = document.querySelectorAll(".b-catalog_entry"); for (const article of animeList) { const animeUrl = animeUrls(article); const isWatched = isCompleted(article); if (animeUrl) { await details(animeUrl, isWatched); await delay(80); } } updateInfo(); done(); } async function main() { if (page()) { createInfBlock(); await processAnimes(); } else if (аnimPage()) { await аnimDetails(); } } function ready(fn) { document.addEventListener("page:load", fn); document.addEventListener("turbolinks:load", fn); if (document.attachEvent? document.readyState === "complete": document.readyState !== "loading") { fn(); } else { document.addEventListener("DOMContentLoaded", fn); } } ready(function () {main();}); })();