您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a “➕” on Kick.com to view multiple streams at once on MultiKick.com
// ==UserScript== // @name Kick ↔ MultiKick Enhancer // @namespace http://tampermonkey.net/ // @version 1.7 // @description Adds a “➕” on Kick.com to view multiple streams at once on MultiKick.com // @match https://kick.com/* // @match https://www.kick.com/* // @match https://multikick.com/* // @match https://www.multikick.com/* // @grant none // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; const host = location.hostname.replace(/^www\./, ''); const WINDOW_NAME = 'multikick-window'; // ─── Kick.com side ───────────────────────────────────────────────────── if (host === 'kick.com') { function addButtons() { // streamer-list pages document .querySelectorAll('a[data-state][href^="/"] > img.rounded-full') .forEach(img => { const a = img.parentElement; if (a.dataset.mkDone) return; a.dataset.mkDone = '1'; const slug = a.getAttribute('href').slice(1); const btn = document.createElement('a'); btn.textContent = '➕'; btn.href = '#'; btn.title = 'Add to MultiKick'; Object.assign(btn.style, { marginLeft: '4px', cursor: 'pointer', fontSize: '1em', textDecoration: 'none', color: 'inherit', }); btn.addEventListener('click', e => { e.preventDefault(); const mkWin = window.open('https://multikick.com', WINDOW_NAME); if (!mkWin) return; mkWin.focus(); const msg = { type: 'MK_APPEND', slug }; mkWin.postMessage(msg, 'https://multikick.com'); setTimeout(() => mkWin.postMessage(msg, 'https://multikick.com'), 500); }); a.parentElement.insertBefore(btn, a.nextSibling); }); // single-streamer pages const header = document.getElementById('channel-username'); if (header && !header.dataset.mkDone) { header.dataset.mkDone = '1'; const slug = location.pathname.replace(/^\/|\/$/g, ''); const btn = document.createElement('a'); btn.textContent = '➕'; btn.href = '#'; btn.title = 'Add to MultiKick'; Object.assign(btn.style, { marginLeft: '8px', cursor: 'pointer', fontSize: '0.9em', textDecoration:'none', color: 'inherit', verticalAlign: 'middle', }); btn.addEventListener('click', e => { e.preventDefault(); const mkWin = window.open('https://multikick.com', WINDOW_NAME); if (!mkWin) return; mkWin.focus(); const msg = { type: 'MK_APPEND', slug }; mkWin.postMessage(msg, 'https://multikick.com'); setTimeout(() => mkWin.postMessage(msg, 'https://multikick.com'), 500); }); header.insertAdjacentElement('afterend', btn); } } addButtons(); new MutationObserver(addButtons) .observe(document.body, { childList: true, subtree: true }); } // ─── MultiKick.com side ──────────────────────────────────────────────── else if (host === 'multikick.com') { // name the window for reuse if (window.name !== WINDOW_NAME) window.name = WINDOW_NAME; // patch History API const desired = location.pathname; function wrap(orig) { return function(state, _title, url) { if ((url === '/' || url === '') && desired !== '/') url = desired; return orig.call(this, state, '', url); }; } history.pushState = wrap(history.pushState); history.replaceState = wrap(history.replaceState); window.addEventListener('popstate', () => { if (location.pathname === '/' && desired !== '/') { history.replaceState(null, '', desired); } }); // handle append messages window.addEventListener('message', e => { if (!/^https?:\/\/(?:www\.)?kick\.com$/.test(e.origin)) return; const msg = e.data || {}; if (msg.type !== 'MK_APPEND' || typeof msg.slug !== 'string') return; const parts = location.pathname .replace(/^\/|\/$/g, '') .split('/') .filter(Boolean); if (!parts.includes(msg.slug)) { parts.push(msg.slug); history.replaceState(null, '', '/' + parts.join('/')); location.reload(); } }); // hook delete buttons—update URL only (no removal) function hookDeletes() { document .querySelectorAll('button[aria-label="delete stream"]') .forEach(btn => { if (btn.dataset.mkHooked) return; btn.dataset.mkHooked = '1'; btn.addEventListener('click', () => { const wrapper = btn.closest('div.relative'); if (!wrapper) return; const iframe = wrapper.querySelector('iframe'); if (!iframe) return; const slug = iframe.getAttribute('src').split('/').pop(); if (!slug) return; const filtered = location.pathname .replace(/^\/|\/$/g, '') .split('/') .filter(p => p !== slug); history.replaceState(null, '', '/' + filtered.join('/')); }); }); } hookDeletes(); new MutationObserver(hookDeletes) .observe(document.body, { childList: true, subtree: true }); } })();