flxemt / Shikiposter

// ==UserScript==
// @name         Shikiposter
// @namespace    https://shikimori.me/
// @downloadURL  https://openuserjs.org/install/flxemt/Shikiposter.user.js
// @updateURL    https://openuserjs.org/meta/flxemt/Shikiposter.meta.js
// @version      1.0.6
// @description  Better posters quality for anime and manga
// @author       FLX
// @match        https://shikimori.me/*
// @match        https://shikimori.one/*
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @license      MIT
// ==/UserScript==

const debug = false;
const enableCache = true; // if use false, posters cache also will be cleared when you refresh the page

function log(message) {
    if (debug) console.log(message);
}

function removeDefaultImage(el) {
    el.previousElementSibling.remove();
}

function replacePoster() {
    if (!enableCache) clearCachedPosters();
    else log(GM_listValues());
    if (location.pathname == '/' || location.pathname.includes('/collections/') || location.pathname.includes('/contests/') || location.pathname.includes('/achievements/franchise/') || location.pathname.includes('/animes') || location.pathname.includes('/mangas')) {
        document.querySelectorAll((document.querySelector('.c-poster img') != null) ? '.cc-similar article.b-catalog_entry' : 'article.b-catalog_entry').forEach((item, index) => {
            let posterLink = item.querySelector('a');
            if(!posterLink.href.includes('/animes/') && !posterLink.href.includes('/mangas/')) return;
            let posterElement = item.querySelector('img');
            removeDefaultImage(posterElement);
            if (GM_getValue(posterLink.href.slice(21)) != undefined && enableCache) {
                posterElement.src = GM_getValue(posterLink.href.slice(21));
                log('Loaded cached poster');
                return;
            }
            else {
                if ((location.pathname.includes('/collections/') || location.pathname.includes('/studio/') || location.pathname.includes('/achievements/franchise/') || location.pathname.includes('/animes/page/') || location.pathname.includes('/mangas/page/') ||
                     location.pathname.endsWith('/animes') || location.pathname.endsWith('/mangas')) && document.querySelectorAll('article.b-catalog_entry').length > 15) return;
                api.loadAniListPoster(posterElement, posterLink.href.slice(21), item.getAttribute('id'), posterLink.href.match(/anime|manga/)[0].toUpperCase());
            }
        });
    }
    if(location.pathname.includes('/animes/') || location.pathname.includes('/mangas/')) {
        if (document.querySelector('.c-poster img') != null) { // Main poster
            const posterElement = document.querySelector('.c-poster img');
            removeDefaultImage(posterElement);
            posterElement.style.width = '225px';
            log(`Found: ${location.pathname.match(/\d{1,}/)[0]}`);
            if (GM_getValue(location.pathname) != undefined && enableCache) {
                posterElement.src = GM_getValue(location.pathname);
                log('Loaded cached poster');
                addRefreshBtn();
            }
            else {
                const defaultPoster = posterElement.src;
                api.loadAniListPoster(posterElement, location.pathname, location.pathname.match(/\d{1,}/)[0], location.pathname.match(/anime|manga/)[0].toUpperCase());
            }
        }
    }
}

const api = {
    async loadAniListPoster(el, cachePath, malId, type) {
        const { src: defaultPoster } = el;
        el.src = 'https://i.gifer.com/HHAm.gif';
        const query = `query ($id: Int, $type: MediaType) {
          Media (idMal: $id, type: $type) {
            id
            idMal
            title {
              romaji
            }
            coverImage {
              extraLarge
            }
          }
        }`;
        const variables = {
            id: malId,
            type
        };
        const baseURL = 'https://graphql.anilist.co';
        const options = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json',
            },
            body: JSON.stringify({
                query,
                variables
            })
        };
        fetch(baseURL, options).then(res => {
            return res.json().then(json => {
                return res.ok ? json : Promise.reject(json);
            });
        })
        .then(data => {
            log(data.data.Media.title.romaji);
            el.src = data.data.Media.coverImage.extraLarge;
            if (enableCache) {
                GM_setValue(cachePath, el.src);
                log('Added to cache');
                addRefreshBtn();
            }
        })
        .catch(error => {
            log(`Can't load poster from AniList. ${error}. Returned default`);
            el.src = defaultPoster;
            return null;
        });
    }
};

function addRefreshBtn() {
    if(document.querySelector('.b-subposter-actions .refresh') == null) {
        let customCSS = document.createElement('style');
        document.head.appendChild(customCSS);
        const iconText = document.querySelector('#custom_css').innerHTML.includes('shiki-theme.web.app') ? 'refresh' : '\\e825';
        customCSS.appendChild(document.createTextNode(`.refresh:before { content: '${iconText}'; }`));
        let refreshBtn = document.createElement('a');
        document.querySelector('.b-subposter-actions').appendChild(refreshBtn);
        refreshBtn.classList = 'b-subposter-action refresh b-tooltipped';
        refreshBtn.href = 'javascript:void(0)';
        refreshBtn.setAttribute('title', (document.querySelector('body').getAttribute('data-locale') == 'ru') ? 'Обновить постер' : 'Reload poster');
        refreshBtn.onclick = () => {
            GM_deleteValue(location.pathname);
            window.location.reload();
        }
    }
}

function clearCachedPosters() {
    for (let i of GM_listValues()) GM_deleteValue(i);
}

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(() => {
    let $ = unsafeWindow.jQuery;
    $(".l-page").on("postloader:success", () => replacePoster());
    replacePoster();
});