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})); });