Редирект-кнопка для Шикимори, которая перенаправляет на Anime365
目前為
// ==UserScript==
// @name ShikiLinker
// @description:ru Редирект-кнопка для Shikimori, которая перенаправляет на Anime365
// @description:en Redirect button for Shikimori that redirects to Anime365
// @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.one
// @author Jogeer
// @license MIT
// @version 3.0.1
// @description Редирект-кнопка для Шикимори, которая перенаправляет на Anime365
// ==/UserScript==
'use strict';
const DEBUG = true;
const SCRIPT = 'ShikiLinker';
const PAGEURL = new RegExp(/^https?:\/\/shikimori\.o(?:ne|rg)\/animes\/[A-z]?(\d*)-(.*)$/);
//#region Const
const CONST = {
url: {
shikimori: `https://${window.location.hostname}/`,
anime365: 'https://anime365.ru/',
},
apiUrl: {
shikimori: `https://${window.location.hostname}/api/`,
anime365: 'https://anime365.ru/api/',
},
headers: {
default: { 'Content-type': 'application/json' },
},
};
const STYLES = {
class: {
parent: '.c-about .c-info-right',
main: `${SCRIPT}`,
button: `${SCRIPT}-btn`,
increment: '.item-add.increment',
},
container: {
parent: 'display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;margin-top:10px',
button: 'text-align:center;background:#18181b;color:white;margin: 0 10px;user-select:none;',
buttonTo: 'flex:1 1 auto;padding:5px;',
buttonEp: 'flex:0 1 auto;padding:5px 15px;',
span: 'width:100%;text-align:center;',
},
};
//#endregion
//#region Main
class ShikiLinker {
constructor() {
this._shikiUserData = this._GetUserData();
this._animeId = this._GetAinimeId();
this._GetShikimoriApiData();
this._GetAnime365ApiData();
return this;
}
_GetAinimeId() {
return PAGEURL.exec(window.location.href)[1];
}
_GetUserData() {
return JSON.parse(document.body.getAttribute('data-user'));
}
_GetUserId() {
return this._shikiUserData.id;
}
async _MakeRequest(url) {
return GM.xmlHttpRequest({
method: 'GET',
headers: CONST.headers.default,
url: url,
});
}
async _GetShikimoriApiData() {
DEBUG
? console.log('ShikiLinker > GetShikimoriApiData > before')
: null;
return this._MakeRequest(`${CONST.apiUrl.shikimori}v2/user_rates?user_id=${this._GetUserId()}&target_id=${this._animeId}&target_type=Anime`).then(async (data) => {
this._shikiApiData = JSON.parse(data.response)[0];
DEBUG
? console.log('ShikiLinker > _GetShikimoriApiData > DATA:', this._shikiApiData)
: null;
}, (error) => {
console.error(error);
return null;
});
}
async _GetAnime365ApiData() {
DEBUG ? console.log('ShikiLinker > GetAnime365ApiData > before') : null;
return this._MakeRequest(`${CONST.apiUrl.anime365}series?myAnimeListId=${this._animeId}`).then(async (data) => {
this._anime365ApiData = JSON.parse(data.response)
.data[0];
this._UpdateGotoButton();
DEBUG
? console.log('ShikiLinker > _GetShikimoriApiData > DATA:', this._anime365ApiData)
: null;
}, (error) => {
console.error(error);
return null;
});
}
_CreateSubElement(tag, attributes) {
let element = document.createElement(tag);
attributes === null || attributes === void 0 ? void 0 : attributes.forEach((attr) => {
element.setAttribute(attr.key, attr.value);
});
return element;
}
async _UpdateGotoButton() {
DEBUG ? console.log('ShikiLinker > UpdateGotoButton > before') : null;
let element = document.querySelector(`#${STYLES.class.main} a`);
let timer = setInterval(() => {
if (this._anime365ApiData) {
//TODO: Костыль для сервака 365
let matches = RegExp('(?:https://)(?:.*?/)(.*)').exec(this._anime365ApiData.url);
element === null || element === void 0 ? void 0 : element.setAttribute('href', `${CONST.url.anime365}${matches[1]}`);
clearInterval(timer);
}
}, 1000);
}
async _UpdateEpisodeButton() {
DEBUG
? console.log('ShikiLinker > UpdateEpisodeButton > before')
: null;
this.SetupEventListener();
let element = document.querySelector(`#${STYLES.class.button}`);
//TODO: Тут нужно больше логики, пока впадлу
let timer = setInterval(() => {
if (this._shikiApiData && this._anime365ApiData) {
let episodes = this._anime365ApiData.episodes.filter((ep) => ['ona', 'ova', 'movie', 'tv'].includes(ep.episodeType));
let data = { href: '#', text: '--' };
if (['completed', 'dropped'].includes(this._shikiApiData.status)) {
// Если брошено или просмотренно
data.href = `${CONST.url.anime365}episodes/${episodes[0].id}`;
data.text = `First Episode`;
}
else if (episodes.length >= this._shikiApiData.episodes) {
// Если эпизоды еще есть
data.href = `${CONST.url.anime365}episodes/${episodes[this._shikiApiData.episodes].id}`;
data.text = `${this._shikiApiData.episodes + 1} Episode`;
}
element.setAttribute('href', data.href);
element.textContent = data.text;
clearInterval(timer);
}
}, 1000);
}
_CreateChildElements() {
DEBUG
? console.log('ShikiLinker > CreateChildElements > before')
: null;
let anime365Button = this._CreateSubElement('a', [
{ key: 'class', value: 'link-button' },
{
key: 'style',
value: `${STYLES.container.button}${STYLES.container.buttonTo}`,
},
]);
anime365Button.textContent = 'Anime 365';
let goToEpisodeButton = this._CreateSubElement('a', [
{ key: 'class', value: 'link-button' },
{ key: 'id', value: STYLES.class.button },
{
key: 'style',
value: `${STYLES.container.button}${STYLES.container.buttonEp}`,
},
]);
goToEpisodeButton.textContent = '🚫';
let addonInfoSpan = this._CreateSubElement('span', [
{ key: 'style', value: STYLES.container.span },
]);
addonInfoSpan.textContent = STYLES.class.main;
return [anime365Button, goToEpisodeButton, addonInfoSpan];
}
_CreateParentElement() {
DEBUG
? console.log('ShikiLinker > CreateParentElement > before')
: null;
let element = this._CreateSubElement('div', [
{ key: 'id', value: STYLES.class.main },
{ key: 'class', value: 'watch-online' },
{ key: 'style', value: STYLES.container.parent },
]);
return element;
}
_CreateElement() {
DEBUG ? console.log('ShikiLinker > CreateElement > before') : null;
const target = document.querySelector(STYLES.class.parent);
const parent = this._CreateParentElement();
const childs = this._CreateChildElements();
childs.forEach((ch) => parent.appendChild(ch));
return target.appendChild(parent);
}
async _Update() {
this._UpdateEpisodeButton();
return this;
}
SetupEventListener() {
var _a;
//TODO: Кто знает как это нормально сделать? Дайте совет
(_a = document
.querySelector(STYLES.class.increment)) === null || _a === void 0 ? void 0 : _a.addEventListener('click', async () => {
DEBUG ? console.log('Got refresh event, do refresh...') : null;
await this._GetShikimoriApiData();
this._UpdateEpisodeButton();
});
}
async Execute() {
DEBUG ? console.log('ShikiLinker > Execute > before') : null;
const element = document.querySelector(`#${STYLES.class.main}`);
if (!element) {
this._CreateElement();
}
DEBUG ? console.log('ShikiLinker > Execute > after') : null;
return this._Update();
}
}
//#endregion
//#region Support
function GotUpdateReaction(func) {
document.addEventListener('turbolinks:load', func);
if (document.attachEvent
? document.readyState === 'complete'
: document.readyState !== 'loading') {
func();
}
else {
document.addEventListener('DOMContentLoaded', func);
}
}
//#endregion
//#region Init
const _init = () => {
DEBUG ? console.log('Script init...') : null;
const obj = new ShikiLinker();
obj.Execute();
DEBUG ? console.log('Script inited') : null;
};
GotUpdateReaction(_init);