NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name Shikimori rating based on users scores
// @namespace https://shikimori.one/
// @description This script displays on the page rating of anime, manga or light novel, calculated by Shikimori users scores
// @version 1.0.6
// @author Nexus <https://shikimori.one/Nexus>
// @match https://shikimori.org/*
// @match https://shikimori.one/*
// @grant none
// @updateURL https://openuserjs.org/meta/Nexus/Shikimori_rating_based_on_users_scores.meta.js
// @downloadURL https://openuserjs.org/install/Nexus/Shikimori_rating_based_on_users_scores.user.js
// @copyright 2022, Nexus (https://openuserjs.org/users/Nexus)
// @license MIT
// ==/UserScript==
// Configuration
var DEFAULT_LOCALE = 'ru';
var FALLBACK_LOCALE = 'en';
var AVAILABLE_PAGES = ['animes', 'mangas', 'ranobe'];
var locales = {
ru: {
errors: {
'no-data': 'Недостаточно данных',
},
'based-on-scores': {
mal: 'На основе оценок MAL',
local: 'На основе :number оценок Shikimori'
},
scores: {
1: 'Хуже некуда',
2: 'Ужасно',
3: 'Очень плохо',
4: 'Плохо',
5: 'Более-менее',
6: 'Нормально',
7: 'Хорошо',
8: 'Отлично',
9: 'Великолепно',
10: 'Эпик вин!'
}
},
en: {
errors: {
'no-data': 'Not enough data',
},
'based-on-scores': {
mal: 'Based on MAL scores',
local: 'Based on :number Shikimori users scores'
},
scores: {
1: 'Worst Ever',
2: 'Terrible',
3: 'Very Bad',
4: 'Bad',
5: 'So-so',
6: 'Fine',
7: 'Good',
8: 'Excellent',
9: 'Great',
10: 'Masterpiece!'
}
},
};
// =====
var CUSTOM_RATING_CONTAINER_ID = 'rating-by-users-score';
var availableLocales = Object.keys(locales);
function getLocale() {
var locale = String(
document.querySelector('body').getAttribute('data-locale')
).trim().toLowerCase();
return (locale && availableLocales.indexOf(locale) >= 0) ? locale : DEFAULT_LOCALE;
}
function trans(key, replace, locale) {
var originalKey = key;
key = String(key || '');
replace = (typeof replace === 'object' && !Array.isArray(replace)) ? replace : {};
locale = String(locale || getLocale());
var section = key.split('.');
if (section.length === 2) {
key = section[1];
section = section[0];
} else {
section = null;
}
var messages = locales[locale];
var sectionMessages = section ? messages[section] : messages;
if (key in sectionMessages) {
var result = sectionMessages[key];
Object.keys(replace).forEach(function (key) {
result = result.replace(':' + key, replace[key]);
});
return result;
}
if (locale !== FALLBACK_LOCALE) {
return trans(originalKey, replace, FALLBACK_LOCALE);
}
return key;
}
function makeElement(message, className, tag) {
var element = document.createElement(tag || 'p');
element.className = className;
element.textContent = message;
return element;
}
function injectErrorMessage(parent, message, shouldClearContainer, tag) {
var container = makeElement(message, 'b-nothing_here', tag);
container.style.textAlign = 'center';
container.style.color = '#7b8084';
container.style.marginTop = '15px';
if (shouldClearContainer !== false) {
parent.innerHTML = '';
}
parent.appendChild(container);
}
function injectCaption(container, message, tag) {
var destinationContainer = container.querySelector('.stars-container');
var messageContainer = makeElement(message, 'score-notice score-source', tag || 'i');
messageContainer.style.display = 'block';
messageContainer.style.whiteSpace = 'normal';
messageContainer.style.marginTop = '-6px';
messageContainer.style.textAlign = 'left';
messageContainer.style.color = '#7b8084';
messageContainer.style.fontSize = '10px';
if (destinationContainer.offsetWidth) {
messageContainer.style.maxWidth = destinationContainer.offsetWidth + 'px';
}
destinationContainer.appendChild(messageContainer);
}
function jsonParseSilent(json) {
try {
if (typeof json !== 'string') {
return null;
}
return JSON.parse(json);
} catch (e) {
return null;
}
}
function updateRatingDetails(container, newScore) {
var changeScoreClasses = function (container) {
container.className = container.className.replace(/score-\d/i, 'score-' + Math.round(newScore));
};
var scoreContainer = container.querySelector('.text-score');
var scoreValueContainer = scoreContainer.querySelector('.score-value');
if (scoreValueContainer) {
scoreValueContainer.textContent = newScore;
changeScoreClasses(scoreValueContainer);
}
var scoreTextRepresentationContainer = scoreContainer.querySelector('.score-notice');
if (scoreTextRepresentationContainer) {
scoreTextRepresentationContainer.textContent = newScore ? trans('scores.' + Math.round(newScore)) : '';
}
var scoreStarsContainer = container.querySelector('.stars-container .stars.score');
if (scoreStarsContainer) {
scoreStarsContainer.style.color = '#456';
changeScoreClasses(scoreStarsContainer);
}
}
function onReady(callable) {
document.addEventListener('page:load', callable);
document.addEventListener('turbolinks:load', callable);
if (document.readyState !== 'loading') {
callable();
} else {
document.addEventListener('DOMContentLoaded', callable);
}
}
onReady(function () {
'use strict';
if (!window || !window.location) {
return; // invalid environment
}
var uriFragments = String(window.location.pathname || '').split('/').map(function (fragment) {
return fragment.trim();
}).filter(Boolean);
if (AVAILABLE_PAGES.indexOf(uriFragments[0]) === -1) {
return;
}
var customRatingContainer = document.getElementById(CUSTOM_RATING_CONTAINER_ID);
var defaultRatingContainer = document.querySelector('.scores > .b-rate');
var scoresContainer = document.getElementById('rates_scores_stats');
var scoresData = jsonParseSilent(
scoresContainer ? scoresContainer.getAttribute('data-stats') : null
);
if (customRatingContainer || !defaultRatingContainer || !scoresData) {
return;
}
customRatingContainer = defaultRatingContainer.cloneNode(true);
customRatingContainer.id = CUSTOM_RATING_CONTAINER_ID;
customRatingContainer.style.marginTop = '15px';
defaultRatingContainer.parentNode.appendChild(customRatingContainer);
if (!Array.isArray(scoresData) || !scoresData.length) {
injectErrorMessage(customRatingContainer, trans('errors.no-data'));
return;
}
var totalScore = 0, totalVotesNumber = 0;
scoresData.forEach(function (item) {
totalScore += +item[1] * +item[0];
totalVotesNumber += +item[1];
});
var score = (totalScore / totalVotesNumber).toFixed(2);
score = score < 0 ? 0 : score;
updateRatingDetails(customRatingContainer, score);
injectCaption(defaultRatingContainer, trans('based-on-scores.mal'));
injectCaption(customRatingContainer, trans('based-on-scores.local', {number: totalVotesNumber}));
});