Kick ↔ MultiKick Enhancer

Adds a “➕” on Kick.com to view multiple streams at once on MultiKick.com

目前為 2025-04-19 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Kick ↔ MultiKick Enhancer
// @namespace    http://tampermonkey.net/
// @version      1.1
// @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() {
      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();
            // open (or reuse) the MultiKick tab at its root
            const mkWin = window.open('https://multikick.com', WINDOW_NAME);
            if (!mkWin) return;
            mkWin.focus();
            // tell it to append our slug
            const msg = { type: 'MK_APPEND', slug };
            mkWin.postMessage(msg, 'https://multikick.com');
            // in case it's still loading, send again
            setTimeout(() => mkWin.postMessage(msg, 'https://multikick.com'), 500);
          });

          a.parentElement.insertBefore(btn, a.nextSibling);
        });
    }

    addButtons();
    new MutationObserver(addButtons)
      .observe(document.body, { childList: true, subtree: true });
  }

  // ─── MultiKick.com side ────────────────────────────────────────────────
  else if (host === 'multikick.com') {
    // 0) Name the window immediately so future window.open() calls reuse it
    if (window.name !== WINDOW_NAME) {
      window.name = WINDOW_NAME;
    }

    // 1) Prevent their router from wiping out deep URLs
    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);
      }
    });

    // 2) Listen for “MK_APPEND” messages and do the append+reload here
    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);
        const newPath = '/' + parts.join('/');
        history.replaceState(null, '', newPath);
        location.reload();
      }
    });
  }
})();