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.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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