NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Shikimori Various Statistic // @namespace http://shikimori.me/ // @version 1.1.9 // @description Добавляет новую статистику в различных местах (история просмотра конкретного тайтла, средняя оценка всех аниме, общее количество тайтлов и пр.) // @author Chortowod // @match *://shikimori.org/* // @match *://shikimori.one/* // @match *://shikimori.me/* // @icon https://www.google.com/s2/favicons?domain=shikimori.me // @license MIT // @copyright 2024, Chortowod (https://openuserjs.org/users/Chortowod) // @updateURL https://openuserjs.org/meta/Chortowod/Shikimori_Various_Statistic.meta.js // @downloadURL https://openuserjs.org/install/Chortowod/Shikimori_Various_Statistic.user.js // @require https://gist.githubusercontent.com/Chortowod/814b010c68fc97e5f900df47bf79059c/raw/chtw_settings.js?v1 // @grant none // ==/UserScript== let siteName = "https://shikimori.one"; let settings = new ChtwSettings('chtwVarStat', '<a target="_black" href="https://openuserjs.org/scripts/Chortowod/Shikimori_Various_Statistic">Разная статистика</a>'); let debug; let style = ` .chtw-watched-item { display: flex; justify-content: space-between; } .chtw-watched-detail { display: none; } .chtw-watched-detail > .chtw-watched-item { justify-content: space-between; flex-direction: column; align-items: center; border-bottom: 1px solid #7f7f7f38; text-align: center; } .chtw-watched-detail > .chtw-watched-item:last-child { border: none; } #chtwWatchedButtonShowAll { color: var(--link-color); text-align: center; width: 100%; } #chtwWatchedButtonShowAll:hover { color: var(--link-hover-color); } .chtw-watched-detail-header { text-align: center; font-weight: bold; } .chtw-watched-detail-header { margin-bottom: 8px; margin-top: 10px; display: flex; justify-content: center; align-items: center; } .chtw-watched-detail-header::before, .chtw-watched-detail-header::after { content: ""; display: block; height: 2px; min-width: 50%; } .chtw-watched-detail-header::before { background: linear-gradient(to right, rgba(240, 240, 240, 0), #7b8084); } .chtw-watched-detail-header::after { background: linear-gradient(to left, rgba(240, 240, 240, 0), #7b8084); } `; function initSettings() { settings.createOption('history', 'История просмотра тайтла', true); settings.createOption('avTitleFr', 'Средн. оценка тайтла друзей', true); settings.createOption('avTitle', 'Средн. оценка тайтла', true); settings.createOption('avProfileFr', 'Средняя оценка друга', true); settings.createOption('allProfileFr', 'Всего тайтлов друга', true); settings.createOption('isDebug', 'Режим отладки', false); debug = settings.getOption('isDebug'); settings.addStyle(style); } function log (message) { debug&&console.log(message) } function getLocale() { return document.querySelector('body').getAttribute('data-locale'); } function findCompletedEntries(entries, type, lang) { let watchedEntries = []; let watchedMatch1 = (type === 'Anime') ? 'Просмотрено' : 'Прочитано'; let watchedMatch2 = (type === 'Anime') ? 'Просмотрено и оценено' : 'Прочитано и оценено'; let watchedMatchEn = 'Completed'; if (lang === 'ru') { entries.forEach(function (entry) { if (entry.description === watchedMatch1 || entry.description.includes(watchedMatch2)) { watchedEntries.push(entry); } }); } else { entries.forEach(function (entry) { if (entry.description.includes(watchedMatchEn)) watchedEntries.push(entry); }); } return watchedEntries.reverse(); } function showWatchHistory() { if (!settings.getOption('history')) return; let currentPath = window.location.pathname.substring(0, 7); if (document.getElementById('chtwDateWatchedWrapper') || !(currentPath === "/animes" || currentPath === "/mangas" || currentPath === "/ranobe")) { log('Неподходящая страница или блок уже есть'); return; } let targetType = (currentPath === "/animes") ? 'Anime' : 'Manga'; let id = $('.c-image > .b-user_rate').data('target_id'); if (id) { let profileID = $('body').data('user').id; let url = siteName+'/api/users/'+profileID+'/history?target_id='+id+'&limit=100&target_type='+targetType; $.ajax({ url: url, success: function(data) { if (data.length === 0) { log('Данных не найдено.'); } else { let completedEntries = findCompletedEntries(data, targetType, getLocale()); let dateOptions = { year: 'numeric', month: 'long', day: 'numeric' }; let dateLocale = (getLocale() === 'ru') ? 'ru-RU' : 'en-GB'; let dateTitle = (getLocale() === 'ru') ? 'История' : 'History'; let parentBlock = $('.b-animes-menu'); let chtwHistoryBlock = document.createElement('div'); chtwHistoryBlock.id = 'chtwDateWatchedWrapper'; chtwHistoryBlock.classList.add("block"); chtwHistoryBlock.style.fontSize = '12px'; let chtwHistoryBlockHeader = document.createElement('div'); chtwHistoryBlockHeader.classList.add("subheadline"); chtwHistoryBlockHeader.innerHTML = dateTitle; let chtwHistoryBlockBody = document.createElement('div'); chtwHistoryBlockBody.classList.add("chtw-watched-body"); let chtwHistoryBlockShowDetail = document.createElement('button'); chtwHistoryBlockShowDetail.id = 'chtwWatchedButtonShowAll'; chtwHistoryBlockShowDetail.innerHTML = (getLocale() === 'ru') ? 'показать/скрыть подробнее' : 'show/hide details'; let chtwHistoryBlockDetail = document.createElement('div'); chtwHistoryBlockDetail.classList.add("chtw-watched-detail"); let chtwHistoryBlockDetailHeader = document.createElement('div'); chtwHistoryBlockDetailHeader.classList.add("chtw-watched-detail-header"); chtwHistoryBlockDetailHeader.src = '-'; chtwHistoryBlockDetail.append(chtwHistoryBlockDetailHeader); chtwHistoryBlock.append(chtwHistoryBlockHeader); chtwHistoryBlock.append(chtwHistoryBlockBody); chtwHistoryBlock.append(chtwHistoryBlockDetail); chtwHistoryBlock.append(chtwHistoryBlockShowDetail); completedEntries.forEach(function (entry, index) { let textLocale = ((getLocale() === 'ru') ? ((entry.target.url.slice(0,6) === '/anime') ? 'Просмотрено' : 'Прочитано') : 'Completed'); let dataText = new Date(entry.created_at); let dateRus = dataText.toLocaleDateString(dateLocale, dateOptions); let newEntry = document.createElement('div'); newEntry.classList.add("chtw-watched-item"); let newEntryText = document.createElement('div'); newEntryText.innerHTML = textLocale+' #'+(index+1)+': '; let newEntryDate = document.createElement('div'); newEntryDate.innerHTML = dateRus; newEntry.append(newEntryText); newEntry.append(newEntryDate); chtwHistoryBlockBody.append(newEntry); }); data.forEach(function (entry) { let dataText = new Date(entry.created_at); let dateRus = dataText.toLocaleDateString(dateLocale, dateOptions); let newEntry = document.createElement('div'); newEntry.classList.add("chtw-watched-item"); let newEntryDate = document.createElement('div'); newEntryDate.innerHTML = dateRus; let newEntryText = document.createElement('div'); newEntryText.innerHTML = entry.description; newEntry.append(newEntryDate); newEntry.append(newEntryText); chtwHistoryBlockDetail.append(newEntry); }); parentBlock.prepend(chtwHistoryBlock); $('#chtwWatchedButtonShowAll').on('click', function () { $(this).prev().slideToggle(); }); } }, error: function(XMLHttpRequest, textStatus, errorThrown) { console.log("Status: " + textStatus + " | Error: " + errorThrown); }, complete: function() { } }); } } function calculateAverageScore(scoreData) { let sumScore = 0; let totalCount = 0; for (let i = 0; i < scoreData.length; i++) { if (scoreData[i].name) { sumScore += parseInt(scoreData[i].value) * scoreData[i].name; totalCount += parseInt(scoreData[i].value); } else { sumScore += parseInt(scoreData[i][1]) * scoreData[i][0]; totalCount += parseInt(scoreData[i][1]); } } return (sumScore / totalCount).toFixed(2); } function calculateAllTitles(scoreData) { let totalCount = 0; for (let i = 0; i < scoreData.length; i++) totalCount += parseInt(scoreData[i].value); return totalCount; } function calculateAverageFriendsRating() { var friendRate = document.querySelectorAll("div[class*=friend-rate] div[class=status]"); var friendRateStatus = []; var sum = 0; var avg = 0; var count = 0; for (var i = friendRate.length - 1; i >= 0; i--) { friendRateStatus[i] = friendRate[i].innerText.replace(/[^-0-9]/gim,''); if (friendRateStatus[i] !== "") { sum += Number(friendRateStatus[i]); count++; } } let result = sum / count; return result ? ((result % 1 === 0) ? result : result.toFixed(2)) : false; } function appendAverageFriendsRating() { if (!settings.getOption('avTitleFr')) return; let element = document.querySelector(".block > .friend-rate"); log(element); if (!element) return; let data = calculateAverageFriendsRating(); log(data); if (data) element.previousSibling.innerHTML = (getLocale() === 'ru') ? 'У друзей (средняя: '+data+')' : 'Friends (average: '+data+')'; } function appendAverageRating() { if (!settings.getOption('avProfileFr')) return; let element = document.querySelector(".p-user_rates.p-profiles .mini-charts > .scores > #scores"); if (!element) return; element = JSON.parse(element.getAttribute("data-stats")); if (!element || !element.length) return; element = calculateAverageScore(element); if (element) document.querySelector(".p-user_rates.p-profiles .mini-charts > .scores > div.subheadline").innerHTML = (getLocale() === 'ru') ? 'Оценки (средняя: '+element+')' : 'Scores (average: '+element+')'; } function appendAverageTitleRating() { if (!settings.getOption('avTitle')) return; let elementRates = document.querySelector("#rates_scores_stats"); if (!elementRates) return; let element = JSON.parse(elementRates.getAttribute("data-stats")); if (!element || !element.length) return; element = calculateAverageScore(element); if (element) elementRates.previousSibling.innerHTML = (getLocale() === 'ru') ? 'Оценки (средняя: '+element+')' : 'Scores (average: '+element+')'; } function appendOverallTitles() { if (!settings.getOption('allProfileFr')) return; let element = document.querySelector(".p-user_rates.p-profiles .mini-charts > .types > #types"); if (!element) return; element = JSON.parse(element.getAttribute("data-stats")); if (!element || !element.length) return; element = calculateAllTitles(element); if (element) document.querySelector(".p-user_rates.p-profiles .mini-charts > .types > div.subheadline").innerHTML = (getLocale() === 'ru') ? 'Типы (всего: '+element+')' : 'Kinds (overall: '+element+')'; } 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(initSettings); ready(appendAverageRating); ready(appendOverallTitles); ready(appendAverageFriendsRating); ready(appendAverageTitleRating); ready(showWatchHistory);