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.2.9
// @description Добавляет новую статистику в различных местах (история просмотра конкретного тайтла, список эпизодов у аниме, статистика хронологии и пр.)
// @author Chortowod
// @match *://shikimori.org/*
// @match *://shikimori.one/*
// @match *://shikimori.me/*
// @match *://shiki.one/*
// @match *://shikimori.io/*
// @icon https://www.google.com/s2/favicons?domain=shikimori.me&sz=64
// @license MIT
// @connect api.jikan.moe
// @copyright 2025, 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 GM_xmlhttpRequest
// ==/UserScript==
let siteName = window.location.origin;
let settings = new ChtwSettings('chtwVarStat', '<a target="_black" href="https://openuserjs.org/scripts/Chortowod/Shikimori_Various_Statistic">Разная статистика</a>');
let debug;
function initStyle() {
let dateWidth = settings.getOption('historyDateWidth') || 115;
let epWrapBackground = settings.getOption('epWrapBackground') || '#0000';
let epWrapPadding = settings.getOption('epWrapPadding') ? '10' : '0';
let style = `
.chtw-ep-wrap {
display: flex;
gap: 10px;
}
.chtw-ep-number, .chtw-ep-score {
width: 30px;
}
.chtwEpisodesDateWatchedWrapper .chtw-watched-body, .chtwEpisodesDateWatchedWrapper .chtw-watched-detail {
padding-left: ${epWrapPadding}px;
background: ${epWrapBackground};
}
.chtw-ep-date {
width: ${dateWidth}px;
}
.chtw-ep-title {
width: 60%;
}
.chtw-ep-filler {
color: #7e7e7e;
}
.chtw-ep-recap {
color: #663a02;
}
.chtw-ep-great {
color: #00d600;
}
.chtw-ep-epic {
color: #00d600;
font-weight: bold;
}
.chtw-ep-good {
color: #86d600;
}
.chtw-ep-bad {
color: #e81c00;
}
.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;
}
#chtwEpisodesWatchedButtonShowAll {
margin-bottom: 10px;
}
#chtwWatchedButtonShowAll, #chtwEpisodesWatchedButtonShowAll {
color: var(--link-color);
text-align: center;
width: 100%;
}
#chtwWatchedButtonShowAll:hover, #chtwEpisodesWatchedButtonShowAll: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);
}
#chtwEpisodesShowMoreButton {
background: #1c1c1c;
padding: 2px 10px;
border-radius: 5px;
}
#chtwEpisodesShowMoreButton:hover {
background: #3b3b3b;
}
@media screen and (max-width: 768px) {
.chtw-ep-title {
width: 40%;
}
.chtw-ep-wrap {
gap: 5px;
}
}
`;
settings.addStyle(style);
}
function initSettings() {
settings.createOption('history', 'История просмотра тайтла', true);
settings.createOption('avTitleFr', 'Средн. оценка тайтла друзей', true);
settings.createOption('avTitle', 'Средн. оценка тайтла', true);
settings.createOption('avProfileFr', 'Средняя оценка друга', true);
settings.createOption('allProfileFr', 'Всего тайтлов друга', true);
settings.createOption('showEpisodes', 'Показать список эпизодов', true);
settings.createOption('historyDateWidth', 'Ширина блока с датой эпизода (px)', '115', 'number');
settings.createOption('epWrapBackground', 'Цвет блока с рейтингом эпизодов', '#0000', 'color');
settings.createOption('epWrapPadding', 'Отступ слева у блока с рейтингом эпизодов', false);
settings.createOption('isDebug', 'Режим отладки', false);
debug = settings.getOption('isDebug');
}
function log (message) { debug&&console.log(message) }
function getLocale() {
return document.querySelector('body').getAttribute('data-locale');
}
function formatTime(time) {
// часы
if (!time) {
return '---';
}
let result = time / 60;
if (result > 24) {
result /= 24;
if (result > 30) {
return (result/30).toFixed(2)+' месяцев';
}
else {
return result.toFixed(2)+' дней';
}
}
else {
return result.toFixed(2)+' часов';
}
}
function appendChronologyStat(stat) {
let notInStatString = '';
stat.notInStat.forEach((anime) => {
let name = anime.russian ? anime.russian : anime.name;
notInStatString += `<p><a class="b-link bubbled-processed" style="text-decoration: none;" href="/animes/${anime.id}">${name}</a></p>`;
});
let parentElement = $('.b-animes-menu');
let newStatElement = document.createElement('div');
newStatElement.classList.add('block');
newStatElement.classList.add('chtw-chronology-stats');
newStatElement.innerHTML = `<div class="subheadline m8">Статистика</div>
<div><b>Всего времени</b>: ${formatTime(stat.minutesOverall)} (${stat.overall} шт.)</div>
<div><b>Просмотрено</b>: ${formatTime(stat.minutesWatched)} (${stat.watched.length} шт.)</div>
<div><b>Не просмотрено</b>: ${formatTime(stat.minutesToWatch)} (${stat.toWatch.length} шт.)</div>
<div><b>Смотрю</b>: ${formatTime(stat.minutesWatching)} (${stat.watching.length} шт.)</div>
<div><b>Не учтено в статистике</b> (${stat.notInStat.length} шт.):</div>
<details style="cursor: pointer">${notInStatString}</details>
`;
parentElement.prepend(newStatElement);
}
function calculateTime(animes) {
let stat = [];
stat.minutesToWatch = 0;
stat.minutesWatched = 0;
stat.minutesWatching = 0;
stat.overall = animes.length;
stat.toWatch = [];
stat.watched = [];
stat.watching = [];
stat.notInStat = [];
animes.forEach((anime) => {
let episodes = anime.episodes ? anime.episodes : anime.episodesAired;
if (!anime.duration || !episodes) {
stat.notInStat.push(anime);
}
else if (anime.status === 'completed') {
stat.minutesWatched += anime.duration * episodes;
stat.watched.push(anime);
}
else if (anime.status === 'watching') {
stat.minutesWatching += anime.duration * episodes;
stat.watching.push(anime);
}
else {
stat.minutesToWatch += anime.duration * episodes;
stat.toWatch.push(anime);
}
});
stat.minutesOverall = stat.minutesWatched + stat.minutesToWatch + stat.minutesWatching;
return stat;
}
function showChronologyStat() {
if (!$('body').hasClass('p-animes-chronology') || $('.chtw-chronology-stats').length) {
return;
}
let chrBlock = $(".b-db_entry-variant-list_item");
let all = chrBlock.length;
let counterAll = all;
let ongoing = 0;
let anons = 0;
let released = 0;
let other = 0;
let none = 0;
let IDs = [];
let animes = [];
chrBlock.each(function (i, item) {
let titleID = item.dataset.id;
IDs.push(titleID);
let infoBlock = $(' div.b-anime_status_tag', item);
if (infoBlock) {
if (infoBlock.hasClass('anons')) {
anons++;
}
else if (infoBlock.hasClass('ongoing')) {
ongoing++;
}
else if (infoBlock.hasClass('released')) {
released++;
}
else {
other++;
}
}
else {
none++;
}
});
while (IDs.length > 0) {
let toFetchIDs = '';
if (IDs.length > 50) {
toFetchIDs = IDs.slice(0, 50);
IDs = IDs.slice(50, IDs.length);
}
else {
toFetchIDs = IDs;
IDs = [];
}
let stringIDs = toFetchIDs.join(',');
fetch(siteName+'/api/graphql', { method: 'POST', headers: {"Content-Type": "application/json"}, body: JSON.stringify({query: `{animes(limit: 50, ids: "${stringIDs}") {id name russian duration episodes episodesAired}}`})})
.then(res => res.json())
.then(res => {
if (res && res.data && res.data.animes) {
let animesFetched = res.data.animes;
if (animesFetched.length === toFetchIDs.length) {
animes = animes.concat(animesFetched);
}
}
counterAll -= toFetchIDs.length;
})
}
let countdownPing = 30;
let countdownResolver = setInterval(function(){
if (counterAll === 0) {
clearInterval(countdownResolver);
console.log(animes);
console.log(all, ongoing, anons, released, other, none);
for (let anime of animes) {
anime.status = $(`.b-db_entry-variant-list_item[data-id="${anime.id}"] .b-user_rate input[name="user_rate[status]"]`).val();
}
let stat = calculateTime(animes);
appendChronologyStat(stat);
console.log(stat);
//todo
}
else if (countdownPing === 0) {
clearInterval(countdownResolver);
// whatAppend.getElementsByClassName('cc')[0].innerText = 'Произошла ошибка при загрузке. Обновите страницу или посмотрите причину ошибки в консоли разработчика.';
console.log("Ошибка при fetch, не совпало количество полученных анимешек с теми, что на странице.");
}
else {
countdownPing--;
}
}, 250);
}
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 getEpisodeColorClass(episode) {
if (episode.filler == true) {
return "chtw-ep-filler";
}
else if (episode.recap == true) {
return "chtw-ep-recap";
}
else if (episode.score >= 4.9) {
return "chtw-ep-epic";
}
else if (episode.score > 4.8) {
return "chtw-ep-great";
}
else if (episode.score > 4.6) {
return "chtw-ep-good";
}
else if (episode.score < 4) {
return "chtw-ep-bad";
}
return "";
}
function getAndAppendBatchEpisodes(animeID, page = 1) {
let url = `https://api.jikan.moe/v4/anime/${animeID}/episodes?page=${page}`;
GM_xmlhttpRequest( {
method:"GET", url:url, onload:function(response) {
if (response && response.responseText) {
let respData = JSON.parse(response.responseText);
if (!respData.data || respData.data.length === 0) {
return;
}
let episodesData = respData.data;
let pagination = respData.pagination;
if (page === 1) {
let dateTitle = (getLocale() === 'ru') ? 'Эпизоды' : 'Episodes';
let chtwEpisodesBlock = document.createElement('div');
chtwEpisodesBlock.id = 'chtwEpisodesDateWatchedWrapper';
chtwEpisodesBlock.classList.add("block");
chtwEpisodesBlock.style.fontSize = '12px';
let chtwEpisodesBlockHeader = document.createElement('div');
chtwEpisodesBlockHeader.classList.add("subheadline");
chtwEpisodesBlockHeader.innerHTML = dateTitle;
let chtwEpisodesBlockBody = document.createElement('div');
chtwEpisodesBlockBody.classList.add("chtw-watched-body");
let chtwEpisodesBlockDetail = document.createElement('div');
chtwEpisodesBlockDetail.classList.add("chtw-watched-detail");
let chtwEpisodesBlockDetailHeader = document.createElement('div');
chtwEpisodesBlockDetailHeader.src = '-';
chtwEpisodesBlockDetail.append(chtwEpisodesBlockDetailHeader);
chtwEpisodesBlock.append(chtwEpisodesBlockHeader);
chtwEpisodesBlock.append(chtwEpisodesBlockBody);
chtwEpisodesBlock.append(chtwEpisodesBlockDetail);
let iLength = episodesData.length > 12 ? 12 : episodesData.length;
let dataText = `<span class="chtw-ep-number">Эп.</span><span class="chtw-ep-title">Название</span><span class="chtw-ep-date">Дата выхода</span><span class="chtw-ep-score">Оценка</span>`;
let newEntry = document.createElement('div');
newEntry.classList.add("chtw-ep-wrap");
newEntry.innerHTML = dataText;
chtwEpisodesBlockBody.append(newEntry);
for (let i = 0; i < iLength; i++) {
let newEntry = createNewEpisodeEntry(episodesData[i], i)
chtwEpisodesBlockBody.append(newEntry);
}
if (episodesData.length > 12) {
let chtwEpisodesBlockShowDetail = document.createElement('button');
chtwEpisodesBlockShowDetail.id = 'chtwEpisodesWatchedButtonShowAll';
chtwEpisodesBlockShowDetail.innerHTML = (getLocale() === 'ru') ? 'показать/скрыть подробнее' : 'show/hide details';
chtwEpisodesBlock.append(chtwEpisodesBlockShowDetail);
for (let i = 12; i < episodesData.length; i++) {
let newEntry = createNewEpisodeEntry(episodesData[i])
chtwEpisodesBlockDetail.append(newEntry);
}
if (pagination.has_next_page === true) {
let chtwEpisodesShowMoreButton = document.createElement('button');
chtwEpisodesShowMoreButton.id = 'chtwEpisodesShowMoreButton';
chtwEpisodesShowMoreButton.innerHTML = (getLocale() === 'ru') ? 'подгрузить ещё' : 'load more';
chtwEpisodesBlockDetail.append(chtwEpisodesShowMoreButton);
}
}
$(chtwEpisodesBlock).insertAfter(document.getElementsByClassName("b-db_entry")[0]);
$('#chtwEpisodesWatchedButtonShowAll').on('click', function () {
$(this).prev().slideToggle();
});
if (pagination.has_next_page === true) {
$('#chtwEpisodesShowMoreButton').on('click', function () {
getAndAppendBatchEpisodes(animeID, page+1);
});
}
}
else {
let chtwEpisodesBlockDetail = $('#chtwEpisodesDateWatchedWrapper .chtw-watched-detail');
for (let i = 0; i < episodesData.length; i++) {
let newEntry = createNewEpisodeEntry(episodesData[i])
chtwEpisodesBlockDetail.append(newEntry);
}
$('#chtwEpisodesShowMoreButton').remove();
if (pagination.has_next_page === true) {
let chtwEpisodesShowMoreButton = document.createElement('button');
chtwEpisodesShowMoreButton.id = 'chtwEpisodesShowMoreButton';
chtwEpisodesShowMoreButton.innerHTML = (getLocale() === 'ru') ? 'подгрузить ещё' : 'load more';
chtwEpisodesBlockDetail.append(chtwEpisodesShowMoreButton);
$('#chtwEpisodesShowMoreButton').on('click', function () {
getAndAppendBatchEpisodes(animeID, page+1);
});
}
}
}
else {
log('Данных не найдено.');
log(response);
}
}
})
}
function createNewEpisodeEntry(episodeData) {
let dateOptions = { year: 'numeric', month: 'long', day: 'numeric' };
let dateLocale = (getLocale() === 'ru') ? 'ru-RU' : 'en-GB';
let score = episodeData.score || '???';
let date = episodeData.aired ? new Date(episodeData.aired) : 'N/A';
if (date !== 'N/A') {
date = date.toLocaleDateString(dateLocale, dateOptions);
}
let dataText = `<span class="chtw-ep-number">#${episodeData.mal_id}</span><span class="chtw-ep-title">${episodeData.title}</span><span class="chtw-ep-date">${date}</span><span class="chtw-ep-score">${score}</span>`;
let newEntry = document.createElement('div');
newEntry.innerHTML = dataText;
newEntry.classList.add("chtw-ep-wrap");
let dataClass = getEpisodeColorClass(episodeData);
if (dataClass) {
newEntry.classList.add(dataClass);
}
return newEntry;
}
function showEpisodesRating() {
if (!settings.getOption('showEpisodes')) return;
let currentPath = window.location.pathname.substring(0, 7);
if (currentPath !== "/animes" || document.getElementById('chtwEpisodesDateWatchedWrapper')) {
return;
}
let id = $('.c-image > .b-user_rate').data('target_id');
getAndAppendBatchEpisodes(id);
}
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(initStyle);
ready(appendAverageRating);
ready(appendOverallTitles);
ready(appendAverageFriendsRating);
ready(appendAverageTitleRating);
ready(showWatchHistory);
ready(showChronologyStat);
ready(showEpisodesRating);