// ==UserScript==
// @name Doobie's Torn City Chat Popout
// @namespace http://tampermonkey.net/
// @version 1.3
// @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 - Updated for Chat 3.0
// @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() {
// Don't create if button already exists
if (document.querySelector('#chat-popout-btn')) {
return;
}
const button = document.createElement('button');
button.id = 'chat-popout-btn';
button.type = 'button';
// Try to match the styling of existing chat buttons
const existingButton = document.querySelector('button[class*="root__"]');
if (existingButton) {
button.className = existingButton.className;
} else {
// Minimal fallback styling
button.style.cssText = `
background: transparent;
border: none;
cursor: pointer;
padding: 0;
margin: 0;
display: inline-flex;
align-items: center;
justify-content: center;
`;
}
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 24" style="width: 20px; height: 20px;">
<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);
button.title = 'Pop out chat';
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)'));
// Try multiple strategies to find where to place the button
const buttonContainer = findButtonContainer();
if (buttonContainer) {
buttonContainer.appendChild(button);
console.log('[Chat Popout] Button created successfully');
} else {
console.warn('[Chat Popout] Could not find suitable location for button');
}
}
function findButtonContainer() {
// Strategy 1: Look for existing chat buttons and place near them
const chatButtons = document.querySelectorAll('button[class*="root__"]');
if (chatButtons.length > 0) {
const lastButton = chatButtons[chatButtons.length - 1];
return lastButton.parentNode;
}
// Strategy 2: Look for button containers
const buttonContainers = [
'div[class*="button"]',
'div[class*="control"]',
'div[class*="header"]',
'div[class*="toolbar"]'
];
for (const selector of buttonContainers) {
const container = document.querySelector(selector);
if (container) {
return container;
}
}
// Strategy 3: Use the chat root as fallback
const chatRoot = document.querySelector('#chatRoot');
if (chatRoot) {
// Create a simple container for our button
let buttonDiv = chatRoot.querySelector('#popout-button-container');
if (!buttonDiv) {
buttonDiv = document.createElement('div');
buttonDiv.id = 'popout-button-container';
buttonDiv.style.cssText = `
position: absolute;
top: 10px;
right: 10px;
z-index: 1000;
background: rgba(0,0,0,0.5);
border-radius: 4px;
padding: 4px;
`;
chatRoot.appendChild(buttonDiv);
}
return buttonDiv;
}
return null;
}
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 waitForChat() {
return new Promise((resolve) => {
const maxAttempts = 100; // Increased attempts
let attempts = 0;
const interval = setInterval(() => {
const chatRoot = document.querySelector('#chatRoot');
if (chatRoot && chatRoot.children.length > 0) {
clearInterval(interval);
resolve();
} else if (++attempts >= maxAttempts) {
clearInterval(interval);
console.warn('[Chat Popout] Chat did not load within expected time');
resolve(); // Resolve anyway to prevent hanging
}
}, 100);
});
}
async function init() {
// Wait for chat to be available
await waitForChat();
const chatRoot = document.querySelector('#chatRoot');
if (chatRoot && !document.querySelector('#chat-popout-btn')) {
console.log('[Chat Popout] Initializing...');
createPopoutButton();
if (localStorage.getItem(POPOUT_KEY) === 'true') {
toggleMainChat(false);
}
}
}
// Enhanced initialization
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.key === 'r') resetState();
});
// Multiple initialization strategies
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(init, 1000));
} else {
setTimeout(init, 1000);
}
// Watch for chat loading with more aggressive observation
const observer = new MutationObserver((mutations) => {
let shouldInit = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1 && (node.id === 'chatRoot' || node.querySelector && node.querySelector('#chatRoot'))) {
shouldInit = true;
}
});
}
});
if (shouldInit) {
setTimeout(init, 500);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['id', 'class']
});
window.addEventListener('storage', e => {
if (e.key === POPOUT_KEY) toggleMainChat(e.newValue === 'false');
});
// Additional failsafe - try to initialize every few seconds if chat exists but button doesn't
setInterval(() => {
const chatRoot = document.querySelector('#chatRoot');
const button = document.querySelector('#chat-popout-btn');
if (chatRoot && !button) {
console.log('[Chat Popout] Failsafe initialization...');
init();
}
}, 5000);
console.log('[Chat Popout] Script loaded for Chat 3.0');
})();
//Made with Love by DoobieSuckin [3255641] Built on the base by Weav3r [1853324] - Updated for Chat 3.0