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.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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();
        }
    });
})();