// ==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 smotret-anime.online
// @grant GM_xmlhttpRequest
// @icon https://www.google.com/s2/favicons?domain=shikimori.me
// @author Jogeer
// @license MIT
// @version 2.3.0
// ==/UserScript==
"use strict"
const DEBUG = true
const PAGEURL = new RegExp(
/^https?:\/\/shikimori\.o(?:ne|rg)\/animes\/[A-z]?(\d*)-(.*)$/
)
const A365URL = "https://smotret-anime.online/"
const A365API = `${A365URL}api/`
const SHIKIAPI = `https://${window.location.hostname}/api/`
const NYAASI = "https://nyaa.land/?"
const DISTRIB = "Erai-raws"
const PARENSSTYLES =
"display:flex;flex-direction:row;flex-wrap:wrap;align-content:center;justify-content:center;align-items:center;margin-top:10px"
const CHILDSTYLES =
"flex:1 1 auto;text-align:center;padding:5px;background:#18181b;color:white;margin: 0 10px"
const SPANSTYLES = "width:100%;text-align:center;"
const BASICLINKATTRS = [
{ attribute: "class", value: "link-button" },
{ attribute: "target", value: "_balnk" },
{ attribute: "style", value: CHILDSTYLES }
]
//#endregion
class ShikiLinker extends EventTarget {
//#region Supports
static BuildElement(element) {
let attrs = ""
element.attributes?.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: { "Content-type": "application/json" },
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(
`${A365API}series?myAnimeListId=${matches[1]}`
)
let second = await ShikiLinker.MakeRequest(
`${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("#shikilinker-inject")) {
DEBUG ? console.log("Parent container already exist") : null
return false
}
let documentDom = document.querySelector(toSelector)
documentDom?.insertAdjacentHTML(
"beforeend",
ShikiLinker.BuildElement({
tag: "div",
attributes: [
{ attribute: "class", value: "watch-online" },
{ attribute: "id", value: "shikilinker-inject" },
{ attribute: "style", value: PARENSSTYLES }
],
text: ""
})
)
DEBUG ? console.log("Parent container has been added") : null
return true
}
static async AddElement(element) {
let documentDom = document.querySelector("#shikilinker-inject")
DEBUG ? console.log("Add:", element, " to:", documentDom) : null
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?.addEventListener("click", () => {
DEBUG ? console.log("Got refresh event, do refresh...") : null
setTimeout(() => {
ShikiLinker.RefreshGoToEpisodeButton()
}, 100)
})
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.innerHTML = ShikiLinker.BuildElement({
tag: "a",
attributes: [
{
attribute: "href",
value: `${A365URL}episodes/${
_a365Data.data[0].episodes[_shikiData[0].episodes].id
}`
},
{ attribute: "id", value: "shikilinker-a365-gtebtn" }
].concat(BASICLINKATTRS),
text: `${_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
}
let domObject = document.querySelector("#shikilinker-inject")
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: `${A365URL}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: "a",
attributes: [
{
attribute: "href",
value: `${NYAASI}u=${DISTRIB}&q=${
document.querySelector('meta[property="og:title"]').content
}`
}
].concat(BASICLINKATTRS),
text: "Nyaa.si"
})
elements.push({
tag: "span",
attributes: [{ attribute: "style", value: SPANSTYLES }],
text: "ShikiLinker"
})
elements.forEach(element => {
ShikiLinker.AddElement(element)
})
}
}
function ready(func) {
//var document = document;
document.addEventListener("turbolinks:load", func)
if (
document.attachEvent
? document.readyState === "complete"
: document.readyState !== "loading"
) {
func()
} else {
document.addEventListener("DOMContentLoaded", func)
}
}
ready(ShikiLinker.Execute)
// to js on: https://www.typescriptlang.org/