Greasy Fork is available in English.

DGG Multichat - Chat Split View

Split view: DGG chat + stream chat (Kick or YouTube) side-by-side on destiny.gg/bigscreen. Use #kick/username or #youtube/VIDEO_ID. Resizable panel, no backend.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         DGG Multichat - Chat Split View
// @namespace    https://github.com/RadiantSol/DGGMultichat
// @version      1.1.0
// @description  Split view: DGG chat + stream chat (Kick or YouTube) side-by-side on destiny.gg/bigscreen. Use #kick/username or #youtube/VIDEO_ID. Resizable panel, no backend.
// @author       RadiantSol
// @license      MIT
// @supportURL   https://github.com/RadiantSol/DGGMultichat/issues
// @match        https://www.destiny.gg/bigscreen*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const STYLE_ID = 'dgg-kick-chat-split-styles';
    const PANEL_ID = 'dgg-kick-chat-panel';
    const HANDLE_ID = 'dgg-kick-chat-resize-handle';
    const TOGGLE_ID = 'dgg-kick-chat-toggle';
    const COLLAPSED_WIDTH = 28;
    const DEFAULT_WIDTH = 400;
    const MIN_WIDTH = 280;
    const MAX_WIDTH = 800;
    const STORAGE_KEY = 'dgg-kick-chat-width';
    const COLLAPSED_KEY = 'dgg-kick-chat-collapsed';

    /** @returns {{ type: 'kick', id: string } | { type: 'youtube', id: string } | null} */
    function getEmbedFromPath() {
        const hash = window.location.hash.slice(1).trim();
        if (!hash) return null;
        const segments = hash.split('/').filter(Boolean);
        if (segments[0] === 'kick' && segments[1]) return { type: 'kick', id: segments[1] };
        if (segments[0] === 'youtube' && segments[1]) return { type: 'youtube', id: segments[1] };
        return null;
    }

    function getStoredWidth() {
        try {
            const w = parseInt(localStorage.getItem(STORAGE_KEY), 10);
            if (w >= MIN_WIDTH && w <= MAX_WIDTH) return w;
        } catch (_) {}
        return DEFAULT_WIDTH;
    }

    function setStoredWidth(w) {
        try {
            localStorage.setItem(STORAGE_KEY, String(w));
        } catch (_) {}
    }

    function getStoredCollapsed() {
        try {
            return localStorage.getItem(COLLAPSED_KEY) === '1';
        } catch (_) {}
        return false;
    }

    function setStoredCollapsed(collapsed) {
        try {
            localStorage.setItem(COLLAPSED_KEY, collapsed ? '1' : '0');
        } catch (_) {}
    }

    function injectStyles() {
        if (document.getElementById(STYLE_ID)) return;
        const style = document.createElement('style');
        style.id = STYLE_ID;
        style.textContent = `
            #${PANEL_ID} {
                position: fixed;
                top: 0;
                right: 0;
                bottom: 0;
                width: ${getStoredWidth()}px;
                display: flex;
                flex-direction: column;
                background: #18181b;
                z-index: 9999;
                box-shadow: -2px 0 8px rgba(0,0,0,0.3);
                box-sizing: border-box;
            }
            #${PANEL_ID} iframe {
                flex: 1;
                width: 100%;
                min-height: 0;
                border: 0;
            }
            #${HANDLE_ID} {
                position: absolute;
                left: 0;
                top: 0;
                bottom: 0;
                width: 6px;
                cursor: col-resize;
                background: transparent;
            }
            #${HANDLE_ID}:hover {
                background: rgba(255,255,255,0.1);
            }
            #${PANEL_ID}.dgg-kick-collapsed #${HANDLE_ID},
            #${PANEL_ID}.dgg-kick-collapsed iframe {
                display: none;
            }
            #${PANEL_ID}.dgg-kick-collapsed {
                background: transparent;
                box-shadow: none;
            }
            #${TOGGLE_ID} {
                position: absolute;
                top: 8px;
                right: 8px;
                width: 24px;
                height: 24px;
                border: none;
                background: rgba(255,255,255,0.08);
                color: #a1a1aa;
                cursor: pointer;
                border-radius: 4px;
                font-size: 12px;
                display: flex;
                align-items: center;
                justify-content: center;
                z-index: 1;
                transition: background 0.15s, color 0.15s;
            }
            #${TOGGLE_ID}:hover {
                background: rgba(255,255,255,0.15);
                color: #fff;
            }
            #${PANEL_ID}.dgg-kick-collapsed #${TOGGLE_ID} {
                top: 50%;
                right: auto;
                left: 50%;
                transform: translate(-50%, -50%);
                background: rgba(0,0,0,0.35);
                opacity: 0.6;
            }
            #${PANEL_ID}.dgg-kick-collapsed #${TOGGLE_ID}:hover {
                opacity: 1;
                background: rgba(0,0,0,0.5);
            }
            body:has(#${PANEL_ID}) {
                margin-right: var(--dgg-kick-panel-width, ${getStoredWidth()}px);
            }
            body:has(#${PANEL_ID}.dgg-kick-collapsed) {
                margin-right: 0;
            }
        `;
        document.head.appendChild(style);
    }

    function applyCollapsedState(panel, collapsed) {
        if (!panel) return;
        const toggle = panel.querySelector('#' + TOGGLE_ID);
        if (!toggle) return;
        if (collapsed) {
            panel.classList.add('dgg-kick-collapsed');
            panel.style.width = COLLAPSED_WIDTH + 'px';
            document.documentElement.style.setProperty('--dgg-kick-panel-width', '0');
            toggle.textContent = '\u25B6';
            toggle.title = 'Expand stream chat';
            toggle.setAttribute('aria-label', 'Expand stream chat');
        } else {
            panel.classList.remove('dgg-kick-collapsed');
            panel.style.width = getStoredWidth() + 'px';
            document.documentElement.style.setProperty('--dgg-kick-panel-width', getStoredWidth() + 'px');
            toggle.textContent = '\u25C0';
            toggle.title = 'Collapse stream chat';
            toggle.setAttribute('aria-label', 'Collapse stream chat');
        }
    }

    function setupCollapse(panel, toggle) {
        toggle.addEventListener('click', function () {
            const collapsed = !panel.classList.contains('dgg-kick-collapsed');
            setStoredCollapsed(collapsed);
            applyCollapsedState(panel, collapsed);
        });
    }

    function makeResizable(panel) {
        const handle = document.getElementById(HANDLE_ID);
        if (!handle) return;

        let startX, startWidth;

        function onMove(e) {
            const dx = startX - e.clientX;
            const w = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth + dx));
            panel.style.width = w + 'px';
            document.documentElement.style.setProperty('--dgg-kick-panel-width', w + 'px');
            setStoredWidth(w);
        }

        function onUp() {
            document.removeEventListener('mousemove', onMove);
            document.removeEventListener('mouseup', onUp);
        }

        handle.addEventListener('mousedown', function (e) {
            e.preventDefault();
            startX = e.clientX;
            startWidth = panel.offsetWidth;
            document.addEventListener('mousemove', onMove);
            document.addEventListener('mouseup', onUp);
        });
    }

    function getChatIframeSrc(embed) {
        if (embed.type === 'kick') {
            return 'https://kick.com/popout/' + embed.id + '/chat';
        }
        if (embed.type === 'youtube') {
            const domain = window.location.hostname || 'www.destiny.gg';
            return 'https://www.youtube.com/live_chat?v=' + encodeURIComponent(embed.id) + '&embed_domain=' + encodeURIComponent(domain);
        }
        return '';
    }

    function getChatIframeTitle(embed) {
        if (embed.type === 'kick') return 'Kick chat: ' + embed.id;
        if (embed.type === 'youtube') return 'YouTube live chat: ' + embed.id;
        return 'Stream chat';
    }

    function showStreamPanel() {
        const embed = getEmbedFromPath();
        if (!embed) return;

        let panel = document.getElementById(PANEL_ID);
        if (panel) {
            const iframe = panel.querySelector('iframe');
            if (iframe) {
                iframe.src = getChatIframeSrc(embed);
                iframe.title = getChatIframeTitle(embed);
            }
            return;
        }

        injectStyles();

        panel = document.createElement('div');
        panel.id = PANEL_ID;

        const toggle = document.createElement('button');
        toggle.id = TOGGLE_ID;
        toggle.type = 'button';
        toggle.title = 'Collapse stream chat';
        toggle.textContent = '\u25C0';
        toggle.setAttribute('aria-label', 'Collapse stream chat');

        const handle = document.createElement('div');
        handle.id = HANDLE_ID;
        handle.title = 'Drag to resize';

        const iframe = document.createElement('iframe');
        iframe.src = getChatIframeSrc(embed);
        iframe.title = getChatIframeTitle(embed);

        panel.appendChild(toggle);
        panel.appendChild(handle);
        panel.appendChild(iframe);
        document.body.appendChild(panel);
        makeResizable(panel);
        setupCollapse(panel, toggle);
        applyCollapsedState(panel, getStoredCollapsed());
    }

    function removeStreamPanel() {
        const panel = document.getElementById(PANEL_ID);
        if (panel) panel.remove();
        document.documentElement.style.removeProperty('--dgg-kick-panel-width');
    }

    function init() {
        if (getEmbedFromPath()) {
            showStreamPanel();
        } else {
            removeStreamPanel();
        }
    }

    init();

    window.addEventListener('hashchange', function () {
        if (getEmbedFromPath()) {
            showStreamPanel();
        } else {
            removeStreamPanel();
        }
    });
})();