AniList Shiru Launcher

Adds a button to AniList anime pages that opens the corresponding anime in Shiru

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AniList Shiru Launcher
// @namespace    https://anilist.co/
// @version      1.0
// @description  Adds a button to AniList anime pages that opens the corresponding anime in Shiru
// @author       darklinkpower
// @match        https://anilist.co/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    const DEBUG = true;
    const PREFIX = '[ShiruButton]';
    const log = (msg, ...args) => DEBUG && console.log(`${PREFIX} ${msg}`, ...args);

    log('Script loaded');

    let lastUrl = location.href;

    // Observe SPA navigation
    const navObserver = new MutationObserver(() => {
        const currentUrl = location.href;
        if (currentUrl !== lastUrl) {
            log(`URL changed: ${lastUrl} → ${currentUrl}`);
            lastUrl = currentUrl;
            onPageChange();
        }
    });
    navObserver.observe(document, { childList: true, subtree: true });

    onPageChange();

    function onPageChange() {
        const match = location.pathname.match(/^\/anime\/(\d+)/);
        if (!match) {
            log('Not an anime page, skipping.');
            return;
        }

        const animeId = match[1];
        const uri = `shiru://anime/${animeId}`;
        log(`Anime detected: ID=${animeId}, URI=${uri}`);

        waitForElement('h1[data-v-5776f768]', (h1) => {
            if (!h1) return log('Target <h1> not found.');

            const existing = h1.querySelector('.shiru-rel-link');
            if (existing) {
                const anchor = existing.querySelector('a.link');
                if (anchor) {
                    anchor.href = uri;
                    anchor.title = `Open in Shiru (${animeId})`;
                    log('Updated existing anchor href to:', anchor.href);
                }
                return;
            }

            addButton(h1, uri);
        });
    }

    function addButton(h1, uri) {
        log('Adding new button for URI:', uri);

        const container = document.createElement('div');
        container.className = 'shiru-rel-link';
        container.style.cssFloat = 'right';
        container.style.display = 'inline-flex';
        container.style.alignItems = 'center';
        container.style.justifyContent = 'center';
        container.style.marginLeft = '16px';
        container.style.width = '16px';
        container.style.height = '16px';

        const link = document.createElement('a');
        link.className = 'link';
        link.title = 'Open in Shiru';
        link.href = uri;
        link.style.display = 'inline-flex';
        link.style.alignItems = 'center';
        link.style.justifyContent = 'center';
        link.style.width = '100%';
        link.style.height = '100%';

        const img = document.createElement('img');
        img.src = 'https://raw.githubusercontent.com/RockinChaos/Shiru/refs/heads/master/common/public/tray_icon_filled.png';
        img.alt = 'Shiru';
        img.style.cursor = 'pointer';
        img.style.objectFit = 'contain';
        img.style.width = '16px';
        img.style.height = '16px';
        img.style.verticalAlign = 'middle';

        link.appendChild(img);
        container.appendChild(link);
        h1.appendChild(container);

        log('Button added successfully:', container);
    }

    function waitForElement(selector, callback, retries = 0) {
        const el = document.querySelector(selector);
        if (el) {
            log(`Element found: ${selector}`);
            callback(el);
            return;
        }
        if (retries > 50) {
            log(`Timeout waiting for element: ${selector}`);
            return;
        }

        setTimeout(() => waitForElement(selector, callback, retries + 1), 150);
    }
})();