Greasy Fork

Greasy Fork is available in English.

Better DGG Kick Embed

Slightly hacky solution to embed full kick site instead of the embed. You can toggle kick chat on and off next to the script toggle in your userscript host extension.

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

// ==UserScript==
// @name        Better DGG Kick Embed
// @namespace   yuniDev.kickembed
// @match       https://kick.com/*
// @match       https://www.destiny.gg/bigscreen
// @match       https://destiny.gg/bigscreen
// @grant       GM.registerMenuCommand
// @grant       GM.setValue
// @grant       GM.getValue
// @version     1.6
// @license     MIT
// @author      yuniDev
// @run-at      document-idle
// @description Slightly hacky solution to embed full kick site instead of the embed. You can toggle kick chat on and off next to the script toggle in your userscript host extension.
// ==/UserScript==

let showChat = false;
GM.getValue("show-chat", "false").then(value => showChat = value === "true");

let insertedFrame = null;

// We need to hide the embed from querySelector("iframe")
class iFrameWrapper extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.innerHTML = `<iframe is="x-frame-bypass" style="width:100%;height:100%;border:none;" class="embed-frame" src="" allow="fullscreen; autoplay; encrypted-media; picture-in-picture; web-share"></iframe>`;
    this.iframe = shadowRoot.querySelector('iframe');
  }

  static get observedAttributes() { return ['src'] };

  connectedCallback() { this.updateSrc(); }
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'src') this.updateSrc();
  }

  updateSrc() {
    const src = this.getAttribute('src');
    if (src && this.iframe) this.iframe.src = src;
  }
}
customElements.define('kick-embed-iframe-wrapper', iFrameWrapper);

function htmlToNode(html) {
  const template = document.createElement('template');
  template.innerHTML = html;
  return template.content.firstChild;
}

function hideFunc(el) {
  el.style.display = 'none';
}

function addObserver(selector, func = hideFunc) {
  function checkAndHide(obs) {
    const elToHide = document.querySelector(selector);
    if (elToHide) {
      func(elToHide);
      obs.disconnect();
    }
  }
  const observer = new MutationObserver((_, obs) => checkAndHide(obs));
  observer.observe(document.body, { childList: true, subtree: true });
  checkAndHide(observer);
}

function hideSurroundings() {
  addObserver("[data-sidebar]", el => {
    el.setAttribute("data-sidebar", false);
    el.setAttribute("data-theatre", true);
    el.setAttribute("data-chat", showChat);
  });

  addObserver(".z-controls.hidden button", el => el.parentNode.style.display = 'none');
  addObserver("#channel-chatroom > div:first-child", el => el.style.display = 'none');

  addObserver(".z-modal:has(button[data-testid='accept-cookies'])");
}

function updateEmbed() {
  const iframe = document.querySelector("iframe.embed-frame");
  if (!iframe) return;
  const iframeLocation = iframe.src;
  let channel = null;
  if (iframeLocation.includes("player.kick")) {
    channel = iframeLocation.split('/').pop();
  } else if (window.location.hash.startsWith("#kick/")) {
    channel = window.location.hash.split('/')[1];
  }
  if (!channel) return;
  iframe.style.display = 'none';
  if (insertedFrame) return;
  insertedFrame = htmlToNode(`<kick-embed-iframe-wrapper class="embed-frame" style="display:block" src="https://kick.com/${channel}"></kick-embed-iframe-wrapper>`);
  iframe.parentNode.appendChild(insertedFrame);
}

function loadDGG(channel) {
  document.body.appendChild(htmlToNode(`<script type="module" src="https://unpkg.com/x-frame-bypass"></script>`));

  function embedObserver(list, obs) {
    for (const mutation of list) {
      if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
        for (const node of mutation.addedNodes) {
          if (node.nodeType === Node.ELEMENT_NODE &&
              node.tagName === 'IFRAME' &&
              node.classList.contains('embed-frame'))
            updateEmbed();
        }
      }
    }
  }
  const observer = new MutationObserver((obj, obs) => embedObserver(obj, obs));
  observer.observe(document.getElementById("embed"), { childList: true, subtree: true });
  updateEmbed();

  function clearObserver() {
    observer.disconnect();
    removeEventListener('hashchange', clearObserver);
  }
  addEventListener('hashchange', clearObserver);
}

async function toggleKickChat() {
  let showChat = (await GM.getValue("show-chat", "false")) === "true";
  showChat = !showChat;
  await GM.setValue("show-chat", String(showChat));
  const iframe = document.querySelector("iframe.embed-frame");
  iframe.contentWindow.postMessage({ type: "yuniDev.kickembed.show-chat", value: showChat }, "*");
}

function setupChatToggleCallback() {
  window.addEventListener("message", ({ data }) => {
    const { type, value } = data;
    if (type == "yuniDev.kickembed.show-chat") {
      showChat = value;
      document.body.querySelector("[data-sidebar]").setAttribute("data-chat", value);
    }
  });
}

if (window.location.hostname === "kick.com" && window.self !== window.top) { // Kick inside of iframe
  hideSurroundings();
  setInterval(() => {
    if (![...document.querySelectorAll("nav")].find(el => el.getAttribute("style") && el.getAttribute("style").indexOf("display: none") > -1)) hideSurroundings();
  }, 200);
  setupChatToggleCallback();
} else if (window.location.pathname === "/bigscreen") {
  loadDGG();
  addEventListener('hashchange', () => {
    insertedFrame?.remove();
    insertedFrame = null;
    loadDGG();
  });

  GM.registerMenuCommand("Toggle Kick Chat", toggleKickChat);
}