您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Adds direct links to MyAnimeList and AniList on HiAnime watch pages and its grid poster
当前为
// ==UserScript== // @name HiAnime Links+ // @namespace http://greasyfork.icu/users/1470715 // @author cattishly6060 // @author forked_bytes // @description Adds direct links to MyAnimeList and AniList on HiAnime watch pages and its grid poster // @icon https://icons.duckduckgo.com/ip3/hianime.to.ico // @match https://hianime.to/* // @grant GM_openInTab // @version 1.0 // @license 0BSD // ==/UserScript== (function () { 'use strict'; // Add images const malBase64Img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAFKUExURQAAAC5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5Roi5RojVWpTxdqDlbpzJUpDVXpTdZppCjzv////r7/Vh0tXmPw2uEvYicysfR5vL0+bnE4LXC3uDl8ThZpkporvT1+oSZyPDz+DBTo6+829LZ66q42V55uHSMwZys06Sz1+js9bfD36Gx1Z2u1F96uPHz+X2Txf39/mB7uP7+/+/x+HOKwTBSo3aNwqOz1svT6PP1+k9ssc7W6fj6/Pf4+4qdy9rg76i32IGWx42gzIKXx73I4TRWpdfe7bTB3qy62kxqr97k8L3I4nWMwvj5/FRws83V6cXP5cHL46+93KKx1dDY6vX3++Po82uDvTpbp/v8/YWZycrT59Xc7LjE3+Hm8i9Soq6725qr0n6Uxktpr/z8/t3i8Ft3tqm32f4+yygAAAAPdFJOUwEVh9P50YNT8RfzVfv1z7hGe4QAAAABYktHRBcL1piPAAAAB3RJTUUH6QYZDiIGG4HDQAAAAZhJREFUSMft1ldXwjAUAOCwh+MyxLhQZIgDF4gL98a9F4pa9/r/r94mtVBPW5oHXzzehyT35H60TU9JCCHEZnc4oWY4XW4bYeHx1q7m4fWweqvlcqCwWf59dg0fcYvUA9iJSww4iIX10awVEasH+Ae/BQLBYDCkpDgMhiuzIUybACLYRSqgmVLa0sqyNhzSdsV2RDvltAsghl1MC2h3HJNEsgJSrBqjJ60LaC9AuI9+g3Q/VWNAH9AMDFIVDLHR8MhoNpvNGYCx/LgKJiaxn5ouzESj0VkwAGogmMNufoFPLeqCJV67zEFCvsAKmIHVNQbWOUjJ3UaxWNw0Blvb2O4AB7vVd2gAYC9J9+MKOLAC4PDoGBRwYgkA1leB06L5MygpB2fYnoP5KmnABbaXP0DpSo5rfZCR30xZC1jclPVBvITdrQ64Y7eUlyTpXgE4lHLAXsTDI596AniWlHgx+kRf3+j7h9A3/ZkNgBAomP0JiMWfAIJbll90U3SJb7s+0Y1d+OhASJ3f8hN7+HEGjz/1tasbGtnx5wvNirJSwodULQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNS0wNi0yNVQxNDozNDowNiswMDowMNrDr1EAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjUtMDYtMjVUMTQ6MzQ6MDYrMDA6MDCrnhftAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI1LTA2LTI1VDE0OjM0OjA2KzAwOjAw/Is2MgAAAABJRU5ErkJggg=='; const anilstBase64Img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAClUExURRkhLQAFHAAAGAAQIBoTFhkUGBkdJtfY2unp6qKjpweO1AOd6wOY4xQ/WwoWJFZbYv///wBPgQCx/wCq/wmGyK2vs/7+/lyRugCt/wAAFfLz9LDe/QCm/2VpcLq8v13C/xQdKnF1esXHyW3H/iEpNMfp/oCEiS40P4DN/gAAAI+SlgAADo3R/kNIUNzy/pudoTq6/+v4/hoNCQCI0xREYxgnNxFReJBTZkgAAAABYktHRBCVsg0sAAAAB3RJTUUH6QYaAyIo3TryMgAAAXNJREFUWMPtlWFXgjAUhoGpzMyxQEmQdJZKpaVW9v9/WrB7B9bpoJdz/Lbn4865z+4L253jWCwWy5VxPQ1zOt2SHrne55q+ezMouB0SDSKQAL8Lo4Jw1KEJvLGMNfK+rI8mVEGSGsE0ayMQD1hf0EqQzCqBnLcQKBbXAp2BKBCLOkEct+ggedQCsMinjCpQS10rfRCsyAK2hgbWPvQwoQoSKJQ5ip4zmkDlkIC/5FJiBpKAvcLGfXeDrYQ0gUkQCPMxthlFoN6gcZ4ogWHeSQJ3B9vOlnnuYDP7iCDY8NNjCEdhSxAUo8SUSWnu9CG7XFCNkl8t7EcfPcO5BOl/gunn8MtwbFRUCXAm4oU6RJOK6NiYAEdJPQ9OxgoQfje1IFL8iU6uWcI/wdF4XmCGoQyY0iR9iRkuE5hRwj1cKM7l3wxNgnKUaMZGUJ4rzSq7RCAWqX7Q0kCYJbaDN44PDKOmb6jgSfVUvcRwqVvhWCwWy3X4Aap4O8LCxI3vAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDI1LTA2LTI2VDAzOjM0OjQwKzAwOjAw9O3HTQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyNS0wNi0yNlQwMzozNDo0MCswMDowMIWwf/EAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjUtMDYtMjZUMDM6MzQ6NDArMDA6MDDSpV4uAAAAAElFTkSuQmCC'; // Add minimal spinner CSS const style = document.createElement('style'); style.textContent = ` @keyframes spin { to { transform: rotate(360deg); } } .mal-mini-spinner { display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(0,0,0,0.2); border-top-color: #3498db; border-radius: 50%; animation: spin 0.8s linear infinite; margin-left: 5px; vertical-align: middle; } .mal-link-loading .tick { opacity: 0.3; } `; document.head.appendChild(style); /** @typedef {{mal_id: ?number, anilist_id: ?number}} Link */ /** @type {Map<string, ?Link>} */ const cachedLinkMap = new Map(); /** @type {Object<string, string>} */ const linkType = { MAL: 'mal', ANILIST: 'anilist', }; function addHiAnimeBtnAndSubTitle() { Array.from(document.querySelectorAll("div.flw-item"))?.filter(e => e.querySelector('div.film-detail') && e.querySelector('div.film-poster') || []).forEach(e => { if (e.querySelector('.custom-jp-title')) return; const title = e.querySelector('.film-name > a')?.textContent || ""; const titleJp = (e.querySelector('.film-name > a')?.getAttribute('data-jname') || title || "").replace('[Uncensored]', '').trim(); const query = encodeURIComponent(titleJp.slice(0, 100)); const uriMAL = `https://myanimelist.net/search/all?q=${query}&cat=all#anime`; const uriAnilist = `https://anilist.co/search/anime?search=${query}`; const linkEndpoint = e.querySelector('a.film-poster-ahref')?.href; const topPad = e.querySelector('.tick-rate') ? 35 : 10; const imgSize = 30; const aHiAnime = createLink(uriMAL, titleJp, linkEndpoint, linkType.MAL); aHiAnime.innerHTML = `<img class="tick" width="${imgSize}" height="${imgSize}" style="position: absolute; left: 10px; top: ${topPad}px;" src="${malBase64Img}">`; e.querySelector('div.film-poster').appendChild(aHiAnime); const aAnilist = createLink(uriAnilist, titleJp, linkEndpoint, linkType.ANILIST); aAnilist.innerHTML = `<img class="tick" width="${imgSize}" height="${imgSize}" style="position: absolute; left: 10px; top: ${topPad + imgSize + 3}px;" src="${anilstBase64Img}">`; e.querySelector('div.film-poster').appendChild(aAnilist); const jpTitleElement = `<h3 class="film-name custom-jp-title" style="font-size: 12px; color: gray;">${titleJp}</h3>`; e.querySelector('.film-detail')?.children?.[0]?.insertAdjacentHTML('afterend', jpTitleElement); [aHiAnime, aAnilist].forEach(e => e.addEventListener('click', async function (e) { e.preventDefault(); // Prevent default behavior // Get configuration from data attributes const endpoint = this.dataset.endpoint; const fallbackUrl = this.dataset.fallbackUrl; const type = this.dataset.type; // Get cached finalUrl const cachedFinalUrl = this.dataset.finalUrl; if (cachedFinalUrl) { GM_openInTab(cachedFinalUrl, {active: true}); return; } // Get cached link /** @type {?Link} */ const cachedLink = cachedLinkMap.get(endpoint); if (cachedLink) { let finalUrl; if (type === linkType.MAL && cachedLink.mal_id) { finalUrl = `https://myanimelist.net/anime/${cachedLink.mal_id}`; } else if (type === linkType.ANILIST && cachedLink.anilist_id) { finalUrl = `https://anilist.co/anime/${cachedLink.anilist_id}`; } else { finalUrl = fallbackUrl; } GM_openInTab(finalUrl || fallbackUrl, {active: true}); return; } // Show loading state // Add small spinner next to existing icon const spinner = document.createElement('span'); spinner.className = 'mal-mini-spinner'; // Add loading class to parent this.classList.add('mal-link-loading'); // Position spinner absolutely near the icon spinner.style.cssText = ` position: absolute; left: 30px; // top: ${topPad + 9}px; top: ${type === linkType.MAL ? topPad + 9 : topPad + imgSize + 3 + 9}px; `; this.appendChild(spinner); this.style.pointerEvents = 'none'; try { // Fetch required data const res = await fetch(endpoint); if (!res?.ok) { GM_openInTab(fallbackUrl, {active: true}); return; } const data = await res.text(); const malId = data.match(/"mal_id":"(\d+)",/)?.[1]; const anilistId = data.match(/"anilist_id":"(\d+)",/)?.[1]; if (malId || anilistId) { cachedLinkMap.set(endpoint, { mal_id: malId, anilist_id: anilistId }); } if ((!malId && type === linkType.MAL) || (!anilistId && type === linkType.ANILIST) || (!anilistId && !malId) || !type) { GM_openInTab(fallbackUrl, {active: true}); return; } // Open the link const finalUrl = type === linkType.MAL ? `https://myanimelist.net/anime/${malId}` : `https://anilist.co/anime/${anilistId}`; this.dataset.finalUrl = finalUrl; GM_openInTab(finalUrl, {active: true}); } catch (error) { console.error('Failed to process link:', error); } finally { // Clean up (loading) spinner.remove(); this.classList.remove('mal-link-loading'); this.style.pointerEvents = ''; } })); }); } window.addEventListener('load', function () { addHiAnimeBtnAndSubTitle(); }); addHiAnimeBtnAndSubTitle(); const syncData = JSON.parse(document.getElementById('syncData')?.textContent || null); if (!syncData) return; const title = document.getElementsByClassName('film-name')[0]; if (!title) return; if (syncData.mal_id) { const a = createLink(`https://myanimelist.net/anime/${syncData.mal_id}`, 'MyAnimeList'); a.innerHTML = ` <img width="25" height="25" src="${malBase64Img}">`; title.appendChild(a); } if (syncData.anilist_id) { const a = createLink(`https://anilist.co/anime/${syncData.anilist_id}`, 'AniList'); a.innerHTML = ` <img width="25" height="25" src="${anilstBase64Img}">`; title.appendChild(a); } function createLink(href, title, endpoint, type) { const a = document.createElement('a'); a.target = '_blank'; a.rel = 'noreferrer,noopener'; a.href = href; a.title = title; if (endpoint) { a.href = "#"; a.dataset.endpoint = endpoint; a.dataset.fallbackUrl = href; a.dataset.type = type; } return a; } })();