Greasy Fork is available in English.
TriX Executor's ChatBox (for territorial.io)!
Від
// ==UserScript==
// @name TrixBox
// @namespace http://tampermonkey.net/
// @version 0.5.5
// @description TriX Executor's ChatBox (for territorial.io)!
// @author Painsel
// @match https://territorial.io/*
// @match https://fxclient.github.io/FXclient/*
// @grant GM_xmlhttpRequest
// @grant GM_info
// ==/UserScript==
(function() {
'use strict';
const CHATTABLE_HEADER_HEIGHT = 45; // pixels
// --- Theme Colors (for UI elements outside the iframe) ---
const theme = {
icon: '#2f3136',
modalBg: '#2f3136',
text: '#dcddde',
buttonPrimary: '#5865f2',
buttonSecondary: '#4f545c'
};
const SCRIPT_UPDATE_URL = 'https://update.greasyfork.org/scripts/555536/TrixBox.meta.js';
const SCRIPT_INSTALL_URL = 'https://update.greasyfork.org/scripts/555536/TrixBox.user.js';
// --- 1. UPDATE CHECKING LOGIC ---
const showUpdateModal = () => {
const modalStyle = `
.trixbox-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100000; display: flex; align-items: center; justify-content: center; }
.trixbox-modal-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.trixbox-modal-content h2 { margin-top: 0; }
.trixbox-modal-content p { margin: 15px 0; }
.trixbox-modal-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 0 10px; font-size: 14px; }
.trixbox-modal-btn.primary { background: ${theme.buttonPrimary}; }
.trixbox-modal-btn.secondary { background: ${theme.buttonSecondary}; }
`;
const styleSheet = document.createElement("style");
styleSheet.innerText = modalStyle;
document.head.appendChild(styleSheet);
const modalOverlay = document.createElement('div');
modalOverlay.className = 'trixbox-modal-overlay';
modalOverlay.innerHTML = `
<div class="trixbox-modal-content">
<h2>OUTDATED VERSION</h2>
<p>You are using an Outdated version of TrixBox.<br>Please update for the best experience!</p>
<button id="trixbox-update-btn" class="trixbox-modal-btn primary">Update Now!</button>
<button id="trixbox-later-btn" class="trixbox-modal-btn secondary">Remind Me Later!</button>
</div>
`;
document.body.appendChild(modalOverlay);
document.getElementById('trixbox-update-btn').onclick = () => { window.location.href = SCRIPT_INSTALL_URL; };
document.getElementById('trixbox-later-btn').onclick = () => { modalOverlay.remove(); };
};
const checkForUpdates = () => {
try {
const localVersion = GM_info.script.version;
const lastSeenVersion = localStorage.getItem('trixbox-last-version');
// Show changelog if version has changed
if (lastSeenVersion !== localVersion) {
localStorage.setItem('trixbox-last-version', localVersion);
setTimeout(showChangelog, 500);
}
GM_xmlhttpRequest({
method: 'GET',
url: SCRIPT_UPDATE_URL,
onload: function(response) {
if (response.status !== 200) return;
const remoteVersionMatch = response.responseText.match(/@version\s+([0-9.]+)/);
if (remoteVersionMatch && remoteVersionMatch[1]) {
if (remoteVersionMatch[1] > localVersion) {
showUpdateModal();
}
}
}
});
} catch (e) { console.error('TrixBox: Update check failed.', e); }
};
const showChangelog = () => {
const modalStyle = `
.trixbox-changelog-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100002; display: flex; align-items: center; justify-content: center; }
.trixbox-changelog-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 400px; max-height: 70vh; overflow-y: auto; }
.trixbox-changelog-content h2 { margin-top: 0; text-align: center; color: ${theme.buttonPrimary}; }
.trixbox-changelog-section { margin: 15px 0; }
.trixbox-changelog-version { font-weight: bold; color: ${theme.buttonPrimary}; margin-bottom: 8px; }
.trixbox-changelog-list { list-style: none; padding-left: 0; margin: 5px 0; }
.trixbox-changelog-list li { margin: 5px 0; padding-left: 20px; position: relative; }
.trixbox-changelog-list li:before { content: "✓"; position: absolute; left: 0; color: ${theme.buttonPrimary}; }
.trixbox-changelog-btn { width: 100%; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin-top: 15px; font-size: 14px; background: ${theme.buttonPrimary}; }
.trixbox-changelog-btn:hover { opacity: 0.9; }
`;
const styleSheet = document.createElement("style");
styleSheet.innerText = modalStyle;
document.head.appendChild(styleSheet);
const overlay = document.createElement('div');
overlay.className = 'trixbox-changelog-overlay';
overlay.innerHTML = `
<div class="trixbox-changelog-content">
<h2>What's New in v0.5.4</h2>
<div class="trixbox-changelog-section">
<div class="trixbox-changelog-version">v0.5.4 - Major Update</div>
<ul class="trixbox-changelog-list">
<li>Dual chat system - Choose between TrixBox Main and New TrixBox</li>
<li>Custom username settings with persistent storage</li>
<li>Profile picture upload (visible to all users)</li>
<li>Coin flip command (!coinflip)</li>
<li>Tendo theme for retro aesthetic</li>
<li>Fully draggable chat windows</li>
<li>Auto-update checking system</li>
</ul>
</div>
<button class="trixbox-changelog-btn">Got it!</button>
</div>
`;
document.body.appendChild(overlay);
overlay.querySelector('button').addEventListener('click', () => {
overlay.remove();
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.remove();
}
});
};
// --- 2. CREATE THE TOGGLE ICON ---
const toggleIcon = document.createElement('div');
toggleIcon.id = 'trixbox-toggle-icon';
toggleIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="${theme.text}" width="28px" height="28px"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
Object.assign(toggleIcon.style, {
backgroundColor: theme.icon,
position: 'fixed', bottom: '20px', right: '20px', width: '50px', height: '50px',
borderRadius: '50%', display: 'flex', alignItems: 'center',
justifyContent: 'center', cursor: 'pointer', zIndex: '99998',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)', transition: 'transform 0.2s ease-in-out'
});
toggleIcon.onmouseover = () => { toggleIcon.style.transform = 'scale(1.1)'; };
toggleIcon.onmouseout = () => { toggleIcon.style.transform = 'scale(1.0)'; };
document.body.appendChild(toggleIcon);
// --- 3. CREATE THE CHATBOX ---
const chatContainer = document.createElement('div');
chatContainer.id = 'trixbox-container';
Object.assign(chatContainer.style, {
position: 'fixed', bottom: '15px', right: '15px', width: '350px', height: '500px',
zIndex: '99999', display: 'none', flexDirection: 'column',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)', borderRadius: '8px', overflow: 'hidden'
});
const dragHeader = document.createElement('div');
dragHeader.id = 'trixbox-header';
Object.assign(dragHeader.style, {
height: '30px', backgroundColor: 'rgb(210, 210, 255)', cursor: 'move',
userSelect: 'none', display: 'flex', alignItems: 'center',
justifyContent: 'space-between', padding: '0 5px 0 15px',
position: 'relative', zIndex: '1'
});
const headerTitle = document.createElement('span');
headerTitle.textContent = 'TrixBox Chat';
Object.assign(headerTitle.style, {
color: 'white', fontWeight: 'bold', fontSize: '14px',
textShadow: '1px 1px 0 rgb(160, 160, 230), 2px 2px 0 rgba(0, 0, 0, 0.15)'
});
dragHeader.appendChild(headerTitle);
const headerButtons = document.createElement('div');
headerButtons.style.display = 'flex';
headerButtons.style.alignItems = 'center';
const settingsButton = document.createElement('button');
settingsButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(80, 80, 120)" width="18px" height="18px"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.69-1.62-0.92L14.4,2.23C14.38,2,14.17,1.84,13.92,1.84H9.92 c-0.25,0-0.47,0.16-0.54,0.39L9.04,4.95C8.45,5.18,7.92,5.49,7.42,5.87L5.03,4.91c-0.22-0.08-0.47,0-0.59,0.22L2.52,8.45 c-0.11,0.2-0.06,0.47,0.12,0.61l2.03,1.58C4.59,11.36,4.56,11.68,4.56,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.69,1.62,0.92l0.34,2.72 c0.07,0.23,0.29,0.39,0.54,0.39h3.99c0.25,0,0.47-0.16,0.54-0.39l0.34-2.72c0.59-0.23,1.12-0.54,1.62-0.92l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.11-0.2,0.06-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>`;
Object.assign(settingsButton.style, {
background: 'none', border: 'none', cursor: 'pointer',
padding: '0 8px', lineHeight: '1', display: 'flex', alignItems: 'center'
});
headerButtons.appendChild(settingsButton);
const closeButton = document.createElement('button');
closeButton.innerHTML = '×';
Object.assign(closeButton.style, {
background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
fontSize: '24px', lineHeight: '1', cursor: 'pointer', padding: '0 8px'
});
headerButtons.appendChild(closeButton);
dragHeader.appendChild(headerButtons);
chatContainer.appendChild(dragHeader);
const iframeWrapper = document.createElement('div');
Object.assign(iframeWrapper.style, {
flexGrow: '1', overflow: 'hidden', position: 'relative'
});
chatContainer.appendChild(iframeWrapper);
const chatLibraryScript = document.createElement('script');
chatLibraryScript.src = 'https://iframe.chat/scripts/main.min.js';
document.head.appendChild(chatLibraryScript);
const chatIframe = document.createElement('iframe');
chatIframe.src = 'https://iframe.chat/embed?chat=15234533';
chatIframe.id = 'chattable';
Object.assign(chatIframe.style, {
border: 'none', backgroundColor: 'transparent', position: 'absolute',
height: `calc(100% + ${CHATTABLE_HEADER_HEIGHT}px)`, width: '100%',
top: `-${CHATTABLE_HEADER_HEIGHT}px`, left: '0'
});
iframeWrapper.appendChild(chatIframe);
document.body.appendChild(chatContainer);
// --- 4. ADD FUNCTIONALITY ---
toggleIcon.addEventListener('click', (e) => {
e.stopPropagation();
showChatSelectionModal();
});
const showChatSelectionModal = () => {
const modalStyle = `
.trixbox-selection-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
.trixbox-selection-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
.trixbox-selection-content h2 { margin-top: 0; margin-bottom: 20px; }
.trixbox-selection-btn-group { display: flex; gap: 15px; }
.trixbox-selection-btn { flex: 1; color: white; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: bold; }
.trixbox-selection-btn.main { background: ${theme.buttonPrimary}; }
.trixbox-selection-btn.new { background: #7289da; }
.trixbox-selection-btn:hover { opacity: 0.9; }
`;
const styleSheet = document.createElement("style");
styleSheet.innerText = modalStyle;
document.head.appendChild(styleSheet);
const overlay = document.createElement('div');
overlay.className = 'trixbox-selection-overlay';
overlay.innerHTML = `
<div class="trixbox-selection-content">
<h2>Select Chat</h2>
<div class="trixbox-selection-btn-group">
<button id="trixbox-main-btn" class="trixbox-selection-btn main">TrixBox Main</button>
<button id="trixbox-new-btn" class="trixbox-selection-btn new">New TrixBox</button>
</div>
</div>
`;
document.body.appendChild(overlay);
document.getElementById('trixbox-main-btn').addEventListener('click', () => {
overlay.remove();
chatContainer.style.display = 'flex';
toggleIcon.style.display = 'none';
});
document.getElementById('trixbox-new-btn').addEventListener('click', () => {
overlay.remove();
showNewTrixBox();
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.remove();
}
});
};
const showNewTrixBox = () => {
const newChatContainer = document.createElement('div');
newChatContainer.id = 'trixbox-new-container';
Object.assign(newChatContainer.style, {
position: 'fixed', bottom: '15px', right: '15px', width: '350px', height: '500px',
zIndex: '99999', display: 'flex', flexDirection: 'column',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)', borderRadius: '8px', overflow: 'hidden'
});
const newDragHeader = document.createElement('div');
Object.assign(newDragHeader.style, {
height: '30px', backgroundColor: 'rgb(210, 210, 255)', cursor: 'move',
userSelect: 'none', display: 'flex', alignItems: 'center',
justifyContent: 'space-between', padding: '0 5px 0 15px',
position: 'relative', zIndex: '1'
});
const newHeaderTitle = document.createElement('span');
newHeaderTitle.textContent = 'New TrixBox';
Object.assign(newHeaderTitle.style, {
color: 'white', fontWeight: 'bold', fontSize: '14px',
textShadow: '1px 1px 0 rgb(160, 160, 230), 2px 2px 0 rgba(0, 0, 0, 0.15)'
});
newDragHeader.appendChild(newHeaderTitle);
const newCloseButton = document.createElement('button');
newCloseButton.innerHTML = '×';
Object.assign(newCloseButton.style, {
background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
fontSize: '24px', lineHeight: '1', cursor: 'pointer', padding: '0 8px'
});
newCloseButton.addEventListener('click', () => {
newChatContainer.remove();
toggleIcon.style.display = 'flex';
});
newDragHeader.appendChild(newCloseButton);
newChatContainer.appendChild(newDragHeader);
const newIframeWrapper = document.createElement('div');
Object.assign(newIframeWrapper.style, {
flexGrow: '1', overflow: 'hidden', position: 'relative'
});
const newIframe = document.createElement('iframe');
newIframe.src = 'https://www5.cbox.ws/box/?boxid=959661&boxtag=LgpZi2';
newIframe.width = '100%';
newIframe.height = '100%';
newIframe.allowTransparency = 'yes';
newIframe.allow = 'autoplay';
newIframe.frameBorder = '0';
newIframe.marginHeight = '0';
newIframe.marginWidth = '0';
newIframe.scrolling = 'auto';
Object.assign(newIframe.style, {
border: 'none', position: 'absolute', top: '0', left: '0'
});
newIframeWrapper.appendChild(newIframe);
newChatContainer.appendChild(newIframeWrapper);
// Hide Cbox text in iframe footer
try {
const hideStyle = document.createElement('style');
hideStyle.textContent = `
iframe#trixbox-new-container-iframe ~ * a[href*="cbox.ws"],
div[align="center"] a[href*="cbox.ws"] { display: none !important; }
`;
document.head.appendChild(hideStyle);
} catch (e) { }
document.body.appendChild(newChatContainer);
// Drag functionality for new chat
let isDragging = false, offsetX, offsetY;
newDragHeader.addEventListener('mousedown', (e) => {
if (e.target !== newDragHeader && e.target !== newHeaderTitle) return;
isDragging = true;
offsetX = e.clientX - newChatContainer.getBoundingClientRect().left;
offsetY = e.clientY - newChatContainer.getBoundingClientRect().top;
newIframe.style.pointerEvents = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
newChatContainer.style.left = `${e.clientX - offsetX}px`;
newChatContainer.style.top = `${e.clientY - offsetY}px`;
newChatContainer.style.bottom = 'auto';
newChatContainer.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
newIframe.style.pointerEvents = 'auto';
});
};
closeButton.addEventListener('click', (e) => {
e.stopPropagation();
chatContainer.style.display = 'none';
toggleIcon.style.display = 'flex';
});
settingsButton.addEventListener('click', (e) => {
e.stopPropagation();
showSettingsModal();
});
const showSettingsModal = () => {
const modalStyle = `
.trixbox-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
.trixbox-settings-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: left; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 350px; max-height: 80vh; overflow-y: auto; }
.trixbox-settings-content h2 { margin-top: 0; text-align: center; }
.trixbox-settings-group { margin: 20px 0; }
.trixbox-settings-group label { display: block; margin-bottom: 8px; font-weight: bold; }
.trixbox-settings-group input[type="text"], .trixbox-settings-group input[type="file"] { width: 100%; padding: 8px; background: #36393f; color: ${theme.text}; border: 1px solid #4f545c; border-radius: 4px; box-sizing: border-box; }
.trixbox-settings-group input[type="text"]::placeholder { color: #99aab5; }
.trixbox-profile-preview { width: 60px; height: 60px; border-radius: 50%; background: #4f545c; margin: 10px auto; overflow: hidden; display: flex; align-items: center; justify-content: center; }
.trixbox-profile-preview img { width: 100%; height: 100%; object-fit: cover; }
.trixbox-settings-btn-group { display: flex; gap: 10px; margin-top: 20px; }
.trixbox-settings-btn { flex: 1; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-size: 14px; }
.trixbox-settings-btn.save { background: ${theme.buttonPrimary}; }
.trixbox-settings-btn.cancel { background: ${theme.buttonSecondary}; }
`;
const styleSheet = document.createElement("style");
styleSheet.innerText = modalStyle;
document.head.appendChild(styleSheet);
const overlay = document.createElement('div');
overlay.className = 'trixbox-settings-overlay';
overlay.innerHTML = `
<div class="trixbox-settings-content">
<h2>Chat Settings</h2>
<div class="trixbox-settings-group">
<label>Username</label>
<input type="text" id="trixbox-username-input" placeholder="Enter your username" maxlength="32">
</div>
<div class="trixbox-settings-group">
<label>Profile Picture</label>
<div class="trixbox-profile-preview" id="trixbox-profile-preview"></div>
<input type="file" id="trixbox-profile-upload" accept="image/*">
</div>
<div class="trixbox-settings-btn-group">
<button id="trixbox-settings-save" class="trixbox-settings-btn save">Save</button>
<button id="trixbox-settings-cancel" class="trixbox-settings-btn cancel">Cancel</button>
</div>
</div>
`;
document.body.appendChild(overlay);
const usernameInput = document.getElementById('trixbox-username-input');
const profileUpload = document.getElementById('trixbox-profile-upload');
const profilePreview = document.getElementById('trixbox-profile-preview');
const saveBtn = document.getElementById('trixbox-settings-save');
const cancelBtn = document.getElementById('trixbox-settings-cancel');
let selectedProfileImage = localStorage.getItem('trixbox-profile-image') || null;
usernameInput.value = localStorage.getItem('trixbox-username') || '';
if (selectedProfileImage) {
profilePreview.innerHTML = `<img src="${selectedProfileImage}" alt="Profile">`;
}
profileUpload.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
selectedProfileImage = event.target.result;
profilePreview.innerHTML = `<img src="${selectedProfileImage}" alt="Profile">`;
};
reader.readAsDataURL(file);
}
});
saveBtn.addEventListener('click', () => {
const username = usernameInput.value.trim();
if (username) {
localStorage.setItem('trixbox-username', username);
if (selectedProfileImage) {
localStorage.setItem('trixbox-profile-image', selectedProfileImage);
}
if (typeof chattable !== 'undefined') {
if (typeof chattable.setName === 'function') {
chattable.setName(username);
}
// Send profile picture to other users via payload
if (selectedProfileImage && typeof chattable.sendPayload === 'function') {
chattable.sendPayload({
type: 'profile-picture',
username: username,
image: selectedProfileImage
});
}
}
overlay.remove();
} else {
alert('Please enter a username');
}
});
cancelBtn.addEventListener('click', () => {
overlay.remove();
});
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.remove();
}
});
};
let isDragging = false, offsetX, offsetY;
dragHeader.addEventListener('mousedown', (e) => {
if (e.target !== dragHeader && e.target !== headerTitle) return;
isDragging = true;
offsetX = e.clientX - chatContainer.getBoundingClientRect().left;
offsetY = e.clientY - chatContainer.getBoundingClientRect().top;
chatIframe.style.pointerEvents = 'none';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
chatContainer.style.left = `${e.clientX - offsetX}px`;
chatContainer.style.top = `${e.clientY - offsetY}px`;
chatContainer.style.bottom = 'auto';
chatContainer.style.right = 'auto';
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
chatIframe.style.pointerEvents = 'auto';
});
// --- 5. DEFINE CUSTOM COMMANDS ---
chattable.commands = {
"coinflip": function(fullCommand) {
const result = Math.random() < 0.5 ? "Heads" : "Tails";
chattable.sendMessage(`🪙 Coin flip result: **${result}**`, "TrixBox", "", false);
}
};
// --- 6. INITIALIZE THE CHAT ---
chatLibraryScript.onload = function() {
if (typeof chattable !== 'undefined') {
chattable.initialize({
theme: "tendo"
});
// Handle profile picture payloads after initialization
chattable.on('payload', function(data) {
if (data.type === 'profile-picture') {
localStorage.setItem(`trixbox-profile-${data.username}`, data.image);
}
});
}
};
// --- 7. RUN THE UPDATE CHECKER ---
checkForUpdates();
})();