Chortowod / Shiki Elite Rating

// ==UserScript==
// @name         Shiki Elite Rating
// @author       Chortowod (https://openuserjs.org/users/Chortowod)
// @description  Рейтинг илитарности (больше старых тайтлов просмотрено = больше рейтинг)
// @namespace    http://shikimori.me/
// @version      1.2.0
// @match        *://shikimori.org/*
// @match        *://shikimori.one/*
// @match        *://shikimori.me/*
// @icon         https://www.google.com/s2/favicons?domain=shikimori.me
// @license      MIT
// @updateURL    https://openuserjs.org/meta/Chortowod/Shiki_Elite_Rating.meta.js
// @downloadURL  https://openuserjs.org/install/Chortowod/Shiki_Elite_Rating.user.js
// @require      https://gist.githubusercontent.com/Chortowod/814b010c68fc97e5f900df47bf79059c/raw/chtw_settings.js?v1
// @copyright    2023, Chortowod (https://openuserjs.org/users/Chortowod)
// ==/UserScript==

let settings = new ChtwSettings('chtwEliteRating', '<a target="_blank" href="https://openuserjs.org/scripts/Chortowod/Shiki_Elite_Rating">Илитарность</a>');
let debug;

function initSettings() {
    settings.createOption('isLeft', 'Расположить слева', false);
    settings.createOption('isDebug', 'Режим отладки', false);
    settings.createOption('top', 'Отступ сверху (в %)', 15, 'number');
    debug = settings.getOption('isDebug');
}

let style = `
        .chtw-elite-rating-wrap:hover { width: 220px; }
        .chtw-elite-rating-wrap {
            display: flex;
            color: initial;
            overflow: hidden;
            width: 50px;
            height: 50px;
            position: absolute;
            padding: 5px;
            ${settings.getOption('isLeft') ? 'left' : 'right'}: 0;
            ${settings.getOption('isLeft') ? 'transform: scale(-1, 1);' : ''};
            top: ${settings.getOption('top')}%;
            border-radius: 10px 0px 0px 10px;
            background: #000000ad;
            transition: 0.5s;
            align-items: center;
            z-index: 9;
        }
        .chtw-elite-rating-info {
            display: flex;
            height: 100%;
            width: 50px;
            min-width: 45px;
            font-size: 34px;
            align-items: center;
            justify-content: center;
            color: #ffbd70;
        }
        .chtw-elite-rating-data {
            min-width: 170px;
            color: #dbfdff;
            font-weight: bold;
            ${settings.getOption('isLeft') ? 'transform: scale(-1, 1);' : ''};
            display: flex;
            flex-direction: column;
            align-items: center;
        }
        .chtwERTitle {
            cursor: help;
        }
        `;
let skippedAnime = {'skipped_type': 'аниме', 'no_year': [], 'no_episodes': [], 'ongoing': []};
let skippedManga = {'skipped_type': 'манга', 'no_year': [], 'no_chapters': [], 'ongoing': []};
let siteURL = document.location.origin;

function log (message) { debug&&console.log(message) }

function getTitle(score) {
    switch (true) {
        case score < 0.5 : return 'Average Hype Fan';
        case score < 1 : return 'Average Hype Enjoyer';
        case score < 3 : return 'Улитка';
        case score < 5 : return 'Илита-кохай';
        case score < 10 : return 'Илитнячок';
        case score < 15 : return 'Илита';
        case score < 30 : return 'Илита-сенсей';
        case score < 50 : return 'Батя';
        case score < 100 : return 'Илитный сверхразум';
        case score >= 100 : return 'Дед-хранитель';
        default: return false;
    }
}

function createDiv(type = 'title') {
    if ($('#chtwEliteRating').length > 0) return;

    let ratingWrap = document.createElement('div');
    ratingWrap.id = 'chtwEliteRating';
    ratingWrap.classList.add('chtw-elite-rating-wrap');

    let ratingInfo = document.createElement('div');
    ratingInfo.classList.add('chtw-elite-rating-info');
    ratingInfo.innerHTML = '🛈';
    ratingWrap.append(ratingInfo);

    let ratingData = document.createElement('div');
    ratingData.id = 'chtwEliteRatingData';
    ratingData.classList.add('chtw-elite-rating-data');

    if (type === 'profile') {
        let ratingAnime = document.createElement('div');
        ratingAnime.innerHTML = '<span id="chtwERanimeTitle" class="chtwERTitle">Аниме: </span><span id="chtwERanime"><span style="cursor: pointer;">посчитать</span></span>';
        ratingData.append(ratingAnime);

        let ratingManga = document.createElement('div');
        ratingManga.innerHTML = '<span id="chtwERmangaTitle" class="chtwERTitle">Манга: </span><span id="chtwERmanga"><span style="cursor: pointer;">посчитать</span></span>';
        ratingData.append(ratingManga);
    }
    if (type === 'title') {
        ratingData.innerHTML = '<span style="cursor: pointer;">Посчитать</span></span>';
    }

    ratingWrap.append(ratingData);

    document.body.append(ratingWrap);

    if (type === 'profile') {
        $('#chtwERanime').on('click', function () {
            $(this).html('~загрузка');
            calculateAll('anime', 0, 1);
        });
        $('#chtwERmanga').on('click', function () {
            $(this).html('~загрузка');
            calculateAll('manga', 0, 1);
        });
    }
    if (type === 'title') {
        $('#chtwEliteRatingData').on('click', function () {
            $(this).html('~загрузка');
            showTitleRating();
        });
    }

    settings.addStyle(style);

}

function showTitleRating() {
    let id = $('.b-db_entry .b-user_rate').data('target_id');
    let type = location.href.includes("/animes/") ? 'animes' : 'mangas';
    $.ajax({
        url: siteURL+'/api/'+type+'/'+id,
        success: function(data) {
            if (data.length === 0) {
                document.getElementById('chtwEliteRatingData').innerHTML = 'Нет данных.';
            }
            else {
                if (type === 'animes') {
                    let titleEp = data.episodes || data.episodes_aired;
                    let titleData = data.aired_on;
                    let titleKind = data.kind;
                    let titleStatus = data.status;
                    if (!titleData) { document.getElementById('chtwEliteRatingData').innerHTML = 'Нет года.' }
                    else if (!titleEp) { document.getElementById('chtwEliteRatingData').innerHTML = 'Нет эпизодов.' }
                    else if (titleStatus === 'ongoing') { document.getElementById('chtwEliteRatingData').innerHTML = 'Онгоинг.' }
                    else { document.getElementById('chtwEliteRatingData').innerHTML = 'Стоимость: ' + parseFloat(calculateOne(titleData.substring(0, 4), 'anime', titleEp, titleKind).toFixed(4)); }
                }
                else {
                    let titleData = data.aired_on;
                    let titleChap = data.chapters;
                    let titleKind = data.kind;
                    if (!titleData) { document.getElementById('chtwEliteRatingData').innerHTML = 'Нет года.' }
                    else if (!titleChap) { document.getElementById('chtwEliteRatingData').innerHTML = 'Нет эпизодов.' }
                    else { document.getElementById('chtwEliteRatingData').innerHTML = 'Стоимость: ' + parseFloat(calculateOne(titleData.substring(0, 4), 'anime', titleChap, titleKind).toFixed(4)); }
                }
            }
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
            log('Произошла ошибка при загрузке. Обновите страницу или посмотрите причину ошибки в консоли разработчика.');
            log(XMLHttpRequest);
            log("Status: " + textStatus + " | Error: " + errorThrown);
        },
        complete: function() {

        }
    });
}

function calculateOne(year, type, episodes, kind = 'tv') {
    let currentYear = new Date().getFullYear();
    if (year >= currentYear) return 0;
    if (year < 1970) year = 1970;
    if (episodes > 120) episodes = 120;
    let coef = (parseInt(year) > 1999) ? 2 : 3;
    let result;

    if (type === 'anime') {
        if (kind === 'movie')
            result = (Math.pow(currentYear - year, coef))/(Math.pow(currentYear - 2000, coef) * 200);
        else
            result = (Math.pow(currentYear - year, coef) * episodes)/(Math.pow(currentYear - 2000, coef) * 1200);
    }
    else {
        if (kind === 'light_novel' || kind === 'novel')
            result = (Math.pow(currentYear - year, coef) * episodes)/(Math.pow(currentYear - 2000, coef) * 2500);
        else
            result = (Math.pow(currentYear - year, coef) * episodes)/(Math.pow(currentYear - 2000, coef) * 5000);
    }
    log(episodes);
    log(Math.pow(currentYear - year, coef) * episodes);
    log(Math.pow(currentYear - 2000, coef) * 1200);
    log(result)
    return result;
}

function initialize() {
    if ($('.p-profiles.p-profiles-show').length !== 0) {
        createDiv('profile');
    }
    else if (!location.href.includes("/animes/") && !location.href.includes("/mangas/") && !location.href.includes("/ranobe/")) return false;
    else if ($('.b-db_entry .b-user_rate').data('target_id')) {
        createDiv('title');
    }
}

function calculateAll(type, result, page) {
    let id = $('.profile-head').data('user-id');
    if (!id) return;
    $.ajax({
        url: siteURL+'/api/users/'+id+'/'+type+'_rates?limit=5000&status=completed&page='+page,
        success: function(data) {
            if (data === null) {
                log((type === 'anime') ? skippedAnime : skippedManga);
                document.getElementById('chtwER'+type+'Title').title = getTitle(result);
                document.getElementById('chtwER'+type).innerHTML = result ? result.toFixed(3) : 'мало данных.';
            }
            else if (data.length === 0) {
                document.getElementById('chtwER'+type).innerHTML = 'нет данных.';
            }
            else {
                data.forEach(function (item) {
                    if (type === 'anime') {
                        let titleEp = item[type].episodes;
                        let titleData = item[type].aired_on;
                        let titleKind = item[type].kind;
                        let titleStatus = item[type].status;
                        let titleRewatches = (item.rewatches) ? item.rewatches + 1 : 1;
                        if (!titleData) { skippedAnime.no_year.push(siteURL+'/animes/' + item[type].id) }
                        else if (!titleEp) { skippedAnime.no_episodes.push(siteURL+'/animes/' + item[type].id) }
                        else if (titleStatus === 'ongoing') { skippedAnime.ongoing.push(siteURL+'/animes/' + item[type].id) }
                        else { result += calculateOne(titleData.substring(0, 4), type, titleEp, titleKind) * titleRewatches; }
                    }
                    else {
                        let titleData = item[type].aired_on;
                        let titleChap = item[type].chapters;
                        let titleKind = item[type].kind;
                        let titleStatus = item[type].status;
                        let titleRewatches = (item.rewatches) ? item.rewatches + 1 : 1;
                        if (!titleData) { skippedManga.no_year.push(siteURL+'/mangas/' + item[type].id) }
                        else if (!titleChap) { skippedManga.no_chapters.push(siteURL+'/mangas/' + item[type].id) }
                        else if (titleStatus === 'ongoing') { skippedManga.ongoing.push(siteURL+'/mangas/' + item[type].id) }
                        else { result += calculateOne(titleData.substring(0, 4), type, titleChap, titleKind) * (titleRewatches > 5 ? 5 : titleRewatches); }
                    }
                });
                setTimeout(() => { calculateAll(type, result, page+1); }, 250);
            }
        },
        error: function(XMLHttpRequest, textStatus, errorThrown) {
            if (XMLHttpRequest.responseJSON && XMLHttpRequest.responseJSON.message === 'You are not authorized to access this page.') {
                log('Пользователь закрыл доступ к своей статистике.');
                document.getElementById('chtwER'+type).innerHTML = 'нет доступа.';
            }
            else {
                log('Произошла ошибка при загрузке. Обновите страницу или посмотрите причину ошибки в консоли разработчика.');
                document.getElementById('chtwER'+type).innerHTML = 'ошибка.';
                log(XMLHttpRequest);
                log("Status: " + textStatus + " | Error: " + errorThrown);
            }
        },
        complete: function() {

        }
    });
}

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(initialize);