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