您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Chat functionalities: Reply, Message and Gift buttons. BBCode buttons. Toggle menu. Emojis.
// ==UserScript== // @name UNIT3D chatbox - enhanced ULCX // @version 1.2 // @description Chat functionalities: Reply, Message and Gift buttons. BBCode buttons. Toggle menu. Emojis. // @match https://upload.cx/ // @grant none // @namespace https://greasyfork.org/users/1344404 // ==/UserScript== (function() { 'use strict'; //Please do not alter this, It is for contributors to the project only. const USER_COLORS = { 'ace': '#ef008c', 'TheoneandonlyPook': '#ef008c', 'Demonic': '#ffac6b', } // Function to extract the CSRF token function extractToken() { const tokenElement = document.querySelector('meta[name="csrf-token"]'); return tokenElement ? tokenElement.content : 'Token not found'; } // Function to extract the user from the URL function extractUser() { const userLink = document.querySelector('a.top-nav__username--highresolution'); if (userLink) { const url = new URL(userLink.href); return url.pathname.split('/').pop(); } return 'User not found'; } // Function to remove the dialog from the page function removeDialog() { const existingDialog = document.querySelector('dialog'); if (existingDialog) { existingDialog.remove(); } } const chatboxID = '#chatbox__messages-create'; const chatMessagesClassName = '.chatroom__messages'; const onlineUserListSelector = '.blocks__online .panel__body ul'; const additionalUserListSelector = '.chatroom-users__list'; const bbCodesPanelID = 'bbCodesPanel'; const settingsButtonID = 'settingsButton'; const settingsPanelID = 'settingsPanel'; const BBCODES_PANEL_HTML = ` <div id="${bbCodesPanelID}" style="position: relative; display: flex; flex-wrap: wrap; z-index: 9998;"> <span id="imgButton" style="cursor: pointer; margin-right: 4px;" data-bbcode="[img][/img]">IMG</span> <span id="urlButton" style="cursor: pointer; margin-right: 4px;" data-bbcode="[url][/url]">URL</span> <span id="colorButton" style="cursor: pointer; margin-right: 4px;">Color</span> <input type="color" id="colorPicker" style="cursor: pointer; margin-left: 10px; display: none;" title="Select Color"> <span style="cursor: pointer; margin: 4px;" id="emojiButton">😊</span> <div id="emojiMenu" style="display: none; position: absolute; left: 10px; top: 30px; background: #000000; border: 1px solid #ccc; z-index: 10000;"> <span class="emoji" data-emoji="😊">😊</span> <span class="emoji" data-emoji="😂">😂</span> <span class="emoji" data-emoji="😍">😍</span> <span class="emoji" data-emoji="😇">😇</span> <span class="emoji" data-emoji="🤨">🤨</span> <span class="emoji" data-emoji="🥳">🥳</span> <span class="emoji" data-emoji="😏">😏</span> <span class="emoji" data-emoji="😞">😞</span> <span class="emoji" data-emoji="😤">😤</span> <span class="emoji" data-emoji="🤬">🤬</span> <span class="emoji" data-emoji="🤯">🤯</span> <span class="emoji" data-emoji="🥵">🥵</span> <span class="emoji" data-emoji="🥶">🥶</span> <span class="emoji" data-emoji="🤫">🤫</span> <span class="emoji" data-emoji="🤥">🤥</span> <span class="emoji" data-emoji="😴">😴</span> <span class="emoji" data-emoji="🤮">🤮</span> <span class="emoji" data-emoji="🤡">🤡</span> <span class="emoji" data-emoji="💩">💩</span> <span class="emoji" data-emoji="👻">👻</span> <span class="emoji" data-emoji="💀">💀</span> <span class="emoji" data-emoji="👽">👽</span> <span class="emoji" data-emoji="🎃">🎃</span> <span class="emoji" data-emoji="🤝">🤝</span> <span class="emoji" data-emoji="👍">👍</span> <span class="emoji" data-emoji="👎">👎</span> <span class="emoji" data-emoji="✌️">✌️</span> <span class="emoji" data-emoji="🖕">🖕</span> <span class="emoji" data-emoji="👮">👮</span> <span class="emoji" data-emoji="🕸️">🕸️</span> <span class="emoji" data-emoji="🐢">🐢</span> <span class="emoji" data-emoji="🐋">🐋</span> <span class="emoji" data-emoji="🐐">🐐</span> <span class="emoji" data-emoji="🐦🔥">🐦🔥</span> <span class="emoji" data-emoji="🌵">🌵</span> <span class="emoji" data-emoji="🎄">🎄</span> <span class="emoji" data-emoji="🔥">🔥</span> <span class="emoji" data-emoji="🌪️">🌪️</span> <span class="emoji" data-emoji="🌈">🌈</span> <span class="emoji" data-emoji="☀️">☀️</span> <span class="emoji" data-emoji="🌧️">🌧️</span> <span class="emoji" data-emoji="❄️">❄️</span> <span class="emoji" data-emoji="🍺">🍺</span> <span class="emoji" data-emoji="🩷">🩷</span> <span class="emoji" data-emoji="💔">💔</span> <span class="emoji" data-emoji="🛑">🛑</span> <span class="emoji" data-emoji="🏴☠️">🏴☠️</span> <!-- Add more emojis as needed --> </div> <div style="position: relative;"> <span style="cursor: pointer;" id="bbCodeDropdown">➪</span> <div id="bbCodeDropdownMenu" style="display: none; position: absolute; left: 20px; top: -2px; flex-direction: row; z-index: 10000;"> <span style="cursor: pointer; margin: 2px;" data-bbcode="[b][/b]">[B]</span> <span style="cursor: pointer; margin: 2px;" data-bbcode="[i][/i]">[I]</span> <span style="cursor: pointer; margin: 2px;" data-bbcode="[u][/u]">[U]</span> </div> </div> </div>`; const SETTINGS_PANEL_HTML = `<div id="${settingsPanelID}" style="display: none; position: absolute; top: 20px; right: 0; background: rgba(0,0,0,0.9); border-radius: 8px; padding: 10px; color: white; z-index: 10001;"> <label><input type="checkbox" id="toggleBBCodes" checked> Show BB Codes</label><br> <label><input type="checkbox" id="toggleMessageGift" checked> Show Message/Gift Buttons</label><br> <label><input type="checkbox" id="toggleReply" checked> Show Reply Button</label> </div>`; const TOGGLE_BUTTON_HTML = `<div id="${settingsButtonID}" style="cursor: pointer; color: #fff; display: inline-block; margin-left: 4px; font-size: 16px;">⚙️</div>`; // Create and add the style block const style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = ` .emoji { cursor: pointer; } .reply-icon, .message-icon, .gift-icon { cursor: pointer; margin-left: 5px; } .mention-dropdown { position: absolute; background: #333; border: 1px solid #ccc; max-height: 150px; overflow-y: auto; display: none; z-index: 10000; color: #ccc; } .mention-dropdown span { display: block; padding: 5px; cursor: pointer; color: #ccc; } .mention-dropdown span:hover { background: #555; } .panel__actions { position: relative; } `; document.head.appendChild(style); function saveSetting(key, value) { localStorage.setItem(key, value); } function loadSetting(key) { return localStorage.getItem(key); } function applySettings() { const toggleBBCodes = loadSetting('toggleBBCodes') === 'true'; const toggleMessageGift = loadSetting('toggleMessageGift') === 'true'; const toggleReply = loadSetting('toggleReply') === 'true'; document.getElementById('toggleBBCodes').checked = toggleBBCodes; document.getElementById(bbCodesPanelID).style.display = toggleBBCodes ? 'flex' : 'none'; document.getElementById('toggleMessageGift').checked = toggleMessageGift; const iconsMessageGift = document.querySelectorAll('.message-icon, .gift-icon'); iconsMessageGift.forEach(icon => icon.style.display = toggleMessageGift ? 'inline' : 'none'); document.getElementById('toggleReply').checked = toggleReply; const iconsReply = document.querySelectorAll('.reply-icon'); iconsReply.forEach(icon => icon.style.display = toggleReply ? 'inline' : 'none'); } function setupSettingsListeners() { document.getElementById('toggleBBCodes').addEventListener('change', (event) => { saveSetting('toggleBBCodes', event.target.checked); document.getElementById(bbCodesPanelID).style.display = event.target.checked ? 'flex' : 'none'; }); document.getElementById('toggleMessageGift').addEventListener('change', (event) => { saveSetting('toggleMessageGift', event.target.checked); const icons = document.querySelectorAll('.message-icon, .gift-icon'); icons.forEach(icon => icon.style.display = event.target.checked ? 'inline' : 'none'); }); document.getElementById('toggleReply').addEventListener('change', (event) => { saveSetting('toggleReply', event.target.checked); const icons = document.querySelectorAll('.reply-icon'); icons.forEach(icon => icon.style.display = event.target.checked ? 'inline' : 'none'); }); } function getOnlineUsernames() { const userElements = document.querySelectorAll(`${onlineUserListSelector} .user-tag__link`); const additionalUserElements = document.querySelectorAll(`${additionalUserListSelector} .user-tag__link`); const userSet = new Set([...Array.from(userElements), ...Array.from(additionalUserElements)].map(el => el.textContent.trim())); return Array.from(userSet); } function setupMentionFeature(chatbox) { const mentionDropdown = document.createElement('div'); mentionDropdown.classList.add('mention-dropdown'); document.body.appendChild(mentionDropdown); chatbox.addEventListener('input', function(event) { const cursorPosition = chatbox.selectionStart; const text = chatbox.value.substring(0, cursorPosition); const mentionMatch = text.match(/@(\w*)$/); if (mentionMatch) { const usernamePrefix = mentionMatch[1].toLowerCase(); const users = getOnlineUsernames().filter(user => user.toLowerCase().startsWith(usernamePrefix)); mentionDropdown.innerHTML = ''; users.forEach(user => { const userElement = document.createElement('span'); userElement.textContent = user; userElement.addEventListener('click', () => { chatbox.value = chatbox.value.substring(0, cursorPosition - usernamePrefix.length - 1) + '@' + user + ' ' + chatbox.value.substring(cursorPosition); chatbox.focus(); mentionDropdown.style.display = 'none'; }); mentionDropdown.appendChild(userElement); }); const rect = chatbox.getBoundingClientRect(); mentionDropdown.style.left = `${rect.left}px`; mentionDropdown.style.top = `${rect.bottom}px`; mentionDropdown.style.display = 'block'; } else { mentionDropdown.style.display = 'none'; } }); chatbox.addEventListener('keydown', (e) => { if (mentionDropdown.style.display === 'block' && e.key === 'Tab') { e.preventDefault(); const firstUser = mentionDropdown.querySelector('span'); if (firstUser) firstUser.click(); } }); document.addEventListener('click', function(event) { if (!mentionDropdown.contains(event.target)) mentionDropdown.style.display = 'none'; }); } // Function to add the dialog element to the page function addDialog(token, user, username) { // Check if a dialog already exists and remove it removeDialog(); // Create dialog HTML const dialogHTML = ` <dialog class="dialog" x-bind="dialogElement"> <h3 class="dialog__heading">Gift BON to: ${username}</h3> <form class="dialog__form" method="POST" action="https://upload.cx/users/${user}/gifts" x-bind="dialogForm"> <input type="hidden" name="_token" value="${token}" autocomplete="off"> <input type="hidden" name="recipient_username" value="${username}"> <p class="form__group"> <input id="bon" class="form__text" name="bon" type="text" pattern="[0-9]*" inputmode="numeric" placeholder=" "> <label class="form__label form__label--floating" for="bon">Amount</label> </p> <p class="form__group"> <textarea id="message" class="form__textarea" name="message" placeholder=" "></textarea> <label class="form__label form__label--floating" for="message">Message</label> </p> <p class="form__group"> <button type="submit" class="form__button form__button--filled">Gift</button> <button type="button" class="form__button form__button--outlined">Cancel</button> </p> </form> </dialog>`; // Create a container <div> if one does not exist let container = document.querySelector('#dialog-container'); if (!container) { container = document.createElement('div'); container.id = 'dialog-container'; document.body.appendChild(container); } // Add dialog HTML to the container container.innerHTML = dialogHTML; // Add styling for the backdrop const style = document.createElement('style'); style.innerHTML = ` dialog::backdrop { background-color: rgba(0, 0, 0, 0.8); }`; document.head.appendChild(style); // Get the dialog element and show it const dialog = container.querySelector('dialog'); dialog.showModal(); // Add event listener to the "Cancel" button to remove the dialog const cancelButton = dialog.querySelector('button[type="button"]'); if (cancelButton) { cancelButton.addEventListener('click', removeDialog); } } function setupReplyFeatures(chatMessages) { //Please do not alter this, It is for contributors to the project only. const USER_COLORS = { 'ace': '#ef008c', 'TheoneandonlyPook': '#ef008c', 'Demonic': '#ffac6b' // Add more users and colors as needed }; const newMessageTextArea = document.querySelector(chatboxID); function rgbToHex(rgb) { // Convert RGB to HEX format const rgbArray = rgb.match(/\d+/g).map(Number); if (rgbArray.length === 3) { return `#${rgbArray.map(value => { const hex = value.toString(16).padStart(2, '0'); return hex; }).join('')}`; } return rgb; // Return original if not in expected format } function quoteMessage(username, message) { const chatbox = document.querySelector(chatboxID); if (!chatbox) { console.error(`Chatbox not found: ${chatboxID}`); return; } // Default color if user is not found in USER_COLORS let userColor = '#ecc846'; // Default color // Check USER_COLORS first if (USER_COLORS.hasOwnProperty(username)) { userColor = USER_COLORS[username]; } else { // If not in USER_COLORS, extract the color from the chat messages const messages = document.querySelectorAll('.chatbox-message, .message'); for (const msg of messages) { const msgUsername = msg.querySelector(".chatbox-message__address.user-tag span, .message-username span"); if (msgUsername && msgUsername.textContent.trim() === username) { const style = window.getComputedStyle(msgUsername); userColor = rgbToHex(style.color); break; // Stop searching once the user is found } } } // Debugging console.log(`Username: ${username}, Color used: ${userColor}`); // Create the quoted message const quoteText = `[color=${userColor}][b]${username}[/b][/color]: [color=#ffff80][i]"${message}"[/i][/color]`; // Append the quoted message to the chat input chatbox.value += `${quoteText}\n\n`; chatbox.focus(); chatbox.setSelectionRange(chatbox.value.length, chatbox.value.length); } function addReplyIconToMessage(message) { const content = message.querySelector(".chatbox-message__content")?.innerText || message.querySelector(".message-content")?.innerText; const username = message.querySelector(".chatbox-message__address.user-tag span")?.innerText || message.querySelector(".message-username span")?.innerText; const header = message.querySelector(".chatbox-message__header") || message.querySelector(".message-header"); if (!content || !username || !header) return; const replyIcon = document.createElement("i"); replyIcon.classList.add("fa", "solid", "fa-reply", "reply-icon"); replyIcon.style.color = "#d82c20"; replyIcon.addEventListener("click", () => quoteMessage(username, content)); const messageIcon = document.createElement("i"); messageIcon.classList.add("fa", "solid", "fa-envelope", "message-icon"); messageIcon.style.color = "#118DFF"; const giftIcon = document.createElement("i"); giftIcon.classList.add("fa", "solid", "fa-gift", "gift-icon"); giftIcon.style.color = "#f3c911"; messageIcon.addEventListener("click", () => { newMessageTextArea.value += `/msg ${username} `; newMessageTextArea.focus(); }); giftIcon.addEventListener("click", () => { const token = extractToken(); const user = extractUser(); addDialog(token, user, username); }); header.appendChild(replyIcon); header.appendChild(messageIcon); header.appendChild(giftIcon); applySettings(); } document.querySelectorAll(".chatbox-message, .message").forEach(addReplyIconToMessage); const observer = new MutationObserver(function(mutationsList, observer) { document.querySelectorAll(".reply-icon, .message-icon, .gift-icon").forEach((icon) => icon.remove()); document.querySelectorAll(".chatbox-message, .message").forEach(addReplyIconToMessage); }); observer.observe(chatMessages, { childList: true }); } function setupChatFeatures(chatbox) { const container = document.createElement('div'); container.style.position = 'relative'; container.style.display = 'inline-flex'; chatbox.parentNode.insertBefore(container, chatbox.nextSibling); const bbCodesPanel = document.createElement('div'); bbCodesPanel.innerHTML = BBCODES_PANEL_HTML; container.appendChild(bbCodesPanel); bbCodesPanel.style.background = 'transparent'; bbCodesPanel.style.border = 'none'; // BBCode Dropdown const bbCodeDropdown = document.getElementById('bbCodeDropdown'); const bbCodeDropdownMenu = document.getElementById('bbCodeDropdownMenu'); bbCodeDropdown.addEventListener('click', function() { bbCodeDropdownMenu.style.display = bbCodeDropdownMenu.style.display === 'flex' ? 'none' : 'flex'; }); // Emoji Button const emojiButton = document.getElementById('emojiButton'); const emojiMenu = document.getElementById('emojiMenu'); emojiButton.addEventListener('click', function() { emojiMenu.style.display = emojiMenu.style.display === 'block' ? 'none' : 'block'; }); // Emoji Menu emojiMenu.addEventListener('click', function(event) { if (event.target.classList.contains('emoji')) { const emoji = event.target.getAttribute('data-emoji'); insertEmoji(emoji, chatbox); emojiMenu.style.display = 'none'; // Hide menu after selection } }); // BBCode Buttons bbCodesPanel.querySelectorAll('span').forEach(function(span) { span.addEventListener('click', function() { const bbCode = span.getAttribute('data-bbcode'); if (bbCode === "[img][/img]") { insertImgBBCodeWithClipboard(bbCode, chatbox); } else if (bbCode === "[url][/url]") { insertBBCodeWithClipboard(bbCode, chatbox); } else if (span.id === 'colorButton') { document.getElementById('colorPicker').style.display = 'block'; // Show color picker } else { insertBBCode(chatbox, bbCode); } }); }); // Color Picker document.getElementById('colorPicker').addEventListener('input', function(event) { const color = event.target.value; const colorBBCode = `[color=${color}][/color]`; chatbox.value += colorBBCode + " "; const pos = chatbox.value.length; chatbox.setSelectionRange(pos, pos); chatbox.focus(); document.getElementById('colorPicker').style.display = 'none'; // Hide color picker after selection }); setupMentionFeature(chatbox); } function insertEmoji(emoji, chatbox) { const pos = chatbox.selectionStart; chatbox.value = chatbox.value.substring(0, pos) + emoji + chatbox.value.substring(pos); chatbox.setSelectionRange(pos + emoji.length, pos + emoji.length); chatbox.focus(); } function insertBBCodeWithClipboard(tag, chatbox) { navigator.clipboard.readText().then(clipText => { const newContent = clipText.trim().length > 0 ? tag.replace(/(\[.*?\])(.*?)(\[\/.*?\])/, `$1${clipText}$3`) : tag.replace(/(\[.*?\])(.*?)(\[\/.*?\])/, `$1$2$3`); chatbox.value += newContent + " "; const pos = chatbox.value.length; chatbox.setSelectionRange(pos, pos); chatbox.focus(); }).catch((err) => { console.error('Failed to read clipboard contents:', err); chatbox.value += tag.replace(/(\[.*?\])(.*?)(\[\/.*?\])/, `$1$2$3`) + " "; const pos = chatbox.value.length; chatbox.setSelectionRange(pos, pos); chatbox.focus(); }); } function insertImgBBCodeWithClipboard(tag, chatbox) { navigator.clipboard.readText().then(clipText => { const newContent = clipText.trim().length > 0 ? tag.replace(/(\[.*?\])(.*?)(\[\/.*?\])/, `$1${clipText}$3`) : tag.replace(/(\[.*?\])(.*?)(\[\/.*?\])/, `$1$2$3`); chatbox.value += newContent + "\n"; const pos = chatbox.value.length; chatbox.setSelectionRange(pos, pos); chatbox.focus(); }).catch((err) => { console.error('Failed to read clipboard contents:', err); chatbox.value += tag.replace(/(\[.*?\])(.*?)(\[\/.*?\])/, `$1$2$3`) + "\n"; const pos = chatbox.value.length; chatbox.setSelectionRange(pos, pos); chatbox.focus(); }); } function insertBBCode(chatbox, bbCode) { const textSelected = chatbox.value.substring(chatbox.selectionStart, chatbox.selectionEnd); const startTag = bbCode.substring(0, bbCode.indexOf(']') + 1); const endTag = bbCode.substring(bbCode.lastIndexOf('[')); if (textSelected.length > 0) { const newText = startTag + textSelected + endTag; chatbox.value = chatbox.value.substring(0, chatbox.selectionStart) + newText + " " + chatbox.value.substring(chatbox.selectionEnd); const newPos = chatbox.value.lastIndexOf(' ') + 1; chatbox.setSelectionRange(newPos, newPos); } else { const pos = chatbox.selectionStart + startTag.length; chatbox.value += startTag + endTag + " "; chatbox.setSelectionRange(pos, pos); } chatbox.focus(); } function setupSettingsPanel() { const chatboxHeaderActions = document.querySelector('#chatbox_header .panel__actions'); if (chatboxHeaderActions) { chatboxHeaderActions.insertAdjacentHTML('beforeend', TOGGLE_BUTTON_HTML); const settingsPanel = document.createElement('div'); settingsPanel.innerHTML = ` <div id="${settingsPanelID}" style="display: none; position: absolute; top: 30px; right: 0; background: rgba(0,0,0,0.9); border-radius: 8px; padding: 10px; color: white; z-index: 10001;"> <label><input type="checkbox" id="toggleBBCodes" checked> Show BB Codes</label><br> <label><input type="checkbox" id="toggleMessageGift" checked> Show Message/Gift Buttons</label><br> <label><input type="checkbox" id="toggleReply" checked> Show Reply Button</label> </div>`; chatboxHeaderActions.appendChild(settingsPanel); const toggleSettingsButton = document.getElementById(settingsButtonID); const settingsPanelElement = document.getElementById(settingsPanelID); toggleSettingsButton.addEventListener('click', (event) => { event.stopPropagation(); // Prevent event from bubbling up const isPanelVisible = settingsPanelElement.style.display === 'block'; settingsPanelElement.style.display = isPanelVisible ? 'none' : 'block'; }); document.addEventListener('click', (event) => { if (!settingsPanelElement.contains(event.target) && !toggleSettingsButton.contains(event.target)) { settingsPanelElement.style.display = 'none'; // Hide when clicking outside } }); applySettings(); setupSettingsListeners(); } else { console.error("Failed to attach the settings button: '#chatbox_header .panel__actions' not found."); } } function checkAndSetup() { const chatbox = document.querySelector(chatboxID); const chatMessages = document.querySelector(chatMessagesClassName); if (chatbox) { setupChatFeatures(chatbox); setupSettingsPanel(); } else { console.error('Chatbox not found: Ensure the chatbox ID is correct.'); } if (chatMessages) { setupReplyFeatures(chatMessages); } else { console.error('Chat messages not found: Ensure the chatMessages class is correct.'); } if (!chatbox || !chatMessages) { setTimeout(checkAndSetup, 1000); } } checkAndSetup(); })();