Doobie's Torn City Chat Popout

Adds a popout button for Torn chat. After clicking Boots up Window you can drag around and use on other sites/windows. To get your chat back in Torn, Close the chat!

// ==UserScript==
// @name         Doobie's Torn City Chat Popout
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Adds a popout button for Torn chat. After clicking Boots up Window you can drag around and use on other sites/windows. To get your chat back in Torn, Close the chat!
// @author       Doobiesuckin with Base from Weav3r
// @match        https://www.torn.com/*
// ==/UserScript==
(function() {
    'use strict';

    let popoutWindow = null;
    const POPOUT_KEY = 'torn_chat_popout_active';
    const POPOUT_CSS = `
        body { margin: 0; padding: 0; background: #1c1c1c; height: 100vh; overflow: hidden; }
        body > *:not(#chatRoot):not(script):not(link):not(style) { display: none !important; }
        #chatRoot { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; display: block !important; }
        #chat-popout-btn { display: none !important; }
    `;
    const DEFAULT_WIDTH = 400, DEFAULT_HEIGHT = 600;

    function saveWindowSize(width, height) {
        try { localStorage.setItem('popoutWindowSize', JSON.stringify({ width, height })); }
        catch (error) { console.error('Failed to save window size to localStorage', error); }
    }

    function getSavedWindowSize() {
        try { return JSON.parse(localStorage.getItem('popoutWindowSize')) || { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }; }
        catch (error) { console.error('Failed to retrieve window size', error); return { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT }; }
    }

    function toggleMainChat(show) {
        const chatRoot = document.querySelector('#chatRoot');
        if (chatRoot) chatRoot.style.display = show ? '' : 'none';
        localStorage.setItem(POPOUT_KEY, !show);
    }

    function createPopoutButton() {
        const button = document.createElement('button');
        button.id = 'chat-popout-btn';
        button.type = 'button';
        button.className = 'chat-list-button___d1Olw';
        button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 24" class="chat-app__footer-chat-list-icon___o_mD2">
            <defs>
                <linearGradient id="popout-default-blue" x1="0.5" x2="0.5" y2="1"><stop offset="0" stop-color="#8faeb4"/><stop offset="1" stop-color="#638c94"/></linearGradient>
                <linearGradient id="popout-hover-blue" x1="0.5" x2="0.5" y2="1"><stop offset="0" stop-color="#eaf0f1"/><stop offset="1" stop-color="#7b9fa6"/></linearGradient>
            </defs>
            <path d="M19 3h-7v2h4.6l-8.3 8.3 1.4 1.4L18 6.4V11h2V3z M5 5h7V3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7H5V5z" fill="url(#popout-default-blue)"/>
        </svg>`;
        button.addEventListener('click', createPopout);
        const path = button.querySelector('path');
        button.addEventListener('mouseenter', () => path.setAttribute('fill', 'url(#popout-hover-blue)'));
        button.addEventListener('mouseleave', () => path.setAttribute('fill', 'url(#popout-default-blue)'));
        const chatListButton = document.querySelector('.chat-list-button___d1Olw');
        chatListButton?.parentNode?.insertBefore(button, chatListButton.nextSibling);
    }

    function createPopout() {
        if (popoutWindow && !popoutWindow.closed) {
            popoutWindow.focus();
            return;
        }
        const { width, height } = getSavedWindowSize();
        popoutWindow = window.open('https://www.torn.com', 'TornChat', `width=${width},height=${height},resizable=yes`);
        waitForChatToLoad(popoutWindow).then(() => {
            toggleMainChat(false);
            const style = document.createElement('style');
            style.textContent = POPOUT_CSS;
            popoutWindow.document.head.appendChild(style);
            addResizeListener();
        }).catch(error => console.error('Error during chat popout initialization', error));
        const checkClosure = setInterval(() => {
            if (popoutWindow.closed) {
                toggleMainChat(true);
                clearInterval(checkClosure);
                popoutWindow = null;
            }
        }, 1000);
    }

    function waitForChatToLoad(window) {
        return new Promise((resolve, reject) => {
            const maxAttempts = 50;
            let attempts = 0;
            const interval = setInterval(() => {
                try {
                    if (window.document.querySelector('#chatRoot')) {
                        clearInterval(interval);
                        resolve();
                    }
                } catch {
                    if (++attempts >= maxAttempts) {
                        clearInterval(interval);
                        reject(new Error('Chat failed to load in the popout window'));
                    }
                }
            }, 200);
        });
    }

    function addResizeListener() {
        let resizeTimeout;
        popoutWindow.addEventListener('resize', () => {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(() => saveWindowSize(popoutWindow.innerWidth, popoutWindow.innerHeight), 200);
        });
    }

    function resetState() {
        toggleMainChat(true);
        localStorage.removeItem(POPOUT_KEY);
    }

    function init() {
        if (document.querySelector('#chatRoot') && !document.querySelector('#chat-popout-btn')) {
            createPopoutButton();
            if (localStorage.getItem(POPOUT_KEY) === 'true') toggleMainChat(false);
        }
    }

    document.addEventListener('keydown', e => {
        if (e.ctrlKey && e.key === 'r') resetState();
    });

    document.readyState === 'loading'
        ? document.addEventListener('DOMContentLoaded', init)
        : init();
    new MutationObserver(init).observe(document.body, { childList: true, subtree: true });
    window.addEventListener('storage', e => {
        if (e.key === POPOUT_KEY) toggleMainChat(e.newValue === 'false');
    });
})();


//Made with Love by DoobieSuckin [3255641] Built on the base by Weav3r [1853324]