// ==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);