Greasy Fork

Greasy Fork is available in English.

Soop(숲) 채팅 확장 스크립트

이모티콘 창 및 버튼 위치 조정, 채팅 붙여넣기 허용, 채팅창 너비 조절, 커스텀 이모티콘 박스

当前为 2024-10-18 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Soop(숲) 채팅 확장 스크립트
// @namespace    https://tampermonkey.net/
// @icon         https://res.sooplive.co.kr/afreeca.ico
// @version      1.1.0
// @description  이모티콘 창 및 버튼 위치 조정, 채팅 붙여넣기 허용, 채팅창 너비 조절, 커스텀 이모티콘 박스
// @match        https://play.sooplive.co.kr/*
// @license      MIT
// @author       ekzmchoco
// @grant        none
// ==/UserScript==
// Referenced Code: http://greasyfork.icu/scripts/512724
 
(function() {
    'use strict';
 
    const DEFAULT_SETTINGS = {
        chatWidthAdjustment: true,
        customEmoticonBox: true,
        allowPasteInChat: true,
        emoticonButtonReposition: true,
        emoticonButtonColor: false,
        emoticonWindowPositionChange: true,
        emoticonTripleInput: false
    };
    
 
    const userSettings = JSON.parse(localStorage.getItem('soopChatSettings')) || DEFAULT_SETTINGS;
    const savedSettings = JSON.parse(localStorage.getItem('soopChatSettings')) || {};

 
    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: '이모티콘 창 위치 변경*' },
            { key: 'emoticonTripleInput', label: '이모티콘 3개 연속 입력' }
        ];
    
        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 'emoticonTripleInput':
            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", () => {
                      const repeatCount = userSettings.emoticonTripleInput ? 3 : 1;
                      for (let i = 0; i < repeatCount; i++) {
                        item.click();
                      }
                    });
    
                    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(
                    `<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='none'>
                        <g opacity='1'>
                            <path fill='${iconColor}' d='M19.56 18.396a.498.498 0 1 1 .86.506c-.598 1.015-1.973 2.735-4.421 2.735-2.445 0-3.82-1.717-4.418-2.73a.497.497 0 0 1 .176-.684.5.5 0 0 1 .684.176c.498.845 1.617 2.24 3.558 2.24 1.943 0 3.063-1.397 3.56-2.243Z'/>
                            <path stroke='${iconColor}' stroke-width='.4' d='M11.581 18.906c.598 1.014 1.973 2.732 4.418 2.732 2.448 0 3.823-1.72 4.42-2.736a.498.498 0 1 0-.86-.506c-.497.846-1.617 2.243-3.56 2.243-1.94 0-3.06-1.395-3.559-2.24a.5.5 0 0 0-.683-.176.497.497 0 0 0-.176.683Zm0 0 .078-.045'/>
                            <path fill='${iconColor}' stroke='${iconColor}' stroke-width='.45' d='M19.527 15.805a1.227 1.227 0 1 1 0-2.455 1.227 1.227 0 0 1 0 2.455ZM12.477 15.805a1.228 1.228 0 1 1 .001-2.456 1.228 1.228 0 0 1 0 2.456Z'/>
                            <path stroke='${iconColor}' stroke-width='1.4' d='M16 25.8a9.3 9.3 0 1 1 0-18.6 9.3 9.3 0 0 1 0 18.6Z'/>
                        </g>
                    </svg>`
                );
 
                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();
 
})();