ShikiLinker

Редирект-кнопка для Шикимори, которая перенаправляет на Anime365

اعتبارا من 25-11-2024. شاهد أحدث إصدار.

// ==UserScript==
// @name            ShikiLinker
// @description     Редирект-кнопка для Шикимори, которая перенаправляет на Anime365
// @description:en  Redirect button for Shikimori that redirects to Anime 365
// @namespace       https://shikimori.one/animes
// @match           https://shikimori.one/animes/*
// @connect         anime365.ru
// @grant           GM_xmlhttpRequest
// @icon            https://www.google.com/s2/favicons?domain=shikimori.me
// @author          Jogeer
// @license         MIT
// @version         2.5.0
// ==/UserScript==
'use strict';
const DEBUG = true;
const PAGEURL = new RegExp(/^https?:\/\/shikimori\.o(?:ne|rg)\/animes\/[A-z]?(\d*)-(.*)$/);
//#region Const
const SCRIPT = 'shikilinker';
const STATIC = {
    class: `#${SCRIPT}`,
    endpoint: `https://anime365.ru/`,
    eAPI: `https://anime365.ru/api/`,
    shikiAPI: `https://${window.location.hostname}/api/`,
    headers: {
        request: { 'Content-type': 'application/json' },
    },
    classes: {
        parent: '.c-info-right',
    },
};
const STYLES = {
    container: {
        parent: 'display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;margin-top:10px',
        child: 'flex:1 1 auto;text-align:center;padding:5px;background:#18181b;color:white;margin: 0 10px',
        span: 'width:100%;text-align:center;',
    },
};
const BASICLINKATTRS = [
    { attribute: 'class', value: 'link-button' },
    { attribute: 'target', value: '_balnk' },
    { attribute: 'style', value: STYLES.container.child },
];
//#endregion
class ShikiLinker extends EventTarget {
    //#region Supports
    static BuildElement(element) {
        var _a;
        let attrs = '';
        (_a = element.attributes) === null || _a === void 0 ? void 0 : _a.forEach((el) => {
            let _val = '';
            el.value ? (_val = `=\"${el.value}\"`) : null;
            attrs += `${el.attribute}${_val} `;
        });
        return `<${element.tag} ${attrs}>${element.text}</${element.tag}>`;
    }
    static ParseUserData() {
        return JSON.parse(document.querySelector('body').getAttribute('data-user'));
    }
    //#endregion
    //#region Key
    static async MakeRequest(url) {
        return await new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'GET',
                headers: STATIC.headers.request,
                url: `${url}`,
                onload: async (data) => {
                    data = await JSON.parse(data.response);
                    DEBUG ? console.log(`Request to ${url}`, data) : null;
                    resolve(data);
                },
                onerror: async (data) => {
                    DEBUG ? console.error(`Request to ${url}`, data) : null;
                    reject(null);
                },
            });
        });
    }
    static async MakeApiRequests() {
        let matches = PAGEURL.exec(window.location.href);
        let userData = ShikiLinker.ParseUserData();
        DEBUG ? console.log('Parsing done:', matches, userData) : null;
        let first = await ShikiLinker.MakeRequest(`${STATIC.eAPI}series?myAnimeListId=${matches[1]}`);
        let second = await ShikiLinker.MakeRequest(`${STATIC.shikiAPI}v2/user_rates?user_id=${userData.id}&target_id=${matches[1]}&target_type=Anime`);
        DEBUG ? console.log('Req done:', first, second) : null;
        return [first, second];
    }
    //#endregion
    static async AddParentContainer(toSelector) {
        if (document.querySelector(STATIC.class)) {
            DEBUG ? console.log('Parent container already exist') : null;
            return false;
        }
        let documentDom = document.querySelector(toSelector);
        documentDom === null || documentDom === void 0 ? void 0 : documentDom.insertAdjacentHTML('beforeend', ShikiLinker.BuildElement({
            tag: 'div',
            attributes: [
                { attribute: 'class', value: 'watch-online' },
                { attribute: 'id', value: SCRIPT },
                { attribute: 'style', value: STYLES.container.parent },
            ],
            text: '',
        }));
        DEBUG ? console.log('Parent container has been added') : null;
        return true;
    }
    static async AddElement(element) {
        let documentDom = document.querySelector(STATIC.class);
        DEBUG ? console.log('Add:', element, ' to:', documentDom) : null;
        documentDom === null || documentDom === void 0 ? void 0 : documentDom.insertAdjacentHTML('beforeend', ShikiLinker.BuildElement(element));
    }
    static async HaveNonWatchedEpisode(a365Data, shikiData) {
        DEBUG ? console.log('API data:', a365Data, shikiData) : null;
        if (!shikiData || !shikiData.episodes) {
            shikiData = JSON.parse('{"status": "none", "episodes": 0}');
        }
        if (['completed', 'dropped'].includes(shikiData.status) ||
            shikiData.episodes >= a365Data.episodes.length) {
            return false;
        }
        return true;
    }
    static SetupEventListeners() {
        let target = document.querySelector('.rate-number > span.item-add');
        target === null || target === void 0 ? void 0 : target.addEventListener('click', () => {
            DEBUG ? console.log('Got refresh event, do refresh...') : null;
            setTimeout(() => {
                ShikiLinker.RefreshGoToEpisodeButton();
            }, 250);
        });
        DEBUG ? console.log('Setted events') : null;
    }
    static async RefreshGoToEpisodeButton() {
        let button = document.querySelector('#shikilinker-a365-gtebtn');
        let datas = await ShikiLinker.MakeApiRequests();
        let _a365Data = datas[0];
        let _shikiData = datas[1];
        if (await ShikiLinker.HaveNonWatchedEpisode(_a365Data.data[0], _shikiData[0])) {
            button.href = `${STATIC.endpoint}episodes/${_a365Data.data[0].episodes[_shikiData[0].episodes].id}`;
            button.textContent = `${_shikiData[0].episodes + 1} ep`;
        }
        else {
            button.remove();
        }
        DEBUG ? console.log('Refreshed') : null;
        ShikiLinker.SetupEventListeners();
    }
    static async Execute() {
        ShikiLinker.SetupEventListeners();
        if (!(await ShikiLinker.AddParentContainer('.c-info-right'))) {
            DEBUG ? console.log('Block already exist') : null;
            return;
        }
        const domObject = document.querySelector(STATIC.class);
        let datas = await ShikiLinker.MakeApiRequests();
        let _a365Data = datas[0];
        let _shikiData = datas[1];
        let elements = [
            {
                tag: 'a',
                attributes: [
                    { attribute: 'href', value: _a365Data.data[0].url },
                ].concat(BASICLINKATTRS),
                text: 'Anime 365',
            },
        ];
        if (await ShikiLinker.HaveNonWatchedEpisode(_a365Data.data[0], _shikiData[0])) {
            DEBUG ? console.log('Have nonwatched ep') : null;
            try {
                elements.push({
                    tag: 'a',
                    attributes: [
                        {
                            attribute: 'href',
                            value: `${STATIC.endpoint}episodes/${_a365Data.data[0].episodes[_shikiData[0].episodes].id}`,
                        },
                        { attribute: 'id', value: 'shikilinker-a365-gtebtn' },
                    ].concat(BASICLINKATTRS),
                    text: `${_shikiData[0].episodes + 1} ep`,
                });
            }
            catch (error) {
                DEBUG ? console.log('Have NWE, but:', error) : null;
            }
        }
        elements.push({
            tag: 'span',
            attributes: [{ attribute: 'style', value: STYLES.container.span }],
            text: 'ShikiLinker',
        });
        elements.forEach((element) => {
            ShikiLinker.AddElement(element);
        });
    }
}
function ready(func) {
    document.addEventListener('turbolinks:load', func);
    if (document.attachEvent
        ? document.readyState === 'complete'
        : document.readyState !== 'loading') {
        func();
    }
    else {
        document.addEventListener('DOMContentLoaded', func);
    }
}
ready(ShikiLinker.Execute);