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')}`,
}
});
}
*/