NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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')}`, } }); } */