// ==UserScript== // @name Soop(숲) 채팅 확장 스크립트 // @namespace https://tampermonkey.net/ // @version 1.0.0 // @description 이모티콘 창 및 버튼 위치 조정, 채팅 붙여넣기 허용, 채팅창 너비 조절, 커스텀 이모티콘 박스스 // @match https://play.sooplive.co.kr/* // @license MIT // @author ekzmchoco // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; const DEFAULT_SETTINGS = { chatWidthAdjustment: true, customEmoticonBox: true, allowPasteInChat: true, emoticonButtonReposition: true, emoticonButtonColor: false, emoticonWindowPositionChange: true }; const userSettings = JSON.parse(localStorage.getItem('soopChatSettings')) || DEFAULT_SETTINGS; function saveSettings() { localStorage.setItem('soopChatSettings', JSON.stringify(userSettings)); } function initSettingsUI() { const chattingArea = document.querySelector("#chatting_area"); const personSettingEl = chattingArea.querySelector(".chat_layer.sub.person .contents > ul"); if (document.getElementById('script-settings')) return; const settingsLI = document.createElement("li"); settingsLI.id = 'script-settings'; const settingsOptions = [ { key: 'chatWidthAdjustment', label: '채팅 너비 조절 기능' }, { key: 'customEmoticonBox', label: '커스텀 이모티콘 박스*' }, { key: 'allowPasteInChat', label: '채팅 붙여넣기 허용*' }, { key: 'emoticonButtonReposition', label: '이모티콘 버튼 위치 변경*' }, { key: 'emoticonButtonColor', label: '이모티콘 버튼 색상 (밝게/어둡게)' }, { key: 'emoticonWindowPositionChange', label: '이모티콘 창 위치 변경*' } ]; settingsOptions.forEach(option => { const div = document.createElement("div"); div.classList.add("checkbox_wrap"); const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.id = option.key; checkbox.checked = userSettings[option.key]; const label = document.createElement("label"); label.htmlFor = option.key; label.textContent = option.label; checkbox.addEventListener("change", () => { userSettings[option.key] = checkbox.checked; saveSettings(); applySettings(option.key); if (option.label.includes('*')) { alert('이 설정은 페이지를 새로고침해야 적용됩니다.'); } }); div.appendChild(checkbox); div.appendChild(label); settingsLI.appendChild(div); }); personSettingEl.appendChild(settingsLI); } function applySettings(optionKey) { switch(optionKey) { case 'chatWidthAdjustment': if (userSettings.chatWidthAdjustment) { initChatWidthAdjustment(); } else { removeChatWidthAdjustment(); } break; case 'customEmoticonBox': if (userSettings.customEmoticonBox) { initCustomEmoticonBox(); } else { removeCustomEmoticonBox(); } break; case 'allowPasteInChat': if (userSettings.allowPasteInChat) { enablePasteInChat(); } else { alert('이 설정은 페이지를 새로고침해야 적용됩니다.'); } break; case 'emoticonButtonColor': if (userSettings.emoticonButtonReposition) { alert('이 설정은 페이지를 새로고침해야 적용됩니다.'); } break; case 'emoticonButtonReposition': case 'emoticonWindowPositionChange': break; default: break; } } function init() { initSettingsUI(); if (userSettings.allowPasteInChat) { enablePasteInChat(); } if (userSettings.chatWidthAdjustment) { initChatWidthAdjustment(); } if (userSettings.customEmoticonBox) { initCustomEmoticonBox(); } if (userSettings.emoticonButtonReposition || userSettings.emoticonWindowPositionChange) { initEmoticonRelatedFeatures(); } } function initChatWidthAdjustment() { const chattingArea = document.querySelector("#chatting_area"); const chatTitleDiv = chattingArea.querySelector(".chat_title"); if (chatTitleDiv && !document.getElementById('chatWidthSlider')) { const ul = chatTitleDiv.querySelector("ul"); const viewerLi = ul.querySelector("#setbox_viewer"); const sliderLi = document.createElement("li"); sliderLi.style.padding = "0 10px"; sliderLi.style.display = "flex"; sliderLi.style.alignItems = "center"; const rangeInput = document.createElement("input"); rangeInput.type = "range"; rangeInput.min = 300; rangeInput.max = 450; rangeInput.step = 5; rangeInput.value = localStorage.getItem("customChattingAreaWidth") ? localStorage.getItem("customChattingAreaWidth") : chattingArea.offsetWidth; rangeInput.style.width = "80px"; rangeInput.style.marginRight = "1px"; rangeInput.id = 'chatWidthSlider'; const rangeLabel = document.createElement("span"); rangeLabel.style.color = "#fff"; rangeLabel.style.fontSize = "12px"; rangeInput.addEventListener("input", () => { changeChatAreaWidth(rangeInput.value); localStorage.setItem("customChattingAreaWidth", rangeInput.value); }); sliderLi.appendChild(rangeInput); sliderLi.appendChild(rangeLabel); ul.insertBefore(sliderLi, viewerLi); const chatStyleEl = document.createElement("style"); chatStyleEl.id = 'custom-chat-width-style'; document.head.append(chatStyleEl); function changeChatAreaWidth(width) { chatStyleEl.textContent = ` #webplayer.chat_open { --chatting_W: ${width}px; } `; } const storedWidth = localStorage.getItem("customChattingAreaWidth") || chattingArea.offsetWidth; changeChatAreaWidth(storedWidth); } } function removeChatWidthAdjustment() { const chatWidthSlider = document.getElementById('chatWidthSlider'); if (chatWidthSlider) { chatWidthSlider.parentElement.remove(); } const chatStyleEl = document.getElementById('custom-chat-width-style'); if (chatStyleEl) { chatStyleEl.remove(); } } function initCustomEmoticonBox() { const chattingArea = document.querySelector("#chatting_area"); const actionBox = chattingArea.querySelector("#actionbox"); if (!document.querySelector(".customEmojiBtn")) { const emoticonBox = document.querySelector("#emoticonBox"); const recentEmoticonBtn = emoticonBox.querySelector( ".tab_area .item_list ul > li[data-type='RECENT'] .ic_clock" ); const subTabArea = emoticonBox.querySelector(".subTab_area"); const defaultSubTab = subTabArea.querySelector("li[data-type='DEFAULT']"); const OGQSubTab = subTabArea.querySelector("li[data-type='OGQ']"); function defaultEmoticonClick() { recentEmoticonBtn.click(); setTimeout(() => { defaultSubTab.click(); }, 100); } function OGQEmoticonClick() { recentEmoticonBtn.click(); setTimeout(() => { OGQSubTab.click(); }, 100); } const chattingItemWrap = chattingArea.querySelector(".chatting-item-wrap"); const chatArea = chattingItemWrap.querySelector("#chat_area"); const customEmojiBox = document.createElement("div"); customEmojiBox.classList.add("customEmojiBox"); let isLoading = false; function renderEmoticon(type = "default") { type === "default" ? defaultEmoticonClick() : OGQEmoticonClick(); if (isLoading) return; isLoading = true; setTimeout(() => { isLoading = false; const diffType = type === "default" ? "OGQ" : "default"; const isOn = customEmojiBox.classList.contains(type); const isDiffOn = customEmojiBox.classList.contains(diffType); if (isOn) { customEmojiBox.classList.remove(type); customEmojiBox.innerHTML = ""; customEmojiBox.style.display = "none"; chatArea.style.bottom = "0"; return; } const emoticonItemBox = emoticonBox.querySelector(".emoticon_item"); const itemList = []; emoticonItemBox.querySelectorAll("span > a")?.forEach((item, index) => { if (index < 21) { const itemClone = item.cloneNode(true); itemClone.addEventListener("click", item.click.bind(item)); itemList.push(itemClone); } }); if (isDiffOn) { customEmojiBox.classList.remove(diffType); customEmojiBox.innerHTML = ""; } customEmojiBox.append(...itemList); if (!chattingItemWrap.contains(customEmojiBox)) { chattingItemWrap.append(customEmojiBox); } customEmojiBox.style.display = "flex"; customEmojiBox.classList.add(type); chatArea.style.position = "relative"; chatArea.style.bottom = customEmojiBox.offsetHeight + 8 + "px"; }, 200); } const recentEmoticonCustomBtnLI = document.createElement("li"); const recentEmoticonCustomBtn = document.createElement("a"); recentEmoticonCustomBtn.href = "javascript:;"; recentEmoticonCustomBtn.classList.add("customEmojiBtn"); recentEmoticonCustomBtn.textContent = "최근"; recentEmoticonCustomBtnLI.append(recentEmoticonCustomBtn); const OGQEmoticonCustomBtnLI = document.createElement("li"); const OGQEmoticonCustomBtn = document.createElement("a"); OGQEmoticonCustomBtn.href = "javascript:;"; OGQEmoticonCustomBtn.classList.add("customEmojiBtn"); OGQEmoticonCustomBtn.textContent = "OGQ"; OGQEmoticonCustomBtnLI.append(OGQEmoticonCustomBtn); recentEmoticonCustomBtnLI.addEventListener("click", () => { renderEmoticon("default"); }); OGQEmoticonCustomBtnLI.addEventListener("click", () => { renderEmoticon("OGQ"); }); actionBox .querySelector(".item_box") .append(recentEmoticonCustomBtnLI, OGQEmoticonCustomBtnLI); const iconColor = userSettings.emoticonButtonColor ? '#333' : '#D5D7DC'; const defaultStyleEl = document.createElement("style"); const defaultStyle = ` .chatbox .actionbox .chat_item_list .item_box li a.customEmojiBtn { line-height: 32px; font-size: 15px; font-weight: bold; color: ${iconColor}; background-color: transparent; } .chatbox .actionbox .chat_item_list .item_box li a.customEmojiBtn:hover { color: ${iconColor}; background-color: transparent; } .chatting-item-wrap .customEmojiBox { position: absolute; bottom: 0; left: 0; width: 100%; display: flex; flex-wrap: wrap; gap: 8px 4px; padding: 8px 8px; background-color: #fefefe; } [dark="true"] .chatting-item-wrap .customEmojiBox { background-color: #222; border-top: 1px solid #444; } .chatting-item-wrap .customEmojiBox a { width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center; border-radius: 4px; } .chatting-item-wrap .customEmojiBox a:hover { background-color: rgba(117, 123, 138, 0.2); } `; defaultStyleEl.textContent = defaultStyle; document.head.append(defaultStyleEl); } } function removeCustomEmoticonBox() { const customEmojiBtns = document.querySelectorAll('.customEmojiBtn'); customEmojiBtns.forEach(btn => btn.parentElement.remove()); const customEmojiBox = document.querySelector('.customEmojiBox'); if (customEmojiBox) customEmojiBox.remove(); const styleEl = document.querySelector('#custom-emoticon-style'); if (styleEl) styleEl.remove(); } function enablePasteInChat() { $("#write_area").off("cut copy paste"); $("#write_area").on("paste", function(e) { e.preventDefault(); const clipboardData = (e.originalEvent || e).clipboardData || window.clipboardData; const pastedData = clipboardData.getData('text'); document.execCommand('insertText', false, pastedData); }); } function initEmoticonRelatedFeatures() { const observer = new MutationObserver((mutations, obs) => { const ul = document.querySelector('ul.item_box'); if (!ul) return; const btnStarLi = document.getElementById('btn_star'); const btnAdballoonLi = document.getElementById('btn_adballoon'); const sooptoreLi = ul.querySelector('li.sooptore'); const btnEmo = document.getElementById('btn_emo'); if (!btnStarLi || !btnAdballoonLi || !sooptoreLi || !btnEmo) return; btnStarLi.classList.remove('off'); btnAdballoonLi.classList.remove('off'); sooptoreLi.classList.remove('off'); btnEmo.classList.remove('off'); if (userSettings.emoticonButtonReposition) { const chatWriteDiv = document.getElementById('chat_write'); if (chatWriteDiv && chatWriteDiv.contains(btnEmo)) { chatWriteDiv.removeChild(btnEmo); } let btnEmoLi = document.createElement('li'); btnEmoLi.id = 'btn_emo_li'; btnEmoLi.className = 'emoticon'; btnEmoLi.appendChild(btnEmo); if (ul.firstChild !== btnEmoLi) { ul.insertBefore(btnEmoLi, ul.firstChild); } ul.appendChild(btnStarLi); ul.appendChild(btnAdballoonLi); ul.appendChild(sooptoreLi); btnStarLi.classList.add('right-align'); const iconColor = userSettings.emoticonButtonColor ? '#333' : '#D5D7DC'; const svgIcon = encodeURIComponent( ` ` ); const dataURL = `data:image/svg+xml,${svgIcon}`; btnEmo.style.backgroundImage = `url("${dataURL}")`; btnEmo.style.backgroundRepeat = 'no-repeat'; btnEmo.style.backgroundPosition = 'center'; btnEmo.style.backgroundSize = 'contain'; btnEmo.style.width = '32px'; btnEmo.style.height = '32px'; btnEmo.style.border = 'none'; btnEmo.style.cursor = 'pointer'; btnEmo.style.padding = '0'; btnEmo.style.margin = '0'; btnEmo.style.backgroundColor = 'transparent'; btnEmo.textContent = ''; } if (userSettings.emoticonWindowPositionChange) { const emoticonContainer = document.getElementById('emoticonContainer'); if (emoticonContainer) { const styleEl = document.createElement('style'); styleEl.id = 'custom-emoticon-position-style'; styleEl.textContent = ` .chatbox #emoticonContainer { bottom: 10px; transform: translateX(0); } .chatbox #emoticonContainer.on { bottom: 10px; max-width: 360px; min-width: 320px; right: unset; left: 0; transform: translateX(-105%); } `; document.head.appendChild(styleEl); } } if (!document.getElementById('sooplive-custom-style')) { const style = document.createElement('style'); style.id = 'sooplive-custom-style'; style.innerHTML = ` ul.item_box { display: flex; align-items: center; } ul.item_box li { margin: 0 3px; } ul.item_box li.right-align { margin-left: auto; } `; document.head.appendChild(style); } obs.disconnect(); }); observer.observe(document.body, { childList: true, subtree: true }); } function startScript() { if (typeof livePlayer !== 'undefined' && livePlayer.mainMedia) { init(); } else { setTimeout(startScript, 500); } } startScript(); })();