Greasy Fork

Twitch Status Badge at img

スレッド内の書き込みにTwitch のリンクがあったら、shields.ioの配信状況バッジを挿入します。 multitwitchのリンクだった場合、チャンネルIDごとに表示します

当前为 2025-06-13 提交的版本,查看 最新版本

// ==UserScript==
// @name         Twitch Status Badge at img
// @namespace    https://github.com/uzuky
// @version      5.2
// @description  スレッド内の書き込みにTwitch のリンクがあったら、shields.ioの配信状況バッジを挿入します。 multitwitchのリンクだった場合、チャンネルIDごとに表示します
// @author       uzuky
// @license      MIT
// @match        https://*.2chan.net/*/res/*
// @match        http://*.2chan.net/*/res/*
// @match        https://tsumanne.net/*
// @match        https://kako.futakuro.com/futa/*
// @match        https://*.ftbucket.info/*/cont/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // shields.ioのバッジの見た目をカスタマイズする設定
    // 詳細は https://shields.io/badges/twitch-status
    const shieldOptions = {
        style: 'flat',          // スタイル (plastic, flat, flat-square, for-the-badge, social)
        logo: 'twitch',         // 表示するロゴ (https://simpleicons.org/ にあるやつ)
        logoColor: 'white',     // ロゴの色
        //logoSize: '',           // ロゴのサイズ
        label: '',              // ラベルの文字
        //labelColor: '',         // ラベルの色
        //color: '',              // ラベルの背景色
        //cacheSeconds: '',       // 配信状況がキャッシュされる秒数 デフォルトの5分より短くしても意味がない
    };

    function createShieldUrl(channelName, customOptions = {}) {
        const finalOptions = { ...shieldOptions, ...customOptions };
        const params = new URLSearchParams(finalOptions);
        return `https://img.shields.io/twitch/status/${channelName}?${params.toString()}`;
    }

    function createBadgeImage(channelName, customOptions = {}) {
        const img = document.createElement('img');
        img.src = createShieldUrl(channelName, customOptions);
        img.alt = `Twitch Status for ${channelName}`;
        img.title = `${channelName} の配信状況`;
        img.style.verticalAlign = 'middle';
        img.style.height = '1.1em';
        return img;
    }


    // 通常のTwitchリンクだったときの処理
    function processSingleTwitchLink(aTag) {
        const twitchRegex = /^https?:\/\/(?:www\.)?twitch\.tv\/([a-zA-Z0-9_]+)\/?$/;
        const match = aTag.href.match(twitchRegex);

        if (match && match[1]) {
            const channelName = match[1];
            const badge = createBadgeImage(channelName);
            badge.style.marginLeft = '4px';
            aTag.appendChild(badge);
            aTag.dataset.twitchBadgeAdded = 'true';
        }
    }

    // multitwitch だったときの処理
    function processMultiTwitchLink(aTag) {
        const multitwitchRegex = /^https?:\/\/(?:www\.)?multitwitch\.(?:tv|live)\/(.+)$/;
        const match = aTag.href.match(multitwitchRegex);

        if (match && match[1]) {
            const path = match[1].replace(/\/$/, '');
            if (!path) return;
            const channels = path.split('/').filter(p => p);

            const badgeContainer = document.createElement('span');
            badgeContainer.style.display = 'block';
            badgeContainer.style.marginTop = '4px';

            channels.forEach(channelName => {
                const link = document.createElement('a');
                link.href = `https://www.twitch.tv/${channelName}`;
                link.target = '_blank';
                link.rel = 'noopener noreferrer';
                link.style.marginRight = '4px';

                // ラベルにチャンネル名を追加
                const badge = createBadgeImage(channelName, { label: channelName });

                link.appendChild(badge);

                badgeContainer.appendChild(link);
            });

            aTag.insertAdjacentElement('afterend', badgeContainer);
            aTag.dataset.twitchBadgeAdded = 'true';
        }
    }


    function scanAllLinksOnPage() {
        const links = document.querySelectorAll('a');

        links.forEach(aTag => {
            // すでにバッジが追加されてたり、imgタグが中にはいってたり、リンクが設定されていない場合は何もしない
            if (aTag.dataset.twitchBadgeAdded || aTag.querySelector('img') || aTag.href === '') {
                return;
            }

            if (aTag.hostname.endsWith('multitwitch.tv') || aTag.hostname.endsWith('multitwitch.live')) {
                processMultiTwitchLink(aTag);
            } else if (aTag.hostname.endsWith('twitch.tv')) {
                processSingleTwitchLink(aTag);
            }
        });
    }

    setTimeout(scanAllLinksOnPage, 250);

    // ページの動的更新に対応
    const observer = new MutationObserver(() => {
        clearTimeout(observer.timer);
        observer.timer = setTimeout(scanAllLinksOnPage, 500);
    });
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();