您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
スレッド内の書き込みにTwitch のリンクがあったら、shields.ioの配信状況バッジを挿入します。 multitwitchのリンクだった場合、チャンネルIDごとに表示します
当前为
// ==UserScript== // @name Twitch Status Badge at img // @namespace https://github.com/uzuky // @version 7.5 // @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()}`; } // バッジのimgを作成する 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.2em'; return img; } // バッジのimgをaで囲む function createLinkedBadge(channelName, customOptions = {}, styleObject = {}) { const badge = createBadgeImage(channelName, customOptions); const link = document.createElement('a'); link.href = `https://www.twitch.tv/${channelName}`; link.target = '_blank'; link.rel = 'noopener noreferrer'; Object.assign(link.style, styleObject); link.appendChild(badge); return link; } // バッジ1つだけの処理 function addSingleTwitchBadge(targetElement, channelName) { const linkedBadge = createLinkedBadge( channelName, {}, { marginLeft: '4px' } ); targetElement.parentNode.insertBefore(linkedBadge, targetElement.nextSibling); } // バッジが複数あるときの処理 function addMultiTwitchBadges(targetElement, channels) { const badgeContainer = document.createElement('span'); badgeContainer.style.display = 'block'; badgeContainer.style.marginTop = '4px'; channels.forEach(channelName => { const linkedBadge = createLinkedBadge( channelName, // バッジのラベルにチャンネル名を追加 { label: channelName }, { marginRight: '4px' } ); badgeContainer.appendChild(linkedBadge); }); targetElement.parentNode.insertBefore(badgeContainer, targetElement.nextSibling); } // メインの処理 function scanPageContent() { // aタグになっている部分を処理する const links = document.querySelectorAll('a:not([data-badged])'); links.forEach(aTag => { aTag.dataset.badged = 'true'; const linkText = aTag.textContent; if (!linkText) return; const multiMatch = linkText.match(/(?:https?:\/\/(?:www\.)?)?multitwitch\.(?:tv|live)\/([a-zA-Z0-9_\/]+)/); if (multiMatch && multiMatch[1]) { const path = multiMatch[1].replace(/\/$/, ''); if (path) addMultiTwitchBadges(aTag, path.split('/').filter(p => p)); return; } const singleMatch = linkText.match(/(?:https?:\/\/(?:www\.)?)?twitch\.tv\/([a-zA-Z0-9_]+)/); if (singleMatch && singleMatch[1]) { addSingleTwitchBadge(aTag, singleMatch[1]); } }); // もしaタグになっていないURLがあっても検出して処理する const blockquotes = document.querySelectorAll('blockquote:not([data-badged])'); blockquotes.forEach(bq => { bq.dataset.badged = 'true'; const walker = document.createTreeWalker(bq, NodeFilter.SHOW_TEXT); const textNodesToProcess = []; let node; while(node = walker.nextNode()) { if (node.parentElement.closest('a, script')) continue; textNodesToProcess.push(node); } textNodesToProcess.reverse().forEach(textNode => { const text = textNode.nodeValue; const multiTwitchRegex = /(?:https?:\/\/(?:www\.)?)?multitwitch\.(?:tv|live)\/([a-zA-Z0-9_\/]+)/g; let multiMatch; if (multiMatch = multiTwitchRegex.exec(text)) { const path = multiMatch[1].replace(/\/$/, ''); if(path) { const channels = path.split('/').filter(p => p); addMultiTwitchBadges(textNode, channels); return; } } const singleTwitchRegex = /(?:https?:\/\/(?:www\.)?)?twitch\.tv\/[a-zA-Z0-9_]+/g; let singleMatch; if (singleMatch = singleTwitchRegex.exec(text)) { const url = singleMatch[0]; const channelName = url.match(/twitch\.tv\/([a-zA-Z0-9_]+)/)[1]; addSingleTwitchBadge(textNode, channelName); return; } }); }); } setTimeout(scanPageContent, 500); // ページの動的更新に対応するやつ const observer = new MutationObserver(() => { clearTimeout(observer.timer); observer.timer = setTimeout(scanPageContent, 500); }); observer.observe(document.body, { childList: true, subtree: true }); })();