NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name All in One for Shikimori // @version 0.4 // @description Всё в одном. // @author MjKey and more.. // @match https://shikimori.org/* // @match https://shikimori.one/* // @match http://shikimori.one/* // @match http://shikimori.org/* // @match https://shikimori.me/* // @match http://shikimori.me/* // @updateURL https://openuserjs.org/meta/MjKey/All_in_One_for_Shikimori.meta.js // @downloadURL https://openuserjs.org/install/MjKey/All_in_One_for_Shikimori.user.js // @connect myanimelist.net // @require https://github.com/qt-kaneko/Shikiplayer/releases/latest/download/manifest.user.js // @run-at document-end // @grant GM_xmlhttpRequest // @grant GM_addStyle // @license MIT // ==/UserScript== // OP/ED by ShaDream & Chortowod const youtubeLinks = true; let debug = 1; const insertAfter = (elem, refElem) => refElem.parentNode.insertBefore(elem, refElem.nextSibling); function log(message) { debug && console.log(message) } function get_anime_id() { let full_url = window.location.href; let start = full_url.lastIndexOf('/') + 1; for (; isNaN(parseInt(full_url[start]));) start++; let number = ""; for (; !isNaN(parseInt(full_url[start]));) { number += full_url[start]; start++; } return number; } function createMusic(elements, placeToAppend) { elements.forEach(element => { let sound = document.createElement("span"); let fst = element[0]; let check = element.indexOf('(ep'); let substringS = element.substring((fst == "#" ? 4 : 0), check); let substringF = element.substring(fst == "#" ? 4 : 0); sound.innerText = element; sound.className = "value sound"; placeToAppend.appendChild(sound); if (youtubeLinks) sound.appendChild(getYoutubeLink(sound, check, substringS, substringF)); }) } function getYoutubeLink(sound, check, substringS, substringF) { let sound2 = document.createElement("a"); sound2.innerText = " -> YouTube"; sound2.href = "https://www.youtube.com/results?search_query=" + (check != -1 ? substringS : substringF); return sound2; } function musicConstructor(main, text, items) { let container = document.createElement("div"); let addedClassName = "OP'S" === text ? " op" : " ed"; container.className = "sound-container" + addedClassName; main.appendChild(container); let title = document.createElement("div"); title.innerText = text; title.className = "subheadline m5"; container.appendChild(title); createMusic(items, container); } function createOPEDList(op, ed) { log(op); if (0 != op.length || 0 != ed.length) { // Create main div and paste in in a right place let main = document.createElement("div"); let paste_after = document.getElementsByClassName("b-db_entry")[0]; //Create open button if needed if (main.className = "main-sound-container", insertAfter(main, paste_after), //Create content musicConstructor(main, "OP'S", op), musicConstructor(main, "ED'S", ed), op.length > 4 || ed.length > 4) { main.style.maxHeight = '150px'; let expand_container = document.createElement("div"); insertAfter(expand_container, main); expand_container.className = "b-height_shortener open-music"; let shade = document.createElement("div"); shade.className = "shade"; expand_container.appendChild(shade); let expander = document.createElement("div"); expander.className = "expand"; expand_container.appendChild(expander); let span = document.createElement('span'); span.innerText = "Развернуть"; expander.appendChild(span); expand_container.onclick = function () { expand_container.parentNode.removeChild(expand_container); main.style.animation = 'height 15s cubic-bezier(.19,1,.22,1) forwards'; } } //apply styles createStyle(); } } function createStyle() { // Here you can change style of all new elements GM_addStyle(".main-sound-container{margin-bottom:15px; overflow:hidden}.sound-container{display:inline-block; vertical-align:top; width: 48%;}.op{margin-right:3%;}.sound{padding-top:5px; margin:5px; display: block;}.open-music{margin-bottom:15px;}@keyframes height {from{max-height:150px;} to {max-height: 2000px;}}"); } function getMusic(doc, class_name) { let music = doc.getElementsByClassName("theme-songs js-theme-songs " + class_name)[0]; log(music); let childs = []; let authors = music.getElementsByClassName("theme-song-artist"); let counter = 1; for (var i = 0; i < authors.length; i++) { let td = authors[i].parentNode; let episodes = td.getElementsByClassName("theme-song-episode")[0]; if (episodes) episodes = episodes.textContent; else episodes = ''; let songName = td.getElementsByClassName("theme-song-title")[0]; if (songName) { log(songName.textContent + authors[i].textContent + ' ' + episodes); childs.push('#' + counter + ' ' + songName.textContent + authors[i].textContent + ' ' + episodes); } else { for (let songName2 of td.childNodes) { if (songName2.nodeType === 3) { log(songName2.textContent + authors[i].textContent); childs.push('#' + counter + ' ' + songName2.textContent + authors[i].textContent + ' ' + episodes); break; } } } counter++; } log(childs); return childs; } function loadMalt() { "use strict"; if (!isAnimePage() || isAdded()) return; let url = "https://myanimelist.net/anime/" + get_anime_id(); log("Finding OP/ED."); GM_xmlhttpRequest({ method: "GET", url: url, onload: function (response) { let doc = (new DOMParser).parseFromString(response.responseText, 'text/html'); log(doc); createOPEDList(getMusic(doc, "opnening"), getMusic(doc, "ending")); log("OP/ED finded!"); } }) } function isAnimePage() { return 0 === window.location.href.replace(/http.?:\/\/shikimori\..*\/animes\/[^\/]*/, "").length } function isAdded() { return document.getElementsByClassName("main-sound-container").length > 0 } function ready0(fn) { if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") fn(); else document.addEventListener('DOMContentLoaded', fn); } ready0(loadMalt); //CustomListMark by AniOleg const clOngoing = 'GreenYellow'; const clOngoingLaze = 'red'; const clAnnounce = 'orange'; const markTitle = (sColor, EntriesList, a, b) => { EntriesList[a].childNodes[b].childNodes[0].style.borderLeft = `1px solid ${sColor}`; }; const cReDraw = () => { if (!(location.pathname.includes('/list/anime') || location.pathname.includes('/list/manga'))) return; try { const EntriesList = document.getElementsByClassName('entries'); for (let a = 0; a < EntriesList.length; a++) { for (let b = 0; b < EntriesList[a].childNodes.length; b++) { const ongoingElem = EntriesList[a].childNodes[b].childNodes[1].getElementsByClassName('ongoing')[0]; if (ongoingElem) { ongoingElem.remove(); const miscValue = EntriesList[a].childNodes[b].childNodes[3].getElementsByClassName('misc-value')[0].childNodes[1].nodeValue; const currentValue = EntriesList[a].childNodes[b].childNodes[3].getElementsByClassName('current-value')[0].childNodes[0].childNodes[0].nodeValue; if (miscValue === '?' || miscValue - currentValue <= 0) { markTitle(clOngoing, EntriesList, a, b); } else { markTitle(clOngoingLaze, EntriesList, a, b); } } else { const anonsElem = EntriesList[a].childNodes[b].childNodes[1].getElementsByClassName('anons')[0]; if (anonsElem) { anonsElem.remove(); markTitle(clAnnounce, EntriesList, a, b); } } } } } catch (error) {} }; const observeDOM = (function () { const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; const eventListenerSupported = window.addEventListener; return function (obj, callback) { if (MutationObserver) { const obs = new MutationObserver((mutations, observer) => { if (mutations[0].addedNodes.length || mutations[0].removedNodes.length) { callback(); } }); obs.observe(obj, { childList: true, subtree: true }); } else if (eventListenerSupported) { obj.addEventListener('DOMNodeInserted', callback, false); } }; })(); observeDOM(document.querySelector('html'), cReDraw); //ShikiAirTime by AniOleg const cGap = 11.4, //отступ между обложками аниме (px) // cGridItemWidth = 96, //цвет линии просматриваемого онгоинга (есть отставание по сериям) clOngoingPlanned = 'orange', //цвет линии запланированного онгоинга bSize = 2, //толщина линии подчёркивания тайтла (px) cTooltip = true, //включить отображение карточек при наведении на обложку аниме showAiredAnime = true, //отображать уже вышедшее аниме, или аниме для которого неизвестна дата выхода эпизода showPlannedAirAnime = false, //отображать онгоинги из списка "Запланировано" gDebul = false; //выводить отладочную информацию в консоль DevTools // const host = location.protocol + '//' + location.host; const lang_data = { ru: { header: 'Ваше аниме', episode: 'Эпизод', progress: 'Прогресс:', day: 'д', hour: 'ч', min: 'м', emptyList: 'Список аниме пуст', errorCatch: 'При обработке данных произошла ошибка. Попробуйте обновить страницу', episodeIncrementProgress: 'Отправка...', cacheResetAlert: 'Кэш расписания сброшен. Обновите страницу', }, en: { header: 'Your anime', episode: 'Ep', progress: 'Progress:', day: 'd', hour: 'h', min: 'm', emptyList: 'Anime list is empty', errorCatch: 'An error occurred while processing the data. Try refreshing the page', episodeIncrementProgress: 'Sending...', cacheResetAlert: 'Schedule cache was reset. Please, refresh page', }, }; var CalendarList = [], AnimeList = [], GlobalServerTime = null, SiteLang, TitleLang; function InitScript(fn) { document.addEventListener('page:load', fn); document.addEventListener('turbolinks:load', fn); if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading") { fn(); var ctrlDown = false, shiftDown = false, ctrlKey = 17, cmdKey = 91, shiftKey = 16, fKey = 70, prevKey = 0; $(document).keydown(function (e) { if (e.keyCode == ctrlKey || e.keyCode == cmdKey) ctrlDown = true; if (e.keyCode == shiftKey) shiftDown = true; if (ctrlDown && shiftDown && e.keyCode == fKey && location.pathname == '/' && GetUserId() != null) { localStorage.setItem('__AniListCalendar', '[]'); alert(lang_data[SiteLang].cacheResetAlert); if (gDebul) console.log("ShikiAirTime: Кэш календаря сброшен пользователем"); } }).keyup(function (e) { if (e.keyCode == ctrlKey || e.keyCode == cmdKey) ctrlDown = false; if (e.keyCode == shiftKey) shiftDown = false; }); } else { document.addEventListener('DOMContentLoaded', fn); } } function InitListFetch() { if (localStorage.getItem('__AniListCalendar') == null) { localStorage.setItem('__AniListCalendar', '[]'); } if (location.pathname == '/' && GetUserId() != null) { GlobalServerTime = GetServerTime(); SiteLang = $('body')[0].attributes['data-locale'].nodeValue; TitleLang = $('body')[0].attributes['data-localized_names'].nodeValue; if (gDebul) console.log('ShikiAirTime: Запрос списка пользователя...'); InitListGrid(); fetch(host + '/api/users/' + GetUserId() + '/anime_rates?status=watching&limit=5000', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', } }) .then(r => r.json()) .then(data => { AnimeList = data; if (gDebul) console.log('ShikiAirTime: Список аниме пользователя получен'); //2.5 if (showPlannedAirAnime) { fetch(host + '/api/users/' + GetUserId() + '/anime_rates?status=planned&limit=5000', { method: 'GET', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', } }) .then(r => r.json()) .then(data => { if (gDebul) console.log('ShikiAirTime: Список аниме пользователя получен (для опции "Отображать запланированное аниме")'); for (var a = 0; a < data.length; a++) { if (data[a].anime.status == "ongoing") { AnimeList.push(data[a]); } } InitDataFetch(); }) .catch(error => { if (gDebul) console.log('ShikiAirTime: Ошибка получения списка аниме пользователя (для опции "Отображать запланированное аниме") ' + error); $('.block2')[0].childNodes[1].innerHTML = '<div style="text-align: center"><span>' + lang_data[SiteLang].errorCatch + '</span></div>'; }) } else { InitDataFetch(); } }) .catch(error => { if (gDebul) console.log('ShikiAirTime: Ошибка получения списка аниме пользователя ' + error); $('.block2')[0].childNodes[1].innerHTML = '<div style="text-align: center"><span>' + lang_data[SiteLang].errorCatch + '</span></div>'; }) } } function InitListGrid() { if (location.pathname == '/') { var d = document.createElement('style'); d.innerHTML = `.b-catalog_entry.__Calendar .cover .image-decor>.text:before {background: none !important} .b-catalog_entry.__Calendar .cover .image-decor>.text {text-align: center !important} .preAirData {display: none !important;} .preAirData.Show {display: block !important;} .addEpisode {display: none !important; padding-top: 5px !important; padding-bottom: 5px; font-size: 14px !important;} .addEpisode:hover > .plus {font-weight: bold !important;} .addEpisode.Show {display: block !important} .calendarGridContainer {display: grid; gap: ` + cGap + `px; grid-template-columns: repeat(auto-fill, ` + cGridItemWidth + `px);}`; $('body')[0].appendChild(d); var g = document.createElement('div'); g.id = 'CalendarTooltip'; g.style = 'display: none; position: absolute; left: 0px; top: 0px; pointer-events: none !important; margin: 0'; g.innerHTML = '<div class="tooltip-inner" style="width: 240px !important; min-height: 50px !important; box-shadow: none !important; margin: 0px !important"></div>'; $('body')[0].appendChild(g); $('.block2')[0].childNodes[1].style = ''; $('.block2')[0].childNodes[1].innerHTML = '<div style="text-align: center"><div class="b-ajax"></div>'; $('.block2')[0].childNodes[1].classList = ''; $('.block2')[0].childNodes[0].childNodes[0].childNodes[0].nodeValue = lang_data[SiteLang].header; $('.block2')[0].childNodes[0].childNodes[0].href = GetUserLink() + '/list/anime/mylist/watching,rewatching'; } } function InitDataFetch() { try { CalendarList = []; CalendarList = JSON.parse(localStorage.getItem('__AniListCalendar')); var Queue = []; if (CalendarList != null && CalendarList.length > 0) { if (gDebul) console.log('ShikiAirTime: В localStorage обнаружены данные календаря'); for (var a = 0; a < AnimeList.length; a++) { if (AnimeList[a].anime.status == 'ongoing') { var IsFinded = false; for (var b = CalendarList.length - 1; b >= 0; b--) { //перебор календаря (сохранённого) if (AnimeList[a].anime.id == CalendarList[b].data.Media.idMal) { //2.2 IsFinded = true; if (CalendarList[b].data.Media.nextAiringEpisode.airingAt - GlobalServerTime < 0) { //если у аниме эпизод уже вышел, то обновляем информацию CalendarList.splice(b, 1); Queue.push(AnimeList[a].anime.id); } } } if (!IsFinded) //если не нашли нужного аниме, то запрашиваем инфу Queue.push(AnimeList[a].anime.id); } } } else { //если календарь не был сохранён, то запрашиваем инфу по всем своим онгоингам if (gDebul) console.log('ShikiAirTime: Запрос новых данных по всему списку...') CalendarList = []; if (AnimeList.length > 0) { for (var i = 0; i < AnimeList.length; i++) { if (AnimeList[i].anime.status == 'ongoing') { Queue.push(AnimeList[i].anime.id); } } } } if (Queue.length > 0) { GrabAniList(Queue, 1); } else { CalendarList.sort(compareNumeric); DrawListGrid(); } } catch (e) { if (gDebul) console.log('ShikiAirTime: Ошибка ' + e + '. Кэш календаря будет сброшен') $('.block2')[0].childNodes[1].innerHTML = '<div style="text-align: center"><span>' + lang_data[SiteLang].errorCatch + '</span></div>'; localStorage.setItem('__AniListCalendar', '[]'); } } function GrabAniList(Queue, Page) { const ApiUrl = 'https://graphql.anilist.co'; const QueryStruct = ` query($idMal_in: [Int], $page: Int) { Page(page: $page) { media(idMal_in: $idMal_in) { idMal nextAiringEpisode { airingAt episode } } pageInfo { perPage hasNextPage total } } }`; var xhr = new XMLHttpRequest(); xhr.open("POST", ApiUrl); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { data = JSON.parse(xhr.responseText); for (var i = 0; i < data.data.Page.media.length; i++) { if (data.data.Page.media[i].nextAiringEpisode != null) { CalendarList.push({ data: { Media: data.data.Page.media[i] } }); } } localStorage.setItem('__AniListCalendar', JSON.stringify(CalendarList)); //сохраняем календарь в локальное хранилище if (data.data.Page.pageInfo.hasNextPage == true) { GrabAniList(Queue, Page + 1) } else { CalendarList.sort(compareNumeric); DrawListGrid(); } } }; var data = JSON.stringify({ query: QueryStruct, variables: { idMal_in: Queue } }); xhr.send(data); } function DrawListGrid() { if (location.pathname == '/') { var tInner = ''; var allCount = 0; function datediff(first, second) { return Math.round((second - first) / (1000 * 60 * 60 * 24)); } var findedArray = []; for (var a = 0; a < CalendarList.length; a++) { for (var b = 0; b < AnimeList.length; b++) { if (CalendarList[a].data.Media.idMal == AnimeList[b].anime.id && AnimeList[b].anime.status == "ongoing") { findedArray.push(b); var tTime = CalendarList[a].data.Media.nextAiringEpisode.airingAt - GlobalServerTime; if (tTime > -60) { //придерживаем серию ещё минуту после её выхода allCount++; var bColor = AnimeList[b].episodes + 1 < CalendarList[a].data.Media.nextAiringEpisode.episode ? clOngoingLaze : clOngoing; if (AnimeList[b].episodes == 0) bColor = clOngoingPlanned; tInner += ` <article class="b-catalog_entry __Calendar" itemtype="http://schema.org/Movie" style="border-bottom: ` + bSize + `px solid ` + bColor + `; position: relative" title_name="` + AnimeList[b].anime.name + `" title_ru="` + AnimeList[b].anime.russian + `" ` + `episodes_watched="` + AnimeList[b].episodes + `" episodes="` + AnimeList[b].anime.episodes + `" episodes_aired="` + (CalendarList[a].data.Media.nextAiringEpisode.episode - 1) + `" anime_rate_id="` + AnimeList[b].id + `" clOngoing="` + clOngoing + `" clOngoingLaze="` + clOngoingLaze + `" is_ongoing=true> <a class="cover anime-tooltip-processed" data-delay="150" href="` + host + AnimeList[b].anime.url + `"> <span class="image-decor"> <span class="image-cutter"> <img alt="` + AnimeList[b].anime.name + `" src="` + AnimeList[b].anime.image.x96 + `"> </span> <div class="text preAirData Show">` + lang_data[SiteLang].episode + ` ` + CalendarList[a].data.Media.nextAiringEpisode.episode + `<br>` + CalcEndTime(tTime) + `</div> <div class="text addEpisode">` + AnimeList[b].episodes + ` <span class="plus">+</span></div> </span> </a> </article> `; } } } } if (showAiredAnime) { for (var c = 0; c < AnimeList.length; c++) { if (findedArray.indexOf(c) == -1) { allCount++; tInner += ` <article class="b-catalog_entry __Calendar" itemtype="http://schema.org/Movie" style=" position: relative" title_name="` + AnimeList[c].anime.name + `" title_ru="` + AnimeList[c].anime.russian + `" ` + `episodes_watched="` + AnimeList[c].episodes + `" episodes="` + AnimeList[c].anime.episodes + `" episodes_aired="` + (AnimeList[c].anime.episodes == 0 ? "?" : AnimeList[c].anime.episodes) + `" anime_rate_id="` + AnimeList[c].id + `" clOngoing="` + clOngoing + `" clOngoingLaze="` + clOngoingLaze + `" is_ongoing=false> <a class="cover anime-tooltip-processed" data-delay="150" href="` + host + AnimeList[c].anime.url + `"> <span class="image-decor"> <span class="image-cutter"> <img alt="` + AnimeList[c].anime.name + `" src="` + AnimeList[c].anime.image.x96 + `"> </span> <div class="text addEpisode">` + AnimeList[c].episodes + ` <span class="plus">+</span></div> </span> </a> </article> `; } } } if (allCount > 0) { $('.block2')[0].childNodes[1].classList = 'calendarGridContainer'; $('.block2')[0].childNodes[1].innerHTML = tInner; } else { $('.block2')[0].childNodes[1].innerHTML = '<div style="text-align: center"><span>' + lang_data[SiteLang].emptyList + '</span></div>'; } //очистка завершенных онгоингов из кэша календаря for (var x = CalendarList.length - 1; x >= 0; x--) { var IsFinded = false; for (var d = 0; d < AnimeList.length; d++) { if (CalendarList[x].data.Media.idMal == AnimeList[d].anime.id) { IsFinded = true; } } if (!IsFinded) { CalendarList.splice(x, 1); } } localStorage.setItem('__AniListCalendar', JSON.stringify(CalendarList)); //сохраняем календарь в локальное хранилище if (cTooltip) { var elements = $('.__Calendar'); for (var i = 0; i < elements.length; i++) { $(elements[i].childNodes[1].childNodes[1]).hover( function (e) { ShowCustomTooltip(e.target); $(e.target.parentElement.parentElement).find(".addEpisode").addClass("Show"); $(e.target.parentElement.parentElement).find(".preAirData").removeClass("Show"); }, function (e) { HideCustomTooltip(); $(e.target.parentElement.parentElement).find(".addEpisode").removeClass("Show"); $(e.target.parentElement.parentElement).find(".preAirData").addClass("Show"); } ) } } $(".addEpisode").click(function (e) { e.preventDefault(); incEpisode($(e.target.parentElement.parentElement).find(".addEpisode")[0].parentElement.parentElement.parentElement.getAttribute("anime_rate_id"), $(e.target.parentElement.parentElement).find(".addEpisode")[0]); }) } } function ShowCustomTooltip(e) { e = e.tagName == 'IMG' ? e.parentElement.parentElement.parentElement.parentElement : e.parentElement.parentElement.parentElement; $('#CalendarTooltip')[0].style.display = 'block'; $('#CalendarTooltip')[0].style.top = e.getBoundingClientRect().y + document.documentElement.scrollTop + 'px'; $('#CalendarTooltip')[0].childNodes[0].innerHTML = (TitleLang == 'ru' ? e.attributes.title_ru.nodeValue : e.attributes.title_name.nodeValue) + '<br><br>'; var EpisodesBehind = e.attributes.episodes_aired.nodeValue - e.attributes.episodes_watched.nodeValue; if (EpisodesBehind > 0) { $('#CalendarTooltip')[0].childNodes[0].innerHTML += (SiteLang == 'ru' ? 'Отставание на ' + EpisodesBehind + ' ' + GetLocalizedEpisodeHint(EpisodesBehind) : EpisodesBehind + ' episode' + (EpisodesBehind > 1 ? 's' : '') + ' behind') + '<br><br>'; } $('#CalendarTooltip')[0].childNodes[0].innerHTML += lang_data[SiteLang].progress + ' ' + e.attributes.episodes_watched.nodeValue + '/' + (e.attributes.episodes.nodeValue != 0 ? e.attributes.episodes.nodeValue : '?'); if (e.getBoundingClientRect().x + e.getBoundingClientRect().width + 2 + $('#CalendarTooltip')[0].childNodes[0].getBoundingClientRect().width + 10 < $(window).width()) { $('#CalendarTooltip')[0].style.left = e.getBoundingClientRect().x + e.getBoundingClientRect().width + 2 + 'px'; } else { $('#CalendarTooltip')[0].style.left = e.getBoundingClientRect().x - 2 - $('#CalendarTooltip')[0].childNodes[0].getBoundingClientRect().width + 'px'; } } function GetLocalizedEpisodeHint(e) { var variants = ['эпизод', 'эпизода', 'эпизодов']; e = Math.abs(e) % 100; var n1 = e % 10; if (e > 10 && e < 20) { return variants[2]; } if (n1 > 1 && n1 < 5) { return variants[1]; } if (n1 == 1) { return variants[0]; } return variants[2]; } function HideCustomTooltip() { $('#CalendarTooltip')[0].style.display = 'none'; } function GetUserId() { return JSON.parse($('body')[0].attributes['data-user'].value).id; } function GetUserLink() { return JSON.parse($('body')[0].attributes['data-user'].value).url; } function GetServerTime() { try { return new Date($('body')[0].attributes['data-server_time'].nodeValue).getTime() / 1000; } catch (e) { if (gDebul) console.log('ShikiAirTime: Ошибка запроса серверного времени'); return null; } } function compareNumeric(a, b) { //сортировка AniList по времени до выхода эпизода a = a.data.Media.nextAiringEpisode.airingAt; b = b.data.Media.nextAiringEpisode.airingAt; if (a > b) return 1; if (a == b) return 0; if (a < b) return -1; } function CalcEndTime(tUTime) { var t = parseInt(tUTime); if (t < 60) { return '<1' + lang_data[SiteLang].min; } else { var days = parseInt(t / 86400); t = t - (days * 86400); var hours = parseInt(t / 3600); t = t - (hours * 3600); var minutes = parseInt(t / 60); var content = ''; if (days) { content += days + lang_data[SiteLang].day; } if (hours > 0) { if (hours || days) { if (content) { content += ' '; } content += hours + lang_data[SiteLang].hour; } } if (minutes) { if (content) { content += ' '; } content += minutes + lang_data[SiteLang].min; } return content; } } function incEpisode(rate_id, addEpisodeElement) { var prevHTML = addEpisodeElement.innerHTML; addEpisodeElement.innerHTML = lang_data[SiteLang].episodeIncrementProgress; fetch(host + '/api/v2/user_rates/' + rate_id + '/increment', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', } }) .then(r => r.json()) .then(data => { if (gDebul) console.log('ShikiAirTime: Количество просмотренных эпизодов увеличено на 1'); addEpisodeElement.innerHTML = data.episodes + ' <span class="plus">+</span>'; addEpisodeElement.parentElement.parentElement.parentElement.setAttribute("episodes_watched", data.episodes); HideCustomTooltip(); if (addEpisodeElement.parentElement.parentElement.parentElement.getAttribute("is_ongoing") == "true") { console.log(data.episodes, addEpisodeElement.parentElement.parentElement.parentElement.getAttribute("episodes_aired")); var bColor = data.episodes < addEpisodeElement.parentElement.parentElement.parentElement.getAttribute("episodes_aired") ? clOngoingLaze : clOngoing; addEpisodeElement.parentElement.parentElement.parentElement.style.borderBottom = bSize + `px solid ` + bColor; } if (addEpisodeElement.parentElement.parentElement.parentElement.getAttribute("episodes") > 0 && addEpisodeElement.parentElement.parentElement.parentElement.getAttribute("episodes") == data.episodes) { addEpisodeElement.parentElement.parentElement.parentElement.remove(); } }) .catch(error => { if (gDebul) console.log('ShikiAirTime: Ошибка увеличения кол-ва эпизодов'); addEpisodeElement.innerHTML = prevHTML; }) } InitScript(InitListFetch) //Shiki Rating by ImoutoChan function getLocale() { return document.querySelector('body').getAttribute('data-locale'); } function needAddRating(urlpart) { return urlpart === "/animes" || urlpart === "/mangas" || urlpart === "/ranobe"; } function removeLastClass(domElement) { var classes = domElement.classList; classes.remove(classes.item(classes.length - 1)); } function setNoData(domElement) { var noData = document.createElement('p'); noData.classList.add('b-nothing_here'); noData.innerText = getLocale() === 'ru' ? `Недостаточно данных` : `Insufficient data`; domElement.innerHTML = ''; domElement.appendChild(noData); domElement.style.textAlign = 'center'; domElement.style.color = '#7b8084'; domElement.style.marginTop = '15px'; } function appendShikiRating() { 'use strict'; var urlpart = window.location.pathname.substring(0, 7); log(urlpart); if (!needAddRating(urlpart)) { log('wrong page'); return; } if (document.querySelector("#shiki-score") !== null) { log('already created'); return; } if (document.querySelector(".scores > .b-rate") === null) { log("can't find default rating"); return; } // get current rating element var malRate = document.querySelector(".scores > .b-rate"); malRate.setAttribute('id', 'mal-score'); // clone it to new element var newShikiRate = malRate.cloneNode(true); newShikiRate.setAttribute('id', 'shiki-score'); // append cloned rating to parent container var rateContainer = document.querySelector(".scores"); rateContainer.appendChild(newShikiRate); // load scores stats var scoreDataJson = document.querySelector("#rates_scores_stats").getAttribute("data-stats"); var scoreData = JSON.parse(scoreDataJson); log(scoreDataJson); // set no data lable if (scoreData.length === 0) { setNoData(newShikiRate); return; } // calculate shiki rating var sumScore = 0; var totalCount = 0; for (var i = 0; i < scoreData.length; i++) { sumScore += scoreData[i][1] * scoreData[i][0]; totalCount += scoreData[i][1] * 1; } var shikiScore = sumScore / totalCount; var shikiScoreDigit = Math.round(shikiScore); log(shikiScore); // set number value var scoreElement = newShikiRate.querySelector("div.text-score > div.score-value"); scoreElement.innerHTML = shikiScore.toFixed(2); removeLastClass(scoreElement); scoreElement.classList.add("score-" + shikiScoreDigit); // set stars calue var starElement = newShikiRate.querySelector("div.stars-container > div.stars.score"); removeLastClass(starElement); starElement.style.color = '#456'; starElement.classList.add("score-" + shikiScoreDigit); // load labels var labelData = getLocale() === 'ru' ? { "0": "", "1": "Хуже некуда", "2": "Ужасно", "3": "Очень плохо", "4": "Плохо", "5": "Более-менее", "6": "Нормально", "7": "Хорошо", "8": "Отлично", "9": "Великолепно", "10": "Эпик вин!" } : { "0": "", "1": "Worst Ever", "2": "Terrible", "3": "Very Bad", "4": "Bad", "5": "So-so", "6": "Fine", "7": "Good", "8": "Excellent", "9": "Great", "10": "Masterpiece!" }; // set label under score newShikiRate.querySelector("div.text-score > div.score-notice").textContent = labelData[shikiScoreDigit]; // set mal description label var malLabel = getLocale() === 'ru' ? 'На основе оценок mal' : 'From MAL users'; malRate.insertAdjacentHTML('afterend', '<p class="score-source">' + malLabel + '</p>'); // set shiki description label var shikiCountLabel = '<strong>' + totalCount + '</strong>'; shikiCountLabel = (getLocale() === 'ru') ? 'На основе ' + shikiCountLabel + ' оценок shiki' : 'From ' + shikiCountLabel + ' shiki users'; newShikiRate.insertAdjacentHTML('afterend', '<p class="score-counter">' + shikiCountLabel + '</p>'); // set style for mal description label var malScoreLabelElement = document.querySelector('.score-source'); malScoreLabelElement.style.marginBottom = '15px'; malScoreLabelElement.style.textAlign = 'center'; malScoreLabelElement.style.color = '#7b8084'; // set style for shiki description label var shikiScoreLabelElement = document.querySelector('.score-counter'); shikiScoreLabelElement.style.textAlign = 'center'; shikiScoreLabelElement.style.color = '#7b8084'; } 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(appendShikiRating); //Shikimori more comments loader by BoberMod const commentsPerClick = 100; //НЕ СТОИТ ДЕЛАТЬ ЗНАЧЕНИЕ БОЛЬШЕ 150-200! function change() { "use strict"; let loader = document.getElementsByClassName("comments-loader")[0]; if (!loader) { return false; } let numberOfComments = loader.getAttribute("data-count"); let skipLink = loader.getAttribute("data-clickloaded-url-template").replace(/SKIP\/\d{1,2}/g, `SKIP/${commentsPerClick}`); loader.setAttribute("data-limit", commentsPerClick); loader.setAttribute("data-clickloaded-url-template", skipLink); if (numberOfComments <= commentsPerClick) { loader.innerText = `Загрузить ${numberOfComments} из ${numberOfComments} комментариев`; } else { loader.innerText = `Загрузить ещё ${commentsPerClick} из ${numberOfComments} комментариев`; } } ready(change); //