Adds a button to individual anime pages on AniList that links to the corresponding Jimaku entry
// ==UserScript==
// @name AniList Jimaku Button
// @namespace http://tampermonkey.net/
// @version 1.9
// @description Adds a button to individual anime pages on AniList that links to the corresponding Jimaku entry
// @author https://github.com/konata-san
// @match https://anilist.co/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAACXBIWXMAAAsSAAALEgHS3X78AAAEMklEQVRoge2ay28TRxzHf7O7s47t2CgxiWOvLXAaSiGhpJQ6xJCH5ZiG2CkNpUE9V+rf0UNPubRVT6FSe2rVh0CCQCtIIQSl6qUSARIoUiEPk1alEk6MmviR9fRgKQqz9npf1irSfuWD/Z3Hbz67vxnPrI18oUOwk8WYPQC9sgDMlgVgtiwAs2UBmC0LwGxZAGbLAjBbOx6AM6QXjDGDEGVuiqIoioAYwm6LUiyi4qYhQUsyAMBeV/f1+c/9vhbKv3z12tj5b9Ojn4ie4JaJNjLuiTFb6r7+uCUZANB5uKMr/CaPMeWfOT009vvz/IFeyl+PnLN9bxiAAXMgMTggHT0ACII/3N0t9QmuA6DzTbP0AtjtdW/Ho2WLOJZNDpzQ2X9V6QWIHAu3eJsrlY4EOYcxy0RF6QJACCWH4gxTsZPdNohVpDNGugBcrvpYf498nbMCkSywRkoXQH9PxNPYIF9nyAe7eT1Bqkg7AMMww0Mnq1bz8BD3Es1Rqg9Dc0tvc9PxSBdl/vl4IZfLUea5ILA1yyLtANG+E7vcLsr85rsLd+7OUeZxDwh2zXGqSCMAy3HS/Mnl8lO3f73y8yQhL+VMIw8JX62ySCPAnqDQebiDMp8sLD5ZWJqanvlvfZ0qGhFqlUUaAQaivW5XPWVO3pgWRXE59fTe/QdUUbgBQk5toapICwDGXOJUnDLzhcLN6RkAKBbJxE/XqSxyY3hXqEkWaQHYv6/t4IFXKXNl5e/5h49K72/P/JbJvKAqjAaAq8HxSUuX8Vifw04vK/MPH3kaG4MBIRgQAODxwiJVod0NHW5NY5SV6q0WxjgpyR8AOBnr6++NbH202WxUBTsL7/jJ7KrBc1n1HXj9UPsrrXulPs/z9U7n1gtzZS7NB0HgjF6LVAMkT8VxueOLErXVwxtVtk6qpS6FbDY+MRjTHIxF8H6AzDobsqEjQCouSvh5is38q7BPdQBHj3T6fT5VTSiNCPDxvqPpvV/K1GHW/vGMf8il/1LSoQoAhNDp5KB0c7+5KWaz2bL1HQ47erlBqxOOedDNZ3Jxiw1Cwf+a8QBut2sgSj9iAICLl658+sU4SDIC8/ir8c/aWkPbTQQwGiBTz5D8t5ryQ5AKgO6ut7zeJjoSIT9enFhOrZRtcv2XW20fhShzsAV28bCaVx5ZTkpXIYZhhhNlji9Ly0/v3K34kEe6MwWAoAOi9HXQLqUA3uamnu6w1J+8cWtjo8wEKGlu/o/FpRRlIoD3BCKXIoQw+Q2FA1OaQu0H96dX11bXMtvNIilevnpNppUoij9cuHR2ZJjy92SJJw3pQlkKglNz/OKswoEhhf9WYVmWY1mpny8UpEmipGGuKJ32AABAAIkFqFAoldI7IJYeNauXTENDdhU7/vcBC8BsWQBmywIwWxaA2bIAzJYFYLYsALO14wH+B+qJGDfRYjT0AAAAAElFTkSuQmCC
// @grant GM.getValue
// @grant GM.setValue
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
let currentPageUrl, anilistId, JIMAKU_API_KEY;
async function setupVariables() {
currentPageUrl = window.location.href;
const anilistIdRegexMatch = currentPageUrl.match(/^https:\/\/anilist\.co\/anime\/(\d+)(\/.*)?$/);
anilistId = anilistIdRegexMatch ? anilistIdRegexMatch[1] : null;
}
async function promptAPIKey() {
const apiKey = prompt("Please enter your Jimaku API key:");
if (apiKey !== null && apiKey !== "") {
await GM.setValue("API_KEY_JIMAKU", apiKey);
}
}
async function getAPIKey() {
let apiKey = await GM.getValue("API_KEY_JIMAKU");
if (!apiKey) {
await promptAPIKey();
apiKey = await getAPIKey();
}
return apiKey;
}
document.addEventListener('DOMContentLoaded', function() {
const pageLoadObserver = new MutationObserver(async function() {
if (!window.location.href.startsWith('https://anilist.co/anime')) {
document.getElementById('jimaku-button').remove();
return;
}
const overviewButton = document.querySelector('.nav > a.router-link-exact-active');
if(!overviewButton) return;
pageLoadObserver.disconnect();
if (document.getElementById('jimaku-button')) return;
JIMAKU_API_KEY = await getAPIKey();
await setupVariables();
await addJimakuButton(overviewButton);
});
const pageNavigationObserver = new MutationObserver(async function() {
if (window.location.href === currentPageUrl) return;
if (!window.location.href.startsWith('https://anilist.co/anime')) {
document.getElementById('jimaku-button').remove();
return;
}
const jimakuButton = document.getElementById('jimaku-button');
if (!jimakuButton) return;
await setupVariables();
await updateJimakuButton(jimakuButton);
});
const observerConfig = { childList: true, subtree: true };
pageLoadObserver.observe(document.body, observerConfig);
pageNavigationObserver.observe(document.body, observerConfig);
window.addEventListener('popstate', function () {
pageLoadObserver.disconnect();
pageLoadObserver.observe(document.body, observerConfig);
});
});
async function fetchJimakuId(anilistId) {
const cachedJimakuId = await GM.getValue('jimakuId_' + anilistId);
return cachedJimakuId ? cachedJimakuId : await fetchJimakuIdFromAPI(anilistId);
}
async function fetchJimakuIdFromAPI(anilistId) {
const response = await fetch(`https://jimaku.cc/api/entries/search?anilist_id=${anilistId}`, { headers: { 'authorization': JIMAKU_API_KEY } });
const data = await response.json();
if (response.ok && data[0]) {
const id = data[0].id;
await GM.setValue('jimakuId_' + anilistId, id);
return id;
}
if (!data[0]) {
console.log(`No jimaku entry found for anilist id: ${anilistId}`)
}
console.error('Error fetching data from Jimaku API:', data.error);
if (response.status === 401) {
await GM.setValue("API_KEY_JIMAKU", null);
console.log("Invalid Jimaku API key supplied.")
alert("Error: Invalid Jimaku API key.");
}
return "..";
}
async function addJimakuButton(overviewButton) {
const jimakuButton = createJimakuButton(overviewButton);
jimakuButton.href = "https://jimaku.cc/";
overviewButton.parentNode.insertBefore(jimakuButton, overviewButton.nextSibling);
updateJimakuButton(jimakuButton);
}
async function updateJimakuButton(jimakuButton) {
try {
const id = await fetchJimakuId(anilistId);
jimakuButton.href = `https://jimaku.cc/entry/${id}`;
} catch (error) {
console.error('Error fetching data:', error);
}
}
function createJimakuButton(overviewButton) {
const jimakuButton = document.createElement('a');
jimakuButton.innerText = 'Jimaku';
jimakuButton.setAttribute('id', 'jimaku-button');
for (const { name, value } of overviewButton.attributes) {
jimakuButton.setAttribute(name, value);
}
return jimakuButton;
}
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
const jimakuButton = document.getElementById('jimaku-button');
if (!jimakuButton || jimakuButton.dataset.duplicated) return;
const hohExtraBox = document.querySelector('.hohExtraBox');
if (hohExtraBox) {
const aniwatcher = document.getElementById('aniwatcher_button_hopefully_this_is_uniue');
if (aniwatcher) {
const clonedElement = aniwatcher.cloneNode(true);
clonedElement.id = 'jimaku-button';
clonedElement.setAttribute('href', jimakuButton.getAttribute('href'));
clonedElement.textContent = 'Jimaku ';
clonedElement.dataset.duplicated = "true";
aniwatcher.after(clonedElement);
jimakuButton.remove();
}
else {
let h = document.createElement("a");
h.id = 'jimaku-button';
h.setAttribute('href', jimakuButton.getAttribute('href'));
h.textContent = 'Jimaku ';
h.dataset.duplicated = "true";
h.setAttribute("data-v-5776f768", "");
h.className = "link";
h.setAttribute("target", "_blank");
h.style.transitionDuration = "150ms";
hohExtraBox.insertBefore(h, hohExtraBox.firstChild.nextSibling);
jimakuButton.remove();
}
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();