lelykmaryan / ShikiSync

// ==UserScript==
// @name         ShikiSync
// @namespace    https://yummyanime.club/catalog/item/*
// @version      1.0
// @description  simple script for sync anime from yummyanime to shikimori!
// @author       lelykmaryan
// @match        https://yummyanime.club/catalog/item/*
// @icon         https://www.google.com/s2/favicons?domain=yummyanime.club
// @grant        none
// @copyright 2021, lelykmaryan (vk.com/avandi)
// @license MIT
// ==/UserScript==

let names = document.querySelector(".alt-names-list > li").innerText; //парсим аниме с сайта

let response = await fetch(`https://shikimori.one/api/animes?search=${names}`); // получаем общую информацию об аниме без токена пользователя
let responseAnime = await response.json();

let anime = {};
let params = [];
anime.id = responseAnime[0].id; 
anime.episodes = responseAnime[0].episodes;

let syncButton = document.createElement('div'); // создаём блок, который в будущем будем заменять на наш раздел
syncButton.setAttribute("id", "shikiblock");
let customButton = document.querySelector(".tab-content");
customButton.before(syncButton);

if (String(localStorage.getItem('access_token')) != 'undefined' && localStorage.getItem('access_token') != null ) { // токен есть в сторадже? -Да - получаем инфу по этому аниме от пользователя, обновляем статусы, Нет - показываем форму авторизации
    await getAnime();
    status();
    
    console.log(1);
} else {
    params[1] = 'display: none';
}

refresh();


if( Date.now()/1000 > localStorage.getItem('expired_token')) { // токен устарел? => меняем на новый (refresh токен активен 28 дней, если они пройдут, наиболее вероятно будет ошибка, исправлю позже)
            const formData = new FormData();
            formData.append('grant_type', 'refresh_token');
            formData.append('client_id', 'RlS3ERkR3Uan7A4B_HzSK6TorumLReSrPJix1mA1uWw');
            formData.append('client_secret', 'yW2JJ1zVTyBDzpIqS0197D0dhkoF5xg0SUkkOfGUI-Y');
            formData.append('refresh_token', localStorage.getItem('refresh_token'));

            let response = await fetch(`https://shikimori.one/oauth/token`, {
                method: 'POST',
                body: formData
            });
            let responseJSON = await response.json();

            localStorage.setItem('expired_token', responseJSON.created_at+responseJSON.expires_in);
            localStorage.setItem('access_token', responseJSON.access_token);
            localStorage.setItem('refresh_token', responseJSON.refresh_token);
}


authAdd.onclick = () => { //авторизуем пользователя (oauth2.0), получаем его статус по аниме, обновляем раздел
    let paramsLink = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, width=450,height=650`;
    let link = `https://shikimori.one/oauth/authorize?client_id=RlS3ERkR3Uan7A4B_HzSK6TorumLReSrPJix1mA1uWw&redirect_uri=https://yummyanime.club/&response_type=code&scope=user_rates`;
    newWindow = open(link, 'test', paramsLink);

    let timerID = setTimeout(async function tick() {
        try {
            let tmppath = newWindow.location.href;
            let path = tmppath.slice(tmppath.indexOf('=')+1);

            const formData = new FormData();
            formData.append('grant_type', 'authorization_code');
            formData.append('client_id', 'RlS3ERkR3Uan7A4B_HzSK6TorumLReSrPJix1mA1uWw');
            formData.append('client_secret', 'yW2JJ1zVTyBDzpIqS0197D0dhkoF5xg0SUkkOfGUI-Y');
            formData.append('code', path);
            formData.append('redirect_uri', 'https://yummyanime.club/');

            let responseToken = await fetch(`https://shikimori.one/oauth/token`, {
                method: 'POST',
                body: formData
            });
            let responseTokenJson = await responseToken.json();

            localStorage.setItem('expired_token', responseTokenJson.created_at+86400);
            localStorage.setItem('access_token', responseTokenJson.access_token);
            localStorage.setItem('refresh_token', responseTokenJson.refresh_token);

            let responseID = await fetch(`https://shikimori.one/api/users/whoami`, { 
                headers: {
                    'Authorization': `Bearer ${localStorage.getItem('access_token')}`
                }
            });
            let responseIdJson = await responseID.json();

            localStorage.setItem('shiki_id', responseIdJson.id);


            newWindow.close();
            await getAnime();
            status();
            refresh();
        } catch {
            time = setTimeout(tick, 2000);
        }
    }, 2000);
}

async function getAnime() { // получаем статус аниме и количество просмотреннных эпизодов
    let response = await fetch(`https://shikimori.one/api/v2/user_rates/?target_id=${anime.id}&target_type=Anime&user_id=${localStorage.getItem('shiki_id')}`, {
        headers: {
            'Authorization': `Bearer ${localStorage.getItem('access_token')}`
        }});
    let responseJSON = await response.json();
    anime.status = responseJSON[0]?.status;
    anime.myEpisodes = responseJSON[0]?.episodes != undefined ? responseJSON[0]?.episodes : 0;
    anime.rewatches = responseJSON[0]?.episodes != undefined ? responseJSON[0]?.rewatches : 0;
       
}

function status() { //устанавливаем статусы кнопок в зависимости от статуса аниме
    switch(String(anime.status)) {
        case 'completed':
            anime.statusRu = 'Просмотрено';
            params[0] = params[2] = params[3] = params[4] = params[6] = params[7] = 'display: none';
            params[1] = params[5] = 'display: inline-block';
            break;
        case 'planned':
            anime.statusRu = 'Запланировано';
            params[0] = params[2] = params[4] = params[5] = params[6] = params[7] = 'display: none';
            params[1] = params[3] = 'display: inline-block';
            break;
        case 'watching':
            anime.statusRu = 'Смотрю';
            params[0] = params[2] = params[3] = params[5] = 'display: none';
            params[1] = params[4] = params[6] = params[7] = 'display: inline-block';
            break;
        case 'rewatching':
            anime.statusRu = 'Пересматриваю';
            params[0] = params[2] = params[3] = params[5] = 'display: none';
            params[1] = params[4] = params[6] =  params[7] = 'display: inline-block';
            break;
        case 'on_hold':
            anime.statusRu = 'Отложено';
            params[0] = params[2] = params[4] = params[5] = params[6] = params[7] = 'display: none';
            params[1] = params[3] = 'display: inline-block';    
            break;
        case 'dropped':
            anime.statusRu = 'Брошено';
            params[0] = params[2] = params[4] = params[5] = params[6] = params[7] = 'display: none';
            params[1] = params[3] = 'display: inline-block';    
            break;
        case 'undefined':
            anime.statusRu = 'Нет статуса';
            params[0] =  params[4] = params[5] = params[6] = params[7] = 'display: none';
            params[1] = params[2] = params[3] = 'display: inline-block';
            break;
        default:
            console.log(anime.status);
    }
}

function refresh(){ //по вызову загружаем обновленную версию раздела, объявляю функции клика
    document.getElementById('shikiblock').innerHTML = `
        <div id="shikiblock" >
            <h3>Shikimori:</h3>
            <div class="btn_authNone" style="${params[0]}; color: #ff6666; font-weight: bold">Без авторизации на Shikimori, добавить аниме в свой список будет невозможно! <br><br>
                <div class="video-button" id="authAdd">Авторизоваться</div>
            </div>
            <div class="btn_auth" style="${params[1]};">
                <div>Статус аниме: <b>${anime.statusRu} (${anime.myEpisodes != undefined ? anime.myEpisodes : 0}/${anime.episodes})</b></div><br>
            <div style="">
                    <div class="video-button" id="animePlanned" style="${params[2]}">Запланировать</div>
                    <div class="video-button" id="animeStart" style="${params[3]}">Начать смотреть</div>
                    <div class="video-button" id="episodeAdd" style="${params[4]}">Отметить просмотренной</div>
                    <div class="video-button" id="animeRestart" style="${params[5]}">Начать пересматривать</div>

                    <div class="video-button" id="animeOnhold" style="margin-left: 50px; background: #e8ebef; color: #000000; ${params[6]};">Отложить</div>
                    <div class="video-button" id="animeDropped" style="background: #ff6666; ${params[7]}">Бросить</div>
                    </div>
            </div>
        </div>`;    
        animePlanned.onclick = function() {
            anime.status = 'planned';
            sendStatus(anime.status);
        }
        animeStart.onclick = function() {
            anime.status = 'watching';
            sendStatus(anime.status, anime.myEpisodes);
        }
        animeRestart.onclick = function() {
            anime.status = 'rewatching';
            anime.myEpisodes = 0;
            sendStatus(anime.status);
        }
        animeOnhold.onclick = function() {
            anime.status = 'on_hold';
            sendStatus(anime.status);
        }
        animeDropped.onclick = function() {
            anime.status = 'dropped';
            sendStatus(anime.status);
        }
        episodeAdd.onclick = function() { // next series and sync shikimori
            let numSeries = (document.querySelector(".video-button.active") != null) ? document.querySelector(".video-button.active").getAttribute('data-id') : 1;
            let nextSeries = document.querySelector(".video-button.active ~ .video-button");
            (nextSeries != null) ? nextSeries.click() && alert(1) : false;
            console.log(anime.status == 'rewathing');
            if (anime.status == 'rewatching') {
                anime.status = (+numSeries === anime.episodes) ? 'completed' : 'rewatching';
                anime.rewatches = (anime.status === 'completed') ? anime.rewatches += 1 : anime.rewatches;
            } else {
                anime.status = (+numSeries === anime.episodes) ? 'completed' : 'watching';
            }

            anime.myEpisodes = +numSeries;
            sendStatus(anime.status, numSeries);
        }
}

sendStatus = async function(statusAnime, numSeries) { //отправляем изменения в статусе/эпизоде аниме
    let json = {
        "user_rate": {
            "episodes": `${anime.status === 'watching' ? numSeries : anime.myEpisodes}`,
            "status": `${statusAnime}`,
            "rewatches": `${anime.rewatches}`,
            "target_id": `${anime.id}`,
            "target_type": "Anime",
            "user_id": `${localStorage.getItem('shiki_id')}`
    }};
    let responsetok = await fetch(`https://shikimori.one/api/v2/user_rates`, {
                method: 'POST',
                headers: {
                    'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(json)
    });
    status();
    refresh();
}
/*
                        Метод по удалению аниме из списка.
Корс не даёт выполнить метод. Если и выполнять, то только с бекенда (придётся найти откуда шики берет ид по которому удаляет, со стандартным отдаёт 403)
<div class="video-button" id="animeDelete" style="background: #ff6666; ${params[8]}">Удалить из списка</div>
animeDelete.onclick = async function() {
    let response = await fetch(`https://shikimori.one/api/v2/user_rates/АЙДИ`, { 
                method: 'DELETE',
                mode: 'cors',
                headers: {
                    'User-Agent': 'ShikiSync',
                    'Authorization': `Bearer ${localStorage.getItem('access_token')}`,

                }
    });
}
*/