您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
-
// ==UserScript== // @name 호감이모티콘 // @namespace http://tampermonkey.net/ // @version 1.0 // @description - // @author You // @match https://play.sooplive.co.kr/* // @icon https://www.google.com/s2/favicons?sz=64&domain=sooplive.co.kr // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const AREA_ID = 'dynamic_area'; const TOGGLEBAR_ID = 'dynamic_toggle'; const FAVORITE_KEY = 'fav_emoticons'; const HEIGHT_KEY = 'dynamic_area_height'; const DEFAULT_DYNAMIC_HEIGHT = 220; const MIN_HEIGHT = 50; const MAX_HEIGHT = 300; const container = document.querySelector('#emoticonContainer'); if (container) { container.style.transition = 'none'; container.style.animation = 'none'; container.classList.remove(...container.classList); const head = container.querySelector('#emoticonBox > div.head'); if (head) { head.innerHTML = ''; head.style.height = '10px'; head.style.padding = '0'; } } function hideEmoticonContainer() { const container = document.querySelector('#emoticonContainer'); if (!container) return; container.style.transition = 'opacity 0.3s ease'; container.style.opacity = '0'; container.style.pointerEvents = 'none'; container.addEventListener('transitionend', function handler() { container.style.display = 'none'; container.removeEventListener('transitionend', handler); }); } function showEmoticonContainer() { const container = document.querySelector('#emoticonContainer'); if (!container) return; positionEmoticonContainer(); container.style.display = 'block'; container.style.opacity = '0'; container.getBoundingClientRect(); container.style.transition = 'opacity 0.3s ease'; container.style.opacity = '1'; container.style.pointerEvents = 'auto'; } const btnEmo = document.querySelector('#btn_emo'); if (btnEmo) { btnEmo.addEventListener('click', () => { const container = document.querySelector('#emoticonContainer'); if (!container) return; if (container.style.display === 'block') hideEmoticonContainer(); else showEmoticonContainer(); }); } function positionEmoticonContainer() { const dynamicArea = document.querySelector('#dynamic_area'); const container = document.querySelector('#emoticonContainer'); if (!dynamicArea || !container) return; const rect = dynamicArea.getBoundingClientRect(); const popupHeight = 300; container.style.position = 'fixed'; container.style.top = `${rect.top - popupHeight - 10}px`; container.style.left = `${rect.left + rect.width / 2}px`; container.style.transform = 'translateX(-50%)'; container.style.width = `${rect.width - 20}px`; container.style.height = `${popupHeight}px`; container.style.margin = '0'; } document.addEventListener('click', (e) => { const container = document.querySelector('#emoticonContainer'); if (!container || container.style.display !== 'block') return; if (e.target.closest('#emoticonContainer .item_list button')) return; const btnEmo = document.querySelector('#btn_emo'); if (!container.contains(e.target) && e.target !== btnEmo) hideEmoticonContainer(); }); window.addEventListener('resize', positionEmoticonContainer); window.addEventListener('scroll', positionEmoticonContainer); function createDynamicArea() { const chatItemWrap = document.querySelector('#chatbox > div.chatting-item-wrap'); const chatArea = document.querySelector('#chat_area'); if (!chatItemWrap || !chatArea || document.getElementById(AREA_ID)) return; // --- #sarsa_btn 제거 --- const sarsaBtn = document.querySelector('#sarsa_btn'); if (sarsaBtn) sarsaBtn.remove(); const dynamicArea = document.createElement('div'); dynamicArea.id = AREA_ID; dynamicArea.style.backgroundColor = '#1C1E22'; dynamicArea.style.height = DEFAULT_DYNAMIC_HEIGHT + 'px'; dynamicArea.style.overflow = 'hidden'; dynamicArea.style.boxSizing = 'border-box'; dynamicArea.style.marginTop = '10px'; const toggleBar = document.createElement('div'); toggleBar.id = TOGGLEBAR_ID; toggleBar.style.height = '20px'; toggleBar.style.backgroundColor = '#232529'; toggleBar.style.cursor = 'ns-resize'; toggleBar.style.userSelect = 'none'; toggleBar.style.display = 'flex'; toggleBar.style.alignItems = 'center'; toggleBar.style.justifyContent = 'center'; dynamicArea.appendChild(toggleBar); const bar = document.createElement('div'); bar.style.width = '40px'; bar.style.height = '5px'; bar.style.background = 'linear-gradient(145deg, #5c5f63, #1a1c1f)'; bar.style.boxShadow = 'inset 0 1px 1px rgba(255,255,255,0.2), 0 2px 4px rgba(0,0,0,0.5)'; bar.style.borderRadius = '2px'; toggleBar.appendChild(bar); const content = document.createElement('div'); content.style.height = 'calc(100% - 20px)'; content.style.overflowY = 'auto'; content.style.padding = '12px 5px'; content.style.display = 'flex'; content.style.flexWrap = 'wrap'; content.style.justifyContent = 'center'; content.style.alignContent = 'flex-start'; content.style.rowGap = '10px'; content.style.columnGap = '10px'; dynamicArea.appendChild(content); chatItemWrap.appendChild(dynamicArea); let favEmoticons = JSON.parse(localStorage.getItem(FAVORITE_KEY) || '[]'); function renderFavEmoticons() { content.innerHTML = ''; favEmoticons.forEach((item, index) => { const img = document.createElement('img'); img.src = item.src; img.title = item.title; img.style.width = '22px'; img.style.height = '22px'; img.style.cursor = 'pointer'; img.draggable = true; img.dataset.index = index; img.addEventListener('click', e => { if (e.ctrlKey || e.metaKey) { favEmoticons.splice(index, 1); localStorage.setItem(FAVORITE_KEY, JSON.stringify(favEmoticons)); renderFavEmoticons(); e.preventDefault(); e.stopPropagation(); return; } syncContainerToDynamic(img.src); const btn = document.querySelector(`#common_emoticon img[src="${item.src}"]`); if (btn) btn.parentElement.click(); }); img.addEventListener('dragstart', e => { e.dataTransfer.setData('text/plain', ''); img.classList.add('dragging'); }); img.addEventListener('dragend', e => { img.classList.remove('dragging'); }); content.appendChild(img); }); content.addEventListener('dragover', e => e.preventDefault()); content.addEventListener('drop', e => { e.preventDefault(); const dragging = content.querySelector('.dragging'); if (!dragging) return; const dragIndex = parseInt(dragging.dataset.index); const dropX = e.clientX; const dropY = e.clientY; const imgs = Array.from(content.querySelectorAll('img')).filter(img => img !== dragging); let closestIndex = favEmoticons.length - 1; let minDistance = Infinity; imgs.forEach(img => { const rect = img.getBoundingClientRect(); const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2; const dist = Math.hypot(dropX - cx, dropY - cy); if (dist < minDistance) { minDistance = dist; closestIndex = parseInt(img.dataset.index); if (dropY < cy) closestIndex -= 0.5; } }); const movedItem = favEmoticons.splice(dragIndex, 1)[0]; if (closestIndex % 1 === 0.5) closestIndex = Math.floor(closestIndex); favEmoticons.splice(closestIndex, 0, movedItem); localStorage.setItem(FAVORITE_KEY, JSON.stringify(favEmoticons)); renderFavEmoticons(); }); } renderFavEmoticons(); function setDynamicHeight(newHeight) { const firstEmo = content.querySelector('img'); const toggleHeight = toggleBar.offsetHeight; let minHeightDynamic = MIN_HEIGHT; if (firstEmo) minHeightDynamic = firstEmo.offsetHeight + toggleHeight + 29; newHeight = Math.max(minHeightDynamic, Math.min(newHeight, MAX_HEIGHT)); dynamicArea.style.height = newHeight + 'px'; chatArea.style.height = (chatItemWrap.offsetHeight - newHeight) + 'px'; content.style.height = (newHeight <= toggleHeight) ? '0' : `calc(100% - ${toggleHeight}px)`; localStorage.setItem(HEIGHT_KEY, newHeight); } const cachedHeight = parseInt(localStorage.getItem(HEIGHT_KEY)); if (!isNaN(cachedHeight)) setDynamicHeight(cachedHeight); else setDynamicHeight(DEFAULT_DYNAMIC_HEIGHT); let isDragging = false, startY, startHeight; toggleBar.addEventListener('mousedown', e => { isDragging = true; startY = e.clientY; startHeight = dynamicArea.offsetHeight; e.preventDefault(); }); window.addEventListener('mousemove', e => { if (!isDragging) return; setDynamicHeight(startHeight + (startY - e.clientY)); }); window.addEventListener('mouseup', () => { isDragging = false; }); const resizeObserver = new ResizeObserver(() => { chatArea.style.height = (chatItemWrap.offsetHeight - dynamicArea.offsetHeight) + 'px'; }); resizeObserver.observe(chatItemWrap); function syncContainerToDynamic(src) { if (!container) return; const allButtons = container.querySelectorAll('#emoticonBox .tab_area li'); allButtons.forEach(btnLi => btnLi.classList.remove('on')); const matchBtn = Array.from(allButtons).find(btnLi => { const img = btnLi.querySelector('img'); return img && src.includes(img.src.split('/').pop()); }); if (matchBtn) matchBtn.classList.add('on'); } document.addEventListener('click', e => { const target = e.target.closest('#common_emoticon span a img'); if (!target) return; if (e.ctrlKey || e.metaKey) { e.preventDefault(); e.stopImmediatePropagation(); const exists = favEmoticons.find(f => f.src === target.src); if (!exists) { favEmoticons.push({src: target.src, title: target.title}); localStorage.setItem(FAVORITE_KEY, JSON.stringify(favEmoticons)); renderFavEmoticons(); } } }, true); } const observer = new MutationObserver((mutations, obs) => { if (document.querySelector('#chatbox > div.chatting-item-wrap') && document.querySelector('#chat_area')) { createDynamicArea(); obs.disconnect(); } }); const chatArea = document.querySelector('#chat_area'); if (chatArea) chatArea.style.padding = '3px 18px'; // --- 채팅 입력란 이미지 24px 미리보기 적용 --- (function() { const writeArea = document.querySelector('#write_area'); if (!writeArea) return; const style = document.createElement('style'); style.textContent = `#write_area img { width: 24px !important; height: auto !important; }`; document.head.appendChild(style); const chatObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.tagName === 'IMG') { node.style.width = '24px'; node.style.height = 'auto'; } }); }); }); chatObserver.observe(writeArea, { childList: true }); })(); observer.observe(document.body, { childList: true, subtree: true }); })();