Kick ↔ MultiKick Enhancer

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

当前为 2025-04-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Kick ↔ MultiKick Enhancer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Adds a “➕” on Kick.com to view multiple streams at once on MultiKick.com
  6. // @match https://kick.com/*
  7. // @match https://www.kick.com/*
  8. // @match https://multikick.com/*
  9. // @match https://www.multikick.com/*
  10. // @grant none
  11. // @run-at document-end
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. const host = location.hostname.replace(/^www\./, '');
  18. const WINDOW_NAME = 'multikick-window';
  19.  
  20. // ─── Kick.com side ─────────────────────────────────────────────────────
  21. if (host === 'kick.com') {
  22. function addButtons() {
  23. document
  24. .querySelectorAll('a[data-state][href^="/"] > img.rounded-full')
  25. .forEach(img => {
  26. const a = img.parentElement;
  27. if (a.dataset.mkDone) return;
  28. a.dataset.mkDone = '1';
  29.  
  30. const slug = a.getAttribute('href').slice(1);
  31. const btn = document.createElement('a');
  32. btn.textContent = '➕';
  33. btn.href = '#';
  34. btn.title = 'Add to MultiKick';
  35. Object.assign(btn.style, {
  36. marginLeft: '4px',
  37. cursor: 'pointer',
  38. fontSize: '1em',
  39. textDecoration: 'none',
  40. color: 'inherit',
  41. });
  42.  
  43. btn.addEventListener('click', e => {
  44. e.preventDefault();
  45. // open (or reuse) the MultiKick tab at its root
  46. const mkWin = window.open('https://multikick.com', WINDOW_NAME);
  47. if (!mkWin) return;
  48. mkWin.focus();
  49. // tell it to append our slug
  50. const msg = { type: 'MK_APPEND', slug };
  51. mkWin.postMessage(msg, 'https://multikick.com');
  52. // in case it's still loading, send again
  53. setTimeout(() => mkWin.postMessage(msg, 'https://multikick.com'), 500);
  54. });
  55.  
  56. a.parentElement.insertBefore(btn, a.nextSibling);
  57. });
  58. }
  59.  
  60. addButtons();
  61. new MutationObserver(addButtons)
  62. .observe(document.body, { childList: true, subtree: true });
  63. }
  64.  
  65. // ─── MultiKick.com side ────────────────────────────────────────────────
  66. else if (host === 'multikick.com') {
  67. // 0) Name the window immediately so future window.open() calls reuse it
  68. if (window.name !== WINDOW_NAME) {
  69. window.name = WINDOW_NAME;
  70. }
  71.  
  72. // 1) Prevent their router from wiping out deep URLs
  73. const desired = location.pathname;
  74. function wrap(orig) {
  75. return function(state, _title, url) {
  76. if ((url === '/' || url === '') && desired !== '/') {
  77. url = desired;
  78. }
  79. return orig.call(this, state, '', url);
  80. };
  81. }
  82. history.pushState = wrap(history.pushState);
  83. history.replaceState = wrap(history.replaceState);
  84. window.addEventListener('popstate', () => {
  85. if (location.pathname === '/' && desired !== '/') {
  86. history.replaceState(null, '', desired);
  87. }
  88. });
  89.  
  90. // 2) Listen for “MK_APPEND” messages and do the append+reload here
  91. window.addEventListener('message', e => {
  92. if (!/^https?:\/\/(?:www\.)?kick\.com$/.test(e.origin)) return;
  93. const msg = e.data || {};
  94. if (msg.type !== 'MK_APPEND' || typeof msg.slug !== 'string') return;
  95.  
  96. const parts = location.pathname
  97. .replace(/^\/|\/$/g, '')
  98. .split('/')
  99. .filter(Boolean);
  100.  
  101. if (!parts.includes(msg.slug)) {
  102. parts.push(msg.slug);
  103. const newPath = '/' + parts.join('/');
  104. history.replaceState(null, '', newPath);
  105. location.reload();
  106. }
  107. });
  108. }
  109. })();