// ==UserScript== // @name SOOP (숲) - 사이드바 UI 변경 // @name:ko SOOP (숲) - 사이드바 UI 변경 // @namespace https://greasyfork.org/ko/scripts/484713 // @version 20241122 // @description SOOP (숲)의 사이드바 UI를 변경합니다. // @description:ko SOOP (숲)의 사이드바 UI를 변경합니다. // @author You // @match https://www.sooplive.co.kr/* // @match https://play.sooplive.co.kr/* // @match https://vod.sooplive.co.kr/player/* // @icon https://res.sooplive.co.kr/afreeca.ico // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-end // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; const NEW_UPDATE_DATE = 20241019; const CURRENT_URL = window.location.href; const IS_DARK_MODE = document.documentElement.getAttribute('dark') === 'true'; const HIDDEN_BJ_LIST = []; let STATION_FEED_DATA; let menuIds = {}; let categoryMenuIds = {}; let delayCheckEnabled = true; let sharpModeCheckEnabled = true; let displayFollow = GM_getValue("displayFollow", 6); let displayMyplus = GM_getValue("displayMyplus", 6); let displayMyplusvod = GM_getValue("displayMyplusvod", 4); let displayTop = GM_getValue("displayTop", 6); let myplusPosition = GM_getValue("myplusPosition", 1); let myplusOrder = GM_getValue("myplusOrder", 1); let blockedUsers = GM_getValue('blockedUsers', []); let blockedCategories = GM_getValue('blockedCategories', []); let registeredWords = GM_getValue("registeredWords"); let nicknameWidth = GM_getValue("nicknameWidth",126); let isOpenNewtabEnabled = GM_getValue("isOpenNewtabEnabled", 0); let isSidebarMinimized = GM_getValue("isSidebarMinimized", 0); let showSidebarOnScreenMode = GM_getValue("showSidebarOnScreenMode", 1); let savedCategory = GM_getValue("szBroadCategory",0); let isAutoChangeMuteEnabled = GM_getValue("isAutoChangeMuteEnabled", 0); let isDuplicateRemovalEnabled = GM_getValue("isDuplicateRemovalEnabled", 1); let isRemainingBufferTimeEnabled = GM_getValue("isRemainingBufferTimeEnabled", 1); let isPinnedStreamWithNotificationEnabled = GM_getValue("isPinnedStreamWithNotificationEnabled", 0); let isPinnedStreamWithPinEnabled = GM_getValue("isPinnedStreamWithPinEnabled", 0); let isBottomChatEnabled = GM_getValue("isBottomChatEnabled", 0); let isMakePauseButtonEnabled = GM_getValue("isMakePauseButtonEnabled", 1); let isMakeSharpModeShortcutEnabled = GM_getValue("isMakeSharpModeShortcutEnabled", 1); let isMakeLowLatencyShortcutEnabled = GM_getValue("isMakeLowLatencyShortcutEnabled", 1); let isSendLoadBroadEnabled = GM_getValue("isSendLoadBroadEnabled", 1); let isSelectBestQualityEnabled = GM_getValue("isSelectBestQualityEnabled", 1); let isHideSupporterBadgeEnabled = GM_getValue("isHideSupporterBadgeEnabled",0); let isHideFanBadgeEnabled = GM_getValue("isHideFanBadgeEnabled",0); let isHideSubBadgeEnabled = GM_getValue("isHideSubBadgeEnabled",0); let isHideVIPBadgeEnabled = GM_getValue("isHideVIPBadgeEnabled",0); let isHideManagerBadgeEnabled = GM_getValue("isHideManagerBadgeEnabled",0); let isHideStreamerBadgeEnabled = GM_getValue("isHideStreamerBadgeEnabled",0); let isBlockWordsEnabled = GM_getValue("isBlockWordsEnabled",0); let isAutoClaimGemEnabled = GM_getValue("isAutoClaimGemEnabled",0); let isVideoSkipHandlerEnabled = GM_getValue("isVideoSkipHandlerEnabled",0); let isSmallUserLayoutEnabled = GM_getValue("isSmallUserLayoutEnabled",0); let isChannelFeedEnabled = GM_getValue("isChannelFeedEnabled",1); let isChangeFontEnabled = GM_getValue("isChangeFontEnabled", 0); let isCustomSidebarEnabled = GM_getValue("isCustomSidebarEnabled", 1); let isRemoveCarouselEnabled = GM_getValue("isRemoveCarouselEnabled", 0); const WEB_PLAYER_SCROLL_LEFT = isSidebarMinimized ? 52 : 240; function applyFontStyles() { const style = document.createElement('style'); style.textContent = ` @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); * { font-family: 'Inter' !important; } `; document.head.appendChild(style); } // Ensure the page is fully loaded before applying styles if(isChangeFontEnabled) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', applyFontStyles); } else { applyFontStyles(); } } function getHiddenbjListAndCallback(callback) { GM_xmlhttpRequest({ method: "GET", url: "https://live.sooplive.co.kr/api/hiddenbj/hiddenbjController.php", onload: function(response) { try { if (response.status === 200) { const responseData = JSON.parse(response.responseText); if (responseData.RESULT === 1) { if (callback) { callback(responseData.DATA); } } else { console.error("Error: Response data not available or invalid"); if (callback) { callback([]); } } } else { console.error("Error: Request failed with status:", response.status); if (callback) { callback([]); } } } catch (error) { console.error("Error: Failed to parse response data or other unexpected error occurred:", error); if (callback) { callback([]); } } }, onerror: function(error) { console.error("Error: An error occurred while loading data:", error); if (callback) { callback([]); } } }); } function getStationFeedAndCallback(callback) { if (!isChannelFeedEnabled) { callback([]); return; }; GM_xmlhttpRequest({ method: "GET", url: "https://myapi.sooplive.co.kr/api/feed?index_reg_date=0&user_id=&is_bj_write=1&feed_type=&page=1", onload: function(response) { try { if (response.status === 200) { const responseData = JSON.parse(response.responseText); if (callback) { callback(responseData.data); } } else { console.error("Error: Request failed with status:", response.status); if (callback) { callback([]); } } } catch (error) { console.error("Error: Failed to parse response data or other unexpected error occurred:", error); if (callback) { callback([]); } } }, onerror: function(error) { console.error("Error: An error occurred while loading data:", error); if (callback) { callback([]); } } }); } function loadData() { // 현재 시간 기록 const currentTime = new Date().getTime(); // 이전 실행 시간 불러오기 const lastExecutionTime = GM_getValue("lastExecutionTime", 0); // 마지막 실행 시간으로부터 15분 이상 경과했는지 확인 if (currentTime - lastExecutionTime >= 900000) { // URL에 현재 시간을 쿼리 스트링으로 추가해서 캐시 방지 const url = "https://live.sooplive.co.kr/script/locale/ko_KR/broad_category.js?" + currentTime; GM_xmlhttpRequest({ method: "GET", url: url, headers: { "Content-Type": "text/plain; charset=utf-8" }, onload: function(response) { if (response.status === 200) { // 성공적으로 데이터를 받았을 때 처리할 코드 작성 let szBroadCategory = response.responseText; //console.log(szBroadCategory); // 이후 처리할 작업 추가 szBroadCategory = JSON.parse(szBroadCategory.split('var szBroadCategory = ')[1].slice(0, -1)); if (szBroadCategory.CHANNEL.RESULT === "1") { // 데이터 저장 GM_setValue("szBroadCategory", szBroadCategory); // 현재 시간을 마지막 실행 시간으로 업데이트 GM_setValue("lastExecutionTime", currentTime); } } else { console.error("Failed to load data:", response.statusText); } }, onerror: function(error) { console.error("Error occurred while loading data:", error); } }); } else { //console.log("30 minutes not elapsed since last execution. Skipping data load."); } } // 페이지가 로드되면 데이터를 로드하고 처리 window.addEventListener('load', function() { //console.log(GM_getValue("lastExecutionTime")); loadData(); }); function observeDarkAttributeChange(callback) { // MutationObserver 설정 const observer = new MutationObserver((mutationsList) => { for (let mutation of mutationsList) { if (mutation.type === 'attributes' && mutation.attributeName === 'dark') { const darkValue = document.documentElement.getAttribute('dark') === "true"; callback(darkValue); // 콜백 함수 호출 } } }); // 감시할 대상과 옵션 설정 observer.observe(document.documentElement, { attributes: true, // 속성 변화를 감지 attributeFilter: ['dark'], // 'dark' 속성만 감시 }); // observer 반환 (원할 경우 중지할 수 있도록) return observer; } const CommonStyles = ` .left_navbar { display: flex; align-items: center; justify-content: flex-end; position: fixed; flex-direction: row-reverse; top: 0px; left: 140px; z-index: 9999; } .left_nav_button { position: relative; width: 70px; height: 64px; padding: 0; border: 0; cursor: pointer; z-index: 3001; font-size: 15px; font-weight: 600; } #sidebar { top: 64px; } .starting-line .chatting-list-item .message-container .username { width: ${nicknameWidth}px !important; } .duration-overlay { position: absolute; top: 235px; right: 4px; background-color: rgba(0, 0, 0, 0.7); color: white; padding: 2px 5px; font-size: 15px; border-radius: 3px; z-index:9999; line-height: 17px; } #studioPlayKorPlayer, #studioPlayKor, #studioPlay, .btn-broadcast { display: none; } .modal { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); color: black; } .modal-content { background-color: #fefefe; margin: 15% auto; padding: 20px; border: 1px solid #888; border-radius: 10px; width: 80%; max-width: 500px; } .myModalClose { color: #aaa; float: right; font-size: 36px; font-weight: bold; margin-top: -12px; } .myModalClose:hover, .myModalClose:focus { color: black; text-decoration: none; cursor: pointer; } .option { margin-bottom: 20px; display: flex; align-items: center; } .option label { margin-right: 10px; font-size: 16px; } .switch { position: relative; display: inline-block; width: 60px; height: 34px; } .switch input { display: none; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #2196F3; } input:focus + .slider { box-shadow: 0 0 1px #2196F3; } input:checked + .slider:before { transform: translateX(26px); } .slider.round { border-radius: 34px; min-width: 60px; } .slider.round:before { border-radius: 50%; } #range { width: 100%; } #rangeValue { display: inline-block; margin-left: 10px; } .divider { width: 100%; /* 가로 폭 설정 */ height: 1px; /* 세로 높이 설정 */ background-color: #000; /* 배경색 설정 */ margin: 20px 0; /* 위아래 여백 설정 */ } #openModalBtn { box-sizing: border-box; font-size: 12px; line-height: 1.2 !important; font-family: "NG"; list-style: none; position: relative; margin-left: 12px; width: 40px; height: 40px; } #topInnerHeader #openModalBtn { margin-right: 12px; } #openModalBtn > button { background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3e%3cpath d='M11 2.5c1.07 0 1.938.867 1.938 1.938l-.001.245.12.036c.318.104.628.232.927.382l.112.06.174-.173a1.937 1.937 0 0 1 2.594-.126l.128.117a1.923 1.923 0 0 1 .015 2.748l-.17.171.062.117c.151.299.279.608.382.927l.036.12h.245c1.02 0 1.855.787 1.932 1.787L19.5 11c0 1.07-.867 1.938-1.938 1.938l-.246-.001-.035.12a6.578 6.578 0 0 1-.382.926l-.062.116.155.157c.333.322.537.752.578 1.21l.008.172c0 .521-.212 1.02-.576 1.372a1.938 1.938 0 0 1-2.733 0l-.173-.174-.112.06a6.58 6.58 0 0 1-.927.383l-.12.035v.247a1.936 1.936 0 0 1-1.786 1.931l-.151.006a1.938 1.938 0 0 1-1.938-1.937v-.245l-.119-.035a6.58 6.58 0 0 1-.927-.382l-.114-.062-.168.171a1.94 1.94 0 0 1-2.62.119l-.123-.113a1.94 1.94 0 0 1-.003-2.746l.172-.171-.06-.112a6.578 6.578 0 0 1-.381-.927l-.036-.119h-.245a1.938 1.938 0 0 1-1.932-1.786l-.006-.151c0-1.07.867-1.938 1.938-1.938h.245l.036-.119a6.33 6.33 0 0 1 .382-.926l.059-.113-.175-.174a1.94 1.94 0 0 1-.108-2.619l.114-.123a1.94 1.94 0 0 1 2.745.008l.166.168.114-.06c.3-.152.609-.28.927-.383l.119-.036v-.25c0-1.019.787-1.854 1.787-1.931zm0 1a.937.937 0 0 0-.938.938v.937a.322.322 0 0 0 .02.098 5.578 5.578 0 0 0-2.345.966.347.347 0 0 0-.056-.075l-.656-.663a.94.94 0 1 0-1.331 1.326l.665.663c.023.02.048.036.075.05a5.576 5.576 0 0 0-.965 2.343l-.094-.019h-.938a.937.937 0 1 0 0 1.875h.938l.094-.018c.137.845.468 1.647.965 2.343a.375.375 0 0 0-.075.05l-.665.663a.94.94 0 1 0 1.331 1.325l.656-.662a.347.347 0 0 0 .056-.075 5.58 5.58 0 0 0 2.344.966.322.322 0 0 0-.018.094v.936a.937.937 0 1 0 1.874 0v-.938l-.018-.094a5.58 5.58 0 0 0 2.343-.966l.047.075.666.663a.937.937 0 0 0 1.322 0 .922.922 0 0 0 0-1.326l-.656-.663-.075-.05a5.578 5.578 0 0 0 .965-2.343.57.57 0 0 0 .094.018h.938a.937.937 0 1 0 0-1.874h-.938a.57.57 0 0 0-.094.016 5.576 5.576 0 0 0-.965-2.343l.075-.05.656-.663a.922.922 0 0 0 0-1.325.938.938 0 0 0-1.322 0l-.666.662-.046.075a5.578 5.578 0 0 0-2.344-.966l.018-.094v-.938A.937.937 0 0 0 11 3.5zm0 4.188a3.313 3.313 0 1 1 0 6.625 3.313 3.313 0 0 1 0-6.626zm0 1a2.313 2.313 0 1 0 0 4.625 2.313 2.313 0 0 0 0-4.626z' fill='%23707173'/%3e%3c/svg%3e") 50% 50% no-repeat !important; background-size: 18px 22px; !important; } @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* .red-dot이 있을 때만 회전 */ #openModalBtn:has(.red-dot) .btn-settings-ui { animation: rotate 4s linear infinite; animation-duration: 4s; /* 4초에 한 번 회전 */ animation-iteration-count: 10; /* 10번 반복 */ } #sidebar.max { width: 240px; } #sidebar.min { width: 52px; } #sidebar.min .users-section a.user span { display: none; } #sidebar.min .users-section button { font-size:11px; padding: 1px; } #sidebar.max .button-fold-sidebar { background-size: 7px 11px; background-repeat: no-repeat; width: 26px; height: 26px; background-position: center; position: absolute; top: 13px; left: 200px; } #sidebar.max .button-unfold-sidebar { display:none; } #sidebar.min .button-fold-sidebar { display:none; } #sidebar.min .button-unfold-sidebar { background-size: 7px 11px; background-repeat: no-repeat; width: 26px; height: 26px; background-position: center; position: relative; top: 8px; left: 12px; padding-bottom:10px; } #sidebar.min .top-section span.max{ display:none; } #sidebar.max .top-section span.min{ display:none; } .users-section.myplus > .user.show-more, .users-section.follow > .user.show-more, .users-section.top > .user.show-more, .users-section.myplusvod > .user.show-more { display: none; } #toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 { padding: 6px 0px; width: 100%; text-align: center; } #sidebar { grid-area: sidebar; padding-bottom: 360px; height: 100vh; overflow-y: auto; position: fixed; scrollbar-width: none; /* 파이어폭스 */ transition: all 0.1s ease-in-out; /* 부드러운 전환 효과 */ } #sidebar::-webkit-scrollbar { display: none; /* Chrome, Safari, Edge */ } #sidebar .top-section { display: flex; align-items: center; justify-content: space-around; margin: 12px 0px 6px 0px; line-height: 17px; } #sidebar .top-section > span { text-transform: uppercase; font-weight: 550; font-size: 14px; margin-top: 6px; margin-bottom: 2px; } .users-section .user { display: grid; grid-template-areas: "profile-picture username watchers" "profile-picture description blank"; grid-template-columns: 40px auto auto; padding: 5px 10px; } .users-section .user:hover { cursor: pointer; } .users-section .user .profile-picture { grid-area: profile-picture; width: 30px; height: 30px; border-radius: 50%; line-height: 20px; } .users-section .user .username { grid-area: username; font-size: 14px; font-weight: 600; letter-spacing: 0.6px; margin-left:1px; line-height: 17px; } .users-section .user .description { grid-area: description; font-size: 13px; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-left:1px; line-height: 16px; } .users-section .user .watchers { grid-area: watchers; display: flex; align-items: center; justify-content: flex-end; font-weight: 400; font-size: 14px; margin-right: 2px; line-height: 17px; } .users-section .user .watchers .dot { font-size: 7px; margin-right: 5px; } .tooltip-container { z-index: 999; width: 460px; height: auto; position: fixed; display: flex; flex-direction: column; /* 아이템들을 세로 정렬 */ align-items: center; /* 수평 가운데 정렬 */ border-radius: 10px; box-shadow: 5px 5px 10px 0px rgba(0, 0, 0, 0.5); } .tooltip-container img { z-index: 999; width: 100%; /* 컨테이너의 너비에 맞게 확장 */ height: 260px; /* 고정 높이 */ object-fit: cover; /* 비율 유지하며 공간에 맞게 잘리기 */ border-top-left-radius: 10px; border-top-right-radius: 10px; border-bottom-left-radius: 0px; border-bottom-right-radius: 0px; } .tooltiptext { position: relative; z-index: 999; width: 100%; max-width: 460px; height: auto; text-align: center; box-sizing: border-box; padding: 14px 20px; font-size: 17px; border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; line-height: 22px; overflow-wrap: break-word; } .tooltiptext .dot { font-size: 11px; margin-right: 2px; vertical-align: middle; line-height: 22px; display: inline-block; } .profile-grayscale { filter: grayscale(100%) contrast(85%); opacity: .8; } #sidebar.max .small-user-layout { grid-template-areas: "profile-picture username description watchers" !important; grid-template-columns: 24px auto 1fr auto !important; padding: 4px 10px !important; gap: 8px !important; } #sidebar.max .small-user-layout .profile-picture { width: 24px !important; height: 24px !important; border-radius: 20% !important; } #sidebar.max .small-user-layout .username { font-size: 14px !important; line-height: 24px !important; } #sidebar.max .small-user-layout .description { font-size: 12px !important; line-height: 24px !important; } #sidebar.max .small-user-layout .watchers { font-size: 14px !important; line-height: 24px !important; } #sidebar.max .small-user-layout .watchers .dot { font-size: 6px !important; margin-right: 4px !important; } .customSidebar #serviceHeader .a_d_banner { display: none !important; } .customSidebar #serviceHeader .btn_flexible+.logo_wrap { left: 24px !important; } .customSidebar #serviceHeader .logo_wrap { left: 24px !important; } `; const mainPageCommonStyles = ` .customSidebar .btn_flexible { display: none; } #sidebar { z-index: 1401; } button.block-icon-svg-white { width: 40px; height: 50px; } button.block-icon-svg-white span { background-size: 100% 100%; width: 20px; height: 20px; } button.block-icon-svg { width: 40px; height: 50px; } button.block-icon-svg span { background-size: 100% 100%; width: 20px; height: 20px; } @media (max-width: 600.98px) { body.customSidebar main { padding-left: var(--lnb-width-val) !important; } } `; const mainPageDarkmodeStyles = ` #sidebar.max .button-fold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23f9f9f9' d='M5.87 11.01L.01 5.51 5.87.01l1.08 1.01-4.74 4.45L7 9.96 5.87 11z'/%3e%3c/svg%3e"); } #sidebar.min .button-unfold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23f9f9f9' d='M1.13 11.01l5.86-5.5L1.13.01.05 1.02l4.74 4.45L0 9.96 1.13 11z'/%3e%3c/svg%3e"); } button.block-icon-svg-white span { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%23B2B2B2;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E'); } button.block-icon-svg-white:hover span { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%235285FF;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E'); } #toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 { color: #A1A1A1; } .left_nav_button { color: #e5e5e5; } .left_nav_button.active { color: #019BFE; } #sidebar { color: #fff; background-color: #1F1F23; } #sidebar .top-section > span { color: #DEDEE3; } #sidebar .top-section > span > a { color: #DEDEE3; } .users-section .user:hover { background-color: #26262c; } .users-section .user .username { color: #DEDEE3; } .users-section .user .description { color: #a1a1a1; } .users-section .user .watchers { color: #c0c0c0; } .tooltip-container { background-color: #26262C; } .tooltiptext { color: #fff; background-color: #26262C; } `; const mainPageWhitemodeStyles = ` #sidebar.max .button-fold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23888' d='M5.87 11.01L.01 5.51 5.87.01l1.08 1.01-4.74 4.45L7 9.96 5.87 11z'/%3e%3c/svg%3e"); } #sidebar.min .button-unfold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23888' d='M1.13 11.01l5.86-5.5L1.13.01.05 1.02l4.74 4.45L0 9.96 1.13 11z'/%3e%3c/svg%3e"); } button.block-icon-svg span { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%237C7D7D;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E'); } button.block-icon-svg:hover span { background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 64 64" style="fill:%235285FF;"%3E%3Cpath d="M32 6C17.641 6 6 17.641 6 32C6 46.359 17.641 58 32 58C46.359 58 58 46.359 58 32C58 17.641 46.359 6 32 6zM32 10C37.331151 10 42.225311 11.905908 46.037109 15.072266L14.505859 45.318359C11.682276 41.618415 10 37.00303 10 32C10 19.869 19.869 10 32 10zM48.927734 17.962891C52.094092 21.774689 54 26.668849 54 32C54 44.131 44.131 54 32 54C26.99697 54 22.381585 52.317724 18.681641 49.494141L48.927734 17.962891z"%3E%3C/path%3E%3C/svg%3E'); } #toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 { color: #53535F; } .left_nav_button { color: #1F1F23; } .left_nav_button.active { color: #0545B1; } #sidebar { color: black; background-color: #EFEFF1; } #sidebar .top-section>span { color: #0E0E10; } #sidebar .top-section>span>a { color: #0E0E10; } .users-section .user:hover { background-color: #E6E6EA; } .users-section .user .username { color: #1F1F23; } .users-section .user .description { color: #53535F; } .users-section .user .watchers { color: black; } .tooltip-container { background-color: #E6E6EA; } .tooltiptext { color: black; background-color: #E6E6EA; } `; const playerCommonStyles = ` .screen_mode .left_navbar, .fullScreen_mode .left_navbar{ display: none; } .customSidebar .btn_flexible { display: none; } /* 스크롤바 스타일링 */ html { overflow: auto; /* 스크롤 기능 유지 */ } /* Firefox 전용 스크롤바 감추기 */ html::-webkit-scrollbar { display: none; /* 크롬 및 사파리에서 */ } /* Firefox에서는 아래와 같이 처리 */ html { scrollbar-width: none; /* Firefox에서 스크롤바 감추기 */ -ms-overflow-style: none; /* Internet Explorer 및 Edge */ } .customSidebar.screen_mode #webplayer { transition: all 0.25s ease-in-out !important; } @media screen and (max-width: 892px) { .screen_mode.bottomChat #webplayer #player .view_ctrl, .screen_mode.bottomChat #webplayer .wrapping.side { display: block !important; } } .customSidebar #webplayer_contents { width: calc(100vw - ${WEB_PLAYER_SCROLL_LEFT}px) !important; gap:0 !important; padding: 0 !important; margin: 64px 0 0 !important; left: ${WEB_PLAYER_SCROLL_LEFT}px !important; } /* sidebar가 .max 클래스를 가질 때, body에 .screen_mode가 없을 경우 */ body:not(.screen_mode):not(.fullScreen_mode):has(#sidebar.max) #webplayer_contents { width: calc(100vw - 240px) !important; left: 240px !important; } /* sidebar가 .min 클래스를 가질 때, body에 .screen_mode가 없을 경우 */ body:not(.screen_mode):not(.fullScreen_mode):has(#sidebar.min) #webplayer_contents { width: calc(100vw - 52px) !important; left: 52px !important; } .screen_mode #sidebar { top: 0 !important; } .customSidebar.screen_mode #webplayer #webplayer_contents, .customSidebar.fullScreen_mode #webplayer #webplayer_contents { top: -64px !important; left: 0 !important; width: 100vw !important; height: 100vh !important; } .screen_mode.bottomChat #webplayer #webplayer_contents { top: 0 !important; margin: 0 !important; } #webplayer_contents #player_area .broadcast_information .player_item_list { margin: 0px 6px 0 0; } .screen_mode.bottomChat #player { min-height: auto !important; } .screen_mode.bottomChat #webplayer #webplayer_contents { position: relative; box-sizing: border-box; flex: auto; display: flex; flex-direction: column !important; justify-content:flex-start !important; } .screen_mode.bottomChat #webplayer #webplayer_contents .wrapping.side { width: 100% !important; max-height: calc(100vh - (100vw * 9 / 16)) !important; } .screen_mode.bottomChat #webplayer #webplayer_contents .wrapping.side section.box.chatting_box { height: 100% !important; } .screen_mode.bottomChat #webplayer #webplayer_contents .wrapping.side section.box.chatting_box #chatting_area { height: 100% !important } .screen_mode.bottomChat #webplayer #webplayer_contents #player_area .htmlplayer_wrap, .screen_mode.bottomChat #webplayer #webplayer_contents #player_area .htmlplayer_content, .screen_mode.bottomChat #webplayer #webplayer_contents #player_area .float_box, .screen_mode.bottomChat #webplayer #webplayer_contents #player_area #player { height: auto !important; max-height: max-content; } `; const darkModePlayerStyles = ` #sidebar.max .button-fold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23f9f9f9' d='M5.87 11.01L.01 5.51 5.87.01l1.08 1.01-4.74 4.45L7 9.96 5.87 11z'/%3e%3c/svg%3e"); } #sidebar.min .button-unfold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23f9f9f9' d='M1.13 11.01l5.86-5.5L1.13.01.05 1.02l4.74 4.45L0 9.96 1.13 11z'/%3e%3c/svg%3e"); } #sidebar { color: white; background-color: #1F1F23; } #sidebar .top-section > span { color:#DEDEE3; } #sidebar .top-section > span > a { color:#DEDEE3; } .users-section .user:hover { background-color: #26262c; } .users-section .user .username { color:#DEDEE3; } .users-section .user .description { color: #a1a1a1; } .users-section .user .watchers { color: #c0c0c0; } .left_nav_button { color: #e5e5e5; } .tooltip-container { background-color: #26262C; } .tooltiptext { color: #fff; background-color: #26262C; } #toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 { color:#A1A1A1; } `; const whiteModePlayerStyles = ` #sidebar.max .button-fold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23888' d='M5.87 11.01L.01 5.51 5.87.01l1.08 1.01-4.74 4.45L7 9.96 5.87 11z'/%3e%3c/svg%3e"); } #sidebar.min .button-unfold-sidebar { background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='none slice' viewBox='0 0 7 11'%3e%3cpath fill='%23888' d='M1.13 11.01l5.86-5.5L1.13.01.05 1.02l4.74 4.45L0 9.96 1.13 11z'/%3e%3c/svg%3e"); } #sidebar { color: white; background-color: #EFEFF1; } #sidebar .top-section > span { color:#0E0E10; } #sidebar .top-section > span > a { color:#0E0E10; } .users-section .user:hover { background-color: #E6E6EA; } .users-section .user .username { color:#1F1F23; } .users-section .user .description { color: #53535F; } .users-section .user .watchers { color: black; } .tooltip-container { background-color: #E6E6EA; } .tooltiptext { color: black; background-color: #E6E6EA; } .left_nav_button { color: #1F1F23; } #toggleButton, #toggleButton2, #toggleButton3, #toggleButton4 { color: #53535F; } `; //======================================공용 함수======================================// // 통합 함수: 날짜를 비교하고 빨간 점을 표시, 클릭 시 빨간 점을 숨기고 날짜 업데이트 function manageRedDot() { const RED_DOT_CLASS = 'red-dot'; const style = document.createElement('style'); style.innerHTML = ` .${RED_DOT_CLASS} { position: absolute; top: 8px; right: 8px; width: 4px; height: 4px; background-color: red; border-radius: 50%; } `; document.head.appendChild(style); const lastUpdateDate = GM_getValue('lastUpdateDate', 0); // 저장된 마지막 업데이트 날짜를 가져옴 const btn = document.querySelector('#openModalBtn > button'); // 빨간 점 추가 함수 function showRedDot() { if (!btn || document.querySelector(`#openModalBtn .${RED_DOT_CLASS}`)) return; const redDot = document.createElement('div'); redDot.classList.add(RED_DOT_CLASS); btn.parentElement.appendChild(redDot); } // 빨간 점 제거 함수 function hideRedDot() { const redDot = document.querySelector(`#openModalBtn .${RED_DOT_CLASS}`); if (redDot) redDot.remove(); } // 날짜를 비교하여 빨간 점 표시 if (NEW_UPDATE_DATE > lastUpdateDate) { showRedDot(); } else { hideRedDot(); } // 버튼 클릭 시 이벤트 핸들러 추가 btn.addEventListener('click', () => { GM_setValue('lastUpdateDate', NEW_UPDATE_DATE); hideRedDot(); }); } function addNumberSeparator(number) { number = Number(number); // 숫자가 10,000 이상일 때 if (number >= 10000) { let displayNumber = (number / 10000).toFixed(2); displayNumber = Number(displayNumber).toFixed(1); if (displayNumber.endsWith('.0')) { displayNumber = displayNumber.slice(0, -2); } return displayNumber + '만'; } return number.toLocaleString(); } function getCategoryName(targetCateNo) { function searchCategory(categories, targetCateNo) { // 카테고리 배열을 순회합니다. for (let category of categories) { // 현재 카테고리의 cate_no가 목표 cate_no와 일치하는지 확인합니다. if (category.cate_no === targetCateNo) { // 일치하는 경우 cate_name을 반환합니다. return category.cate_name; } else { // 현재 카테고리에 child가 있는지 확인합니다. if (category.child && category.child.length > 0) { // 재귀적으로 child 카테고리를 검색합니다. let result = searchCategory(category.child, targetCateNo); // 재귀 호출 결과가 null이 아니라면 해당 cate_name을 반환합니다. if (result !== null) { return result; } } } } if (targetCateNo === "ADULT_BROAD_CATE") return "연령제한"; // cate_no에 해당하는 카테고리가 없는 경우 null을 반환합니다. return null; } // 함수 호출 시 CHANNEL.BROAD_CATEGORY에서 시작합니다. return searchCategory(savedCategory.CHANNEL.BROAD_CATEGORY, targetCateNo); } // 차단 목록을 저장합니다. function saveBlockedUsers() { GM_setValue('blockedUsers', blockedUsers); } // 사용자를 차단 목록에 추가합니다. function blockUser(userName, userId) { // 이미 차단된 사용자인지 확인 if (!isUserBlocked(userId)) { blockedUsers.push({ userName, userId }); saveBlockedUsers(); alert(`사용자 ${userName}(${userId})를 차단했습니다.`); registerUnblockMenu({ userName, userId }); } else { alert(`사용자 ${userName}(${userId})는 이미 차단되어 있습니다.`); } } // 함수: 사용자 차단 해제 function unblockUser(userId) { // 차단된 사용자 목록에서 해당 사용자 찾기 let unblockedUser = blockedUsers.find(user => user.userId === userId); // 사용자를 찾았을 때만 차단 해제 및 메뉴 삭제 수행 if (unblockedUser) { // 차단된 사용자 목록에서 해당 사용자 제거 blockedUsers = blockedUsers.filter(user => user.userId !== userId); // 변경된 목록을 저장 GM_setValue('blockedUsers', blockedUsers); alert(`사용자 ${userId}의 차단이 해제되었습니다.`); unregisterUnblockMenu(unblockedUser.userName); } } // 사용자가 이미 차단되어 있는지 확인합니다. function isUserBlocked(userId) { return blockedUsers.some(user => user.userId === userId); } // 함수: 동적으로 메뉴 등록 function registerUnblockMenu(user) { // GM_registerMenuCommand로 메뉴를 등록하고 메뉴 ID를 기록 let menuId = GM_registerMenuCommand(`💔 차단 해제 - ${user.userName}`, function() { unblockUser(user.userId); }); // 메뉴 ID를 기록 menuIds[user.userName] = menuId; } // 함수: 동적으로 메뉴 삭제 function unregisterUnblockMenu(userName) { // userName을 기반으로 저장된 메뉴 ID를 가져와서 삭제 let menuId = menuIds[userName]; if (menuId) { GM_unregisterMenuCommand(menuId); delete menuIds[userName]; // 삭제된 메뉴 ID를 객체에서도 제거 } } // 카테고리 목록을 저장합니다. function saveBlockedCategories() { GM_setValue('blockedCategories', blockedCategories); } // 카테고리를 차단 목록에 추가합니다. function blockCategory(categoryName, categoryId) { // 이미 차단된 카테고리인지 확인 if (!isCategoryBlocked(categoryId)) { blockedCategories.push({ categoryName, categoryId }); saveBlockedCategories(); alert(`카테고리 ${categoryName}(${categoryId})를 차단했습니다.`); registerCategoryUnblockMenu({ categoryName, categoryId }); } else { alert(`카테고리 ${categoryName}(${categoryId})는 이미 차단되어 있습니다.`); } } // 함수: 카테고리 차단 해제 function unblockCategory(categoryId) { // 차단된 카테고리 목록에서 해당 카테고리 찾기 let unblockedCategory = blockedCategories.find(category => category.categoryId === categoryId); // 카테고리를 찾았을 때만 차단 해제 및 메뉴 삭제 수행 if (unblockedCategory) { // 차단된 카테고리 목록에서 해당 카테고리 제거 blockedCategories = blockedCategories.filter(category => category.categoryId !== categoryId); // 변경된 목록을 저장 GM_setValue('blockedCategories', blockedCategories); alert(`카테고리 ${categoryId}의 차단이 해제되었습니다.`); unregisterCategoryUnblockMenu(unblockedCategory.categoryName); } } // 카테고리가 이미 차단되어 있는지 확인합니다. function isCategoryBlocked(categoryId) { return blockedCategories.some(category => category.categoryId === categoryId); } // 함수: 동적으로 카테고리 메뉴 등록 function registerCategoryUnblockMenu(category) { // GM_registerMenuCommand로 카테고리 메뉴를 등록하고 메뉴 ID를 기록 let menuId = GM_registerMenuCommand(`💔 카테고리 차단 해제 - ${category.categoryName}`, function() { unblockCategory(category.categoryId); }); // 메뉴 ID를 기록 categoryMenuIds[category.categoryName] = menuId; } // 함수: 동적으로 카테고리 메뉴 삭제 function unregisterCategoryUnblockMenu(categoryName) { // categoryName을 기반으로 저장된 메뉴 ID를 가져와서 삭제 let menuId = categoryMenuIds[categoryName]; if (menuId) { GM_unregisterMenuCommand(menuId); delete categoryMenuIds[categoryName]; // 삭제된 메뉴 ID를 객체에서도 제거 } } function waitForElement(elementSelector, callBack, attempts = 0, maxAttempts = 100) { const element = document.body.querySelector(elementSelector); if (element) { callBack(elementSelector, element); } else { if (attempts < maxAttempts) { setTimeout(function () { waitForElement(elementSelector, callBack, attempts + 1, maxAttempts); }, 200); } else { console.log(`Reached maximum attempts. ${elementSelector} not found.`); } } } function desc_order(selector) { // Get the container element const container = document.body.querySelector(selector); // Get all user elements const userElements = document.body.querySelectorAll(`${selector} > .user`); // Create arrays for each category let category1 = []; let category2 = []; let category3 = []; let category4 = []; let category5 = []; // Categorize users userElements.forEach(user => { const isPin = user.getAttribute('is_pin') === 'Y'; const hasBroadThumbnail = user.hasAttribute('broad_thumbnail'); // 온라인 채널 const isMobilePush = user.getAttribute('is_mobile_push') === 'Y'; const isOffline = user.hasAttribute('is_offline'); if (isPin && hasBroadThumbnail) { category1.push(user); } else if (isPin && !hasBroadThumbnail) { category2.push(user); } else if (!isPin && isMobilePush && !isOffline) { category3.push(user); } else if (!isPin && !isMobilePush && !isOffline) { category4.push(user); } else { category5.push(user); } }); // Sort each category by watchers category1.sort(compareWatchers); category2.sort(compareWatchers); category3.sort(compareWatchers); category4.sort(compareWatchers); category5.sort(compareWatchers); // Clear container and append sorted elements container.innerHTML = ''; [...category1, ...category2, ...category3, ...category4, ...category5].forEach(user => { container.appendChild(user); }); } function compareWatchers(a, b) { const watchersA = parseInt(a.getAttribute('data-watchers') || '0'); const watchersB = parseInt(b.getAttribute('data-watchers') || '0'); return watchersB - watchersA; // Sort by watchers } function makeTopNavbarAndSidebar(page){ // .left_navbar를 찾거나 생성 let leftNavbar = document.body.querySelector('.left_navbar'); if (!leftNavbar) { leftNavbar = document.createElement('div'); leftNavbar.className = 'left_navbar'; // 페이지의 적절한 위치에 추가 const targetElement = document.body; // 원하는 위치에 따라 수정 targetElement.insertBefore(leftNavbar, targetElement.firstChild); } const buttonData = [ { href: '/live/all', text: 'LIVE' }, { href: '/directory/category', text: '탐색', onClickTarget: '#container a.more[href="/directory/category"]' } ]; buttonData.reverse().forEach(function (data) { const newButton = document.createElement('a'); if (data.onClickTarget) { newButton.addEventListener('click', function(event) { const targetElement = document.querySelector(data.onClickTarget); if (targetElement) { event.preventDefault(); // 타겟 요소가 존재하면 기본 동작 방지 targetElement.click(); // 타겟 요소 클릭 } else { // 타겟 요소가 없으면 기본 동작으로 href 이동 newButton.href = data.href; newButton.target = "_self"; } }); } else { newButton.href = data.href; newButton.target = "_self"; } newButton.innerHTML = ``; leftNavbar.appendChild(newButton); }); const tooltipContainer = document.createElement('div'); tooltipContainer.classList.add('tooltip-container'); const sidebarClass = isSidebarMinimized ? "min" : "max"; if(page==="main"){ const newHtml = `
`; const serviceLnbElement = document.getElementById('soop-gnb'); if (serviceLnbElement) { serviceLnbElement.insertAdjacentHTML('afterend', newHtml); } const listsection = document.body; listsection.appendChild(tooltipContainer); } if(page==="player"){ const sidebarHtml = ` `; const webplayerElement = document.body; if (webplayerElement) { webplayerElement.insertAdjacentHTML('beforeend', sidebarHtml); } webplayerElement.appendChild(tooltipContainer); } } function updateElementWithContent(targetElement, newContent) { // DocumentFragment 생성 function createFragment(content) { const fragment = document.createDocumentFragment(); const tempDiv = document.createElement('div'); tempDiv.innerHTML = content; while (tempDiv.firstChild) { fragment.appendChild(tempDiv.firstChild); } return fragment; } // 기존 내용을 지우고 DocumentFragment를 적용 function applyFragment(fragment) { targetElement.innerHTML = ''; // 기존 내용을 모두 지움 targetElement.appendChild(fragment); // 새로운 내용 추가 } // 호출 시점에 전달된 newContent를 사용하여 DocumentFragment 생성 후 적용 applyFragment(createFragment(newContent)); } // 사용자 요소를 생성하는 함수 function createUserElement(channel, is_mobile_push, is_pin) { const userId = channel.user_id; const broadNo = channel.broad_no; const totalViewCnt = channel.total_view_cnt; const broadTitle = channel.broad_title; const userNick = channel.user_nick; const broadStart = channel.broad_start; const playerLink = "https://play.sooplive.co.kr/"+userId+"/"+broadNo; const broad_thumbnail = `https://liveimg.sooplive.co.kr/m/${broadNo}`; const userElement = document.createElement('a'); userElement.classList.add('user'); if(isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); if(!isOpenNewtabEnabled){ userElement.setAttribute('href',`${playerLink}`); if (isSendLoadBroadEnabled && CURRENT_URL.startsWith("https://play.sooplive.co.kr/")) { userElement.setAttribute('onclick', ` if(event.ctrlKey){ return; } event.preventDefault(); event.stopPropagation(); if (document.body.querySelector('div.loading') && getComputedStyle(document.body.querySelector('div.loading')).display === 'none') { liveView.playerController.sendLoadBroad('${userId}', ${broadNo}); } else { location.href = '${playerLink}'; } `); } } else { userElement.setAttribute('href',`${playerLink}`); userElement.setAttribute('target','_blank'); } userElement.setAttribute('data-watchers',`${totalViewCnt}`); userElement.setAttribute('broad_thumbnail',`${broad_thumbnail}`); userElement.setAttribute('tooltip',`${broadTitle}`); userElement.setAttribute('user_id',`${userId}`); userElement.setAttribute('broad_start',`${broadStart}`); if (is_mobile_push) { userElement.setAttribute('is_mobile_push', is_mobile_push); if (is_pin) { userElement.setAttribute('is_pin', 'Y'); } else { userElement.setAttribute('is_pin', 'N'); } } const profilePicture = document.createElement('img'); const pp_webp="https://stimg.sooplive.co.kr/LOGO/"+userId.slice(0, 2)+"/"+userId+"/m/"+userId+".webp"; const pp_jpg="https://profile.img.sooplive.co.kr/LOGO/"+userId.slice(0, 2)+"/"+userId+"/m/"+userId+".jpg"; profilePicture.src = pp_webp; profilePicture.setAttribute('onerror', `this.onerror=null; this.src='${pp_jpg}'`); profilePicture.setAttribute('alt', `${userId}'`); if (isOpenNewtabEnabled === 1) { profilePicture.setAttribute('onclick', `event.preventDefault(); event.stopPropagation(); document.getElementById('sidebar').offsetWidth === 52 ? window.open('${playerLink}', '_blank') : window.open('https://ch.sooplive.co.kr/${userId}', '_blank');`); } else { // 프로필 클릭, 빠른 이동 & 플레이어 페이지 if(isSendLoadBroadEnabled && CURRENT_URL.startsWith("https://play.sooplive.co.kr/")){ profilePicture.setAttribute('onclick', ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { if(event.ctrlKey) { window.open('${playerLink}', '_blank'); return; }; if (document.body.querySelector('div.loading') && getComputedStyle(document.body.querySelector('div.loading')).display === 'none') { liveView.playerController.sendLoadBroad('${userId}', ${broadNo}); } else { location.href = '${playerLink}'; } } else { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } `); } else { // 프로필 클릭, 보통 이동, 메인 페이지 profilePicture.setAttribute('onclick', ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { if(event.ctrlKey) { window.open('${playerLink}', '_blank'); } else { location.href = '${playerLink}'; } } else { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } `); } } // 프로필 휠클릭일 때 profilePicture.setAttribute('onmousedown', ` if (event.button === 1) { if (document.getElementById('sidebar').offsetWidth !== 52) { event.preventDefault(); event.stopPropagation(); window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } } `); profilePicture.classList.add('profile-picture'); const username = document.createElement('span'); username.classList.add('username'); if(is_pin){ username.textContent = `🖈${userNick}`; username.setAttribute('title','고정됨(상단 고정 켜짐)'); } else if (is_mobile_push==="Y") { username.textContent = `🖈${userNick}`; username.setAttribute('title','고정됨(알림 받기 켜짐)'); } else { username.textContent = userNick; } const cat_no = channel.broad_cate_no; const categoryName = channel.category_name || getCategoryName(cat_no); const description = document.createElement('span'); description.classList.add('description'); description.textContent = categoryName; description.title = categoryName; userElement.setAttribute('broad_cate_no',`${cat_no}`); const watchers = document.createElement('span'); watchers.classList.add('watchers'); watchers.innerHTML = `🔴${addNumberSeparator(totalViewCnt)}`; userElement.appendChild(profilePicture); userElement.appendChild(username); userElement.appendChild(description); userElement.appendChild(watchers); return userElement; } function createUserElement_vod(channel) { const userId = channel.user_id; const broadNo = channel.title_no; const totalViewCnt = channel.view_cnt; const broadTitle = channel.title; const userNick = channel.user_nick; const vod_duration = channel.vod_duration; const playerLink = "https://vod.sooplive.co.kr/player/"+broadNo; const broad_thumbnail = `${channel.thumbnail}`.replace("http://", "https://"); const userElement = document.createElement('a'); userElement.classList.add('user'); if(isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); if(!isOpenNewtabEnabled){ userElement.setAttribute('href',`${playerLink}`); } else { userElement.setAttribute('href',`${playerLink}`); userElement.setAttribute('target','_blank'); } userElement.setAttribute('data-watchers',`${totalViewCnt}`); userElement.setAttribute('broad_thumbnail',`${broad_thumbnail}`); userElement.setAttribute('tooltip',`${broadTitle}`); userElement.setAttribute('user_id',`${userId}`); userElement.setAttribute('vod_duration',`${vod_duration}`); const profilePicture = document.createElement('img'); const pp_webp="https://stimg.sooplive.co.kr/LOGO/"+userId.slice(0, 2)+"/"+userId+"/m/"+userId+".webp"; const pp_jpg="https://profile.img.sooplive.co.kr/LOGO/"+userId.slice(0, 2)+"/"+userId+"/m/"+userId+".jpg"; profilePicture.src = pp_webp; profilePicture.setAttribute('onerror', `this.onerror=null; this.src='${pp_jpg}'`); profilePicture.setAttribute('alt', `${userId}'`); if (isOpenNewtabEnabled === 1) { profilePicture.setAttribute('onclick', `event.preventDefault(); event.stopPropagation(); document.getElementById('sidebar').offsetWidth === 52 ? window.open('${playerLink}', '_blank') : window.open('https://ch.sooplive.co.kr/${userId}', '_blank');`); } else { profilePicture.setAttribute('onclick', `event.preventDefault(); event.stopPropagation(); document.getElementById('sidebar').offsetWidth === 52 ? location.href = '${playerLink}' : window.open('https://ch.sooplive.co.kr/${userId}', '_blank');`); } // 프로필 휠클릭일 때 profilePicture.setAttribute('onmousedown', ` if (event.button === 1) { if (document.getElementById('sidebar').offsetWidth !== 52) { event.preventDefault(); event.stopPropagation(); window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } } `); profilePicture.classList.add('profile-picture'); profilePicture.classList.add('profile-grayscale'); const username = document.createElement('span'); username.classList.add('username'); username.textContent = userNick; const reg_date = channel.reg_date; const description = document.createElement('span'); description.classList.add('description'); description.textContent = vod_duration; description.title = vod_duration; const watchers = document.createElement('span'); watchers.classList.add('watchers'); watchers.innerHTML = timeSince(reg_date); userElement.appendChild(profilePicture); userElement.appendChild(username); userElement.appendChild(description); userElement.appendChild(watchers); return userElement; } function createUserElement_offline(channel, isFeeditem) { const feeditem = isFeeditem; const userId = channel.user_id; const totalViewCnt = channel.total_view_cnt; const userNick = channel.user_nick; const playerLink = feeditem ? feeditem.url : "https://ch.sooplive.co.kr/"+userId; const is_mobile_push = channel.is_mobile_push; const is_pin = isPinnedStreamWithPinEnabled === 1 ? channel.is_pin : false; const is_offline = "Y"; const feedTimestamp = feeditem ? feeditem.reg_timestamp : false; const feedRegDate = feeditem ? feeditem.reg_date : false; const userElement = document.createElement('a'); userElement.classList.add('user'); if(isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); if(!isOpenNewtabEnabled){ userElement.setAttribute('href',`${playerLink}`); userElement.setAttribute('target','_blank'); } else { userElement.setAttribute('href',`${playerLink}`); userElement.setAttribute('target','_blank'); } userElement.setAttribute('broad_start',`${feedRegDate}`); if (isFeeditem) { userElement.setAttribute('data-watchers',`${feedTimestamp}`); } else { userElement.setAttribute('data-watchers',`${totalViewCnt}`); } userElement.setAttribute('user_id',`${userId}`); if (isFeeditem) { if (feeditem.photo_cnt) { userElement.setAttribute('broad_thumbnail',`https:${feeditem.photos[0].url}`) } else { userElement.setAttribute('data-tooltip-listener',`false`) } userElement.setAttribute('tooltip',`${feeditem.title_name}`); } else { userElement.setAttribute('data-tooltip-listener',`false`) } if (is_mobile_push) { userElement.setAttribute('is_mobile_push', is_mobile_push); if (is_pin) { userElement.setAttribute('is_pin', 'Y'); } else { userElement.setAttribute('is_pin', 'N'); } } userElement.setAttribute('is_offline',`${is_offline}`); const profilePicture = document.createElement('img'); const pp_webp="https://stimg.sooplive.co.kr/LOGO/"+userId.slice(0, 2)+"/"+userId+"/m/"+userId+".webp"; const pp_jpg="https://profile.img.sooplive.co.kr/LOGO/"+userId.slice(0, 2)+"/"+userId+"/m/"+userId+".jpg"; profilePicture.src = pp_webp; profilePicture.setAttribute('onerror', `this.onerror=null; this.src='${pp_jpg}'`); profilePicture.setAttribute('alt', `${userId}'`); if (isOpenNewtabEnabled === 1) { profilePicture.setAttribute('onclick', `event.preventDefault(); event.stopPropagation(); document.getElementById('sidebar').offsetWidth === 52 ? window.open('${playerLink}', '_blank') : window.open('https://ch.sooplive.co.kr/${userId}', '_blank');`); } else { profilePicture.setAttribute('onclick', `event.preventDefault(); event.stopPropagation(); document.getElementById('sidebar').offsetWidth === 52 ? location.href = '${playerLink}' : window.open('https://ch.sooplive.co.kr/${userId}', '_blank');`); } profilePicture.classList.add('profile-picture'); profilePicture.classList.add('profile-grayscale'); const username = document.createElement('span'); username.classList.add('username'); if (is_pin) { username.textContent = `🖈${userNick}`; username.setAttribute('title','고정됨(상단 고정 켜짐)'); } else { username.textContent = `${userNick}`; } const description = document.createElement('span'); description.classList.add('description'); if (isFeeditem) { description.textContent = `${feeditem.title_name}`; description.title = `${feeditem.title_name}`; } else { description.textContent = ''; } const watchers = document.createElement('span'); watchers.classList.add('watchers'); if (!isFeeditem) { watchers.innerHTML = `🔴오프라인`; } else { watchers.innerHTML = `${timeSince(feeditem.reg_date)}`; } userElement.appendChild(profilePicture); userElement.appendChild(username); userElement.appendChild(description); userElement.appendChild(watchers); return userElement; } function checkIfTimeover(timestamp) { const now = Date.now(); // 현재 시간을 타임스탬프로 얻음 const inputTime = timestamp * 1000; // 초 단위 타임스탬프를 밀리초 단위로 변환 // 1시간 = 3600000 밀리초 return (now - inputTime) > 24*3600000; } function timeSince(timestamp) { const currentTime = new Date(); const pastTime = new Date(timestamp.replace(/-/g, '/')); const seconds = Math.floor((currentTime - pastTime) / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 365) { const years = Math.floor(days / 365); return years + "년 전"; } else if (days > 30) { const months = Math.floor(days / 30); return months + "개월 전"; } else if (days > 0) { return days + "일 전"; } else if (hours > 0) { return hours + "시간 전"; } else if (minutes > 0) { return minutes + "분 전"; } else { return seconds + "초 전"; } } function isUserInFollowSection(userid) { const followUsers = document.body.querySelectorAll('.users-section.follow .user'); // 유저가 포함되어 있는지 확인 for (const user of followUsers) { if (user.getAttribute('user_id') === userid) { return true; // 유저가 포함되어 있으면 true를 리턴 } } return false; // 유저가 포함되어 있지 않으면 false를 리턴 } function insertFoldButton() { const foldButton = ` `; const newHtml = `${foldButton}`; const webplayer_scroll = document.getElementById('webplayer_scroll') || document.getElementById('list-container'); const serviceLnbElement = document.getElementById('sidebar'); if (serviceLnbElement) { serviceLnbElement.insertAdjacentHTML('beforeend', newHtml); // 버튼 요소 가져오기 const buttons = serviceLnbElement.querySelectorAll('.button-fold-sidebar, .button-unfold-sidebar'); buttons.forEach(function(button) { // 버튼에 클릭 이벤트 리스너 추가 button.addEventListener('click', function () { // sidebar 상태 변경 isSidebarMinimized = !isSidebarMinimized; // max 클래스가 있으면 제거하고 min 클래스 추가 if (serviceLnbElement.classList.contains('max')) { serviceLnbElement.classList.remove('max'); serviceLnbElement.classList.add('min'); webplayer_scroll.style.left = '52px'; } // min 클래스가 있으면 제거하고 min 클래스 추가 else if (serviceLnbElement.classList.contains('min')) { serviceLnbElement.classList.remove('min'); serviceLnbElement.classList.add('max'); webplayer_scroll.style.left = '240px'; } // isSidebarMinimized 값을 저장 GM_setValue("isSidebarMinimized", isSidebarMinimized ? 1 : 0); }); }); } } function insertTopChannels(update){ let topIcon = `${getElapsedTime(broadcastStartTimeText, "HH:MM:SS")}
`; } setInterval(updateElapsedTime, 1000); } function insertRemainingBuffer(element){ const video = element; const empty_chat = document.body.querySelector('#empty_chat'); function getRemainingBufferTime(){ const buffered = video.buffered; if (buffered.length > 0) { let remainingBufferTime = buffered.end(buffered.length - 1) - video.currentTime; remainingBufferTime = remainingBufferTime.toFixed(1); remainingBufferTime = parseFloat(remainingBufferTime); remainingBufferTime = remainingBufferTime % 1 === 0 ? remainingBufferTime.toFixed(1) : remainingBufferTime; return remainingBufferTime; } } // video의 onprogress 이벤트 핸들러 video.onprogress = function() { // 남은 버퍼 시간 가져오기 let remainingBufferTime = getRemainingBufferTime(); if (empty_chat) { empty_chat.innerText = `${remainingBufferTime}s 지연됨`; } }; } // 타이머 식별자를 함수 외부에서 선언합니다. let timerId_m; function handleMuteByVisibility() { const button = document.body.querySelector("#btn_sound"); if (document.hidden) { // 탭이 비활성화됨 timerId_m = setTimeout(function(){ if (!button.classList.contains("mute")) { button.click(); //console.log("탭이 비활성화됨, 음소거"); } },1000); } else { // 탭이 활성화됨 if (typeof timerId_m !== 'undefined') { clearTimeout(timerId_m); } if (button.classList.contains("mute")) { button.click(); //console.log("탭이 활성화됨, 음소거 해제"); } } } function isVideoInPiPMode() { // 현재 비디오 요소 가져오기 const videoElement = document.body.querySelector('video'); // 비디오 요소가 존재하고, PiP 모드인지 확인 if (videoElement !== null && document.pictureInPictureElement === videoElement) { return true; } else { return false; } } function registerVisibilityChangeHandler() { document.addEventListener('visibilitychange', () => { if(!isVideoInPiPMode()){ if(isAutoChangeMuteEnabled) handleMuteByVisibility(); } }, true); } function appendPauseButton() { try { let intervalId; let elapsedTime = 0; const checkInterval = 250; function checkLiveViewStatus() { const closeStreamButton = document.body.querySelector("#closeStream"); const playerDiv = document.body.querySelector("#player"); const isPlayerPresent = playerDiv !== null; const isMouseoverClass = isPlayerPresent && playerDiv.classList.contains("mouseover"); const isTimeover = isPlayerPresent && (elapsedTime > 30); if (closeStreamButton) { closeStreamButton.remove(); } if ((!closeStreamButton && isMouseoverClass) || isTimeover) { clearInterval(intervalId); // 조건이 충족되면 interval 클리어 waitForElement('button#time_shift_play', function (elementSelector, element) { const displayStyle = window.getComputedStyle(element).getPropertyValue("display"); if (displayStyle === 'none') { // Time Shift 기능이 비활성화된 경우 const ctrlDiv = document.body.querySelector('div.ctrl'); const newCloseStreamButton = document.createElement("button"); newCloseStreamButton.setAttribute("type", "button"); newCloseStreamButton.setAttribute("id", "closeStream"); newCloseStreamButton.setAttribute("class", "pause on"); const tooltipDiv = document.createElement("div"); tooltipDiv.setAttribute("class", "tooltip"); const spanElement = document.createElement("span"); spanElement.textContent = "일시정지"; const emElement = document.createElement("em"); tooltipDiv.appendChild(spanElement); tooltipDiv.appendChild(emElement); newCloseStreamButton.appendChild(tooltipDiv); ctrlDiv.insertBefore(newCloseStreamButton, ctrlDiv.firstChild); newCloseStreamButton.addEventListener("click", function(e) { e.preventDefault(); try { if (newCloseStreamButton.classList.contains("on")) { // livePlayer 변수가 정의되어 있어야 합니다. livePlayer.closeStreamConnector(); newCloseStreamButton.classList.remove("on", "pause"); newCloseStreamButton.classList.add("off", "play"); spanElement.textContent = "재생"; } else { // livePlayer 변수가 정의되어 있어야 합니다. livePlayer._startBroad(); newCloseStreamButton.classList.remove("off", "play"); newCloseStreamButton.classList.add("on", "pause"); spanElement.textContent = "일시정지"; } } catch (error) { console.log(error); } }); } }); } // elapsedTime 증가 elapsedTime += checkInterval / 1000; // checkInterval을 초 단위로 변환하여 증가 } // setInterval을 사용해 일정 간격으로 체크 intervalId = setInterval(checkLiveViewStatus, checkInterval); } catch (error) { console.error(error); } } function detectPlayerChangeAndAppendPauseButton(){ function updateHrefIfMismatch() { // 현재 페이지의 URL에서 data-bj_id에 해당하는 부분을 추출 const currentUrl = window.location.href; const urlBjId = currentUrl.split('/')[3]; // 'https://play.sooplive.co.kr/${data-bj_id}/345345345'에서 data-bj_id 부분을 추출 // 하위 요소의 a 태그를 선택 const anchorTag = document.body.querySelector('#bjThumbnail > a'); const anchorHref = anchorTag.getAttribute('href'); const anchorurlBjId = anchorHref.split('/')[3]; // 'https://play.sooplive.co.kr/${data-bj_id}/345345345'에서 data-bj_id 부분을 추출 // href가 다를 경우 하위 a 태그의 href를 상위 href로 대체 if (urlBjId !== anchorurlBjId) { anchorTag.setAttribute('href', `https://ch.sooplive.co.kr/${urlBjId}`); } } function updateBjIdIfMismatch() { // 현재 페이지의 URL에서 data-bj_id에 해당하는 부분을 추출 const currentUrl = window.location.href; const urlBjId = currentUrl.split('/')[3]; // 'https://play.sooplive.co.kr/${data-bj_id}/345345345'에서 data-bj_id 부분을 추출 // #infoNickName 요소의 data-bj_id 값을 가져옴 const infoNickName = document.querySelector('#infoNickName'); const dataBjId = infoNickName.getAttribute('data-bj_id'); // data-bj_id 값이 URL에서 추출한 값과 다를 경우 대체 if (dataBjId !== urlBjId) { infoNickName.setAttribute('data-bj_id', urlBjId); } } appendPauseButton(); // 대상 요소를 선택합니다. const targetNode = document.body.querySelector('#infoNickName'); // 변화를 감지할 MutationObserver를 생성합니다. const observer = new MutationObserver(function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { appendPauseButton(); emptyViewStreamer(); updateHrefIfMismatch(); updateBjIdIfMismatch(); } } }); // MutationObserver를 설정하고 변화를 감지할 대상과 감시할 옵션을 지정합니다. observer.observe(targetNode, { childList: true, subtree: true }); } function emptyViewStreamer(){ const viewStreamer = document.getElementById('view_streamer'); if (viewStreamer) { viewStreamer.innerHTML = ''; } } function setWidthNickname(wpx){ GM_addStyle(` .starting-line .chatting-list-item .message-container .username { width: ${wpx}px !important; } `) } function hideBadges() { isHideSupporterBadgeEnabled = GM_getValue("isHideSupporterBadgeEnabled"); isHideFanBadgeEnabled = GM_getValue("isHideFanBadgeEnabled"); isHideSubBadgeEnabled = GM_getValue("isHideSubBadgeEnabled"); isHideVIPBadgeEnabled = GM_getValue("isHideVIPBadgeEnabled"); isHideManagerBadgeEnabled = GM_getValue("isHideManagerBadgeEnabled"); isHideStreamerBadgeEnabled = GM_getValue("isHideStreamerBadgeEnabled"); if(isHideSupporterBadgeEnabled + isHideFanBadgeEnabled + isHideSubBadgeEnabled + isHideVIPBadgeEnabled + isHideManagerBadgeEnabled + isHideStreamerBadgeEnabled === 0){ return; } const elements = document.querySelectorAll('[class^="grade-badge-"]:not(.done)'); elements.forEach(function(element) { const className = element.className.split("grade-badge-")[1].split(" ")[0]; switch(true) { case className==="fan" && !!isHideFanBadgeEnabled: element.parentNode.removeChild(element); break; case className==="vip" && !!isHideVIPBadgeEnabled: element.parentNode.removeChild(element) break; case className==="manager" && !!isHideManagerBadgeEnabled: element.parentNode.removeChild(element); break; case className==="streamer" && !!isHideStreamerBadgeEnabled: element.parentNode.removeChild(element); break; case className==="support" && !!isHideSupporterBadgeEnabled: element.parentNode.removeChild(element); break; default: element.classList.add('done'); break; } }); if(isHideSubBadgeEnabled){ let thumbSpan = ''; if (CURRENT_URL.startsWith("https://play.sooplive.co.kr/")) { thumbSpan = document.querySelectorAll('#chat_area div.username > button > span.thumb'); } else if (CURRENT_URL.startsWith("https://vod.sooplive.co.kr/")) { thumbSpan = document.querySelectorAll('#chatMemo div.username > button > span.thumb'); } thumbSpan.forEach(function(element) { element.parentNode.removeChild(element); }); } } function observeChat(elementSelector,elem){ // 페이지 변경 시 이미지 감지 및 숨기기 const observer = new MutationObserver(function(mutations) { for (const mutation of mutations) { if(isBlockWordsEnabled) deleteMessages(); hideBadges(); } }); const config = { childList: true, subtree: true }; observer.observe(elem, config); } function deleteMessages() { const messages = document.body.querySelectorAll('div.message-text > p.msg:not(.done)'); const rw = registeredWords ? registeredWords.split(',') : []; for (const message of messages) { const messageText = message.textContent.trim(); const emoticons = message.querySelectorAll('img.emoticon'); const shouldRemove = rw.some(word => { const trimmedWord = word.trim(); // 공백인 경우를 처리 if (trimmedWord.length === 0) { return false; // 빈 문자열인 경우 false 반환 } const wordToCheck = word.trim().startsWith("e:") ? word.trim().slice(2) : word.trim(); return (word.trim().startsWith("e:") && messageText === wordToCheck) || (!word.trim().startsWith("e:") && messageText.includes(wordToCheck)); }); if (shouldRemove) { message.closest('.chatting-list-item').classList.add('filtered-message'); message.closest('.chatting-list-item').remove(); } else { message.classList.add('done'); } } } function autoClaimGem() { const element = document.querySelector('#actionbox > div.ic_gem'); // 요소가 존재하고, display 속성이 'none'이 아닌 경우 클릭 if (element && getComputedStyle(element).display !== 'none') { element.click(); } } // 비디오 재생 건너뛰기 및 입력란 확인 함수 function videoSkipHandler(e) { const activeElement = document.activeElement; const tagName = activeElement.tagName.toLowerCase(); // 입력란 활성화 여부 체크 const isInputActive = (tagName === 'input') || (tagName === 'textarea') || (activeElement.id === 'write_area') || (activeElement.contentEditable === 'true'); // 입력란이 활성화되어 있지 않은 경우 비디오 제어 if (!isInputActive) { const video = document.querySelector('video'); if (video) { switch (e.code) { case 'ArrowRight': // 오른쪽 방향키: 동영상을 1초 앞으로 이동 video.currentTime += 1; break; case 'ArrowLeft': // 왼쪽 방향키: 동영상을 1초 뒤로 이동 video.currentTime -= 1; break; } } } } function homePageCurrentTab(){ waitForElement('#logo > a', function (elementSelector, element) { element.removeAttribute("target"); }); } function useBottomChat(){ // 해상도에 따라 bottomChat 클래스를 추가하거나 제거하는 함수 function toggleBottomChat() { const screenHeight = window.innerHeight; const screenWidth = window.innerWidth; const playerBody = document.body; const isPortrait = screenHeight+50 > screenWidth; if (screenWidth <= 1350 && isPortrait) { playerBody.classList.add('bottomChat'); } else { playerBody.classList.remove('bottomChat'); } } // 윈도우 리사이즈 이벤트를 감지하여 toggleBottomChat 함수를 호출합니다. window.addEventListener('resize', toggleBottomChat); // 페이지 로드 시 한 번 실행하여 초기 설정을 수행합니다. toggleBottomChat(); } //=================================플레이어 페이지 함수 끝=================================// //============================ 메인 페이지 실행 ============================// if (CURRENT_URL.startsWith("https://www.sooplive.co.kr")) { if (isCustomSidebarEnabled) document.body.classList.add('customSidebar'); GM_addStyle(mainPageCommonStyles); observeDarkAttributeChange((darkValue) => { if(darkValue){ GM_addStyle(mainPageDarkmodeStyles); } else { GM_addStyle(mainPageWhitemodeStyles); } }); waitForElement('#serviceLnb', function (elementSelector, element) { if (isCustomSidebarEnabled) makeTopNavbarAndSidebar("main"); runCommonFunctions(); if (isCustomSidebarEnabled) element.remove(); }); if (isRemoveCarouselEnabled) { GM_addStyle(` div[class^="player_player_wrap"] { display: none !important; } `); removeCarousel(); window.addEventListener('popstate', function(event) { removeCarousel(); }); } } //============================ 플레이어 페이지 실행 ============================// if (CURRENT_URL.startsWith("https://play.sooplive.co.kr")) { // Embed 페이지에서는 실행하지 않음 const pattern = /^https:\/\/play.sooplive.co.kr\/.*\/.*\/embed(\?.*)?$/; if (pattern.test(CURRENT_URL) || CURRENT_URL.includes("vtype=chat")) { return; } if (isCustomSidebarEnabled) document.body.classList.add('customSidebar'); GM_addStyle(playerCommonStyles); observeDarkAttributeChange((darkValue) => { if(darkValue){ GM_addStyle(darkModePlayerStyles); } else { GM_addStyle(whiteModePlayerStyles); } }); if (isCustomSidebarEnabled) { makeTopNavbarAndSidebar("player"); insertFoldButton(); detectScreenMode(); detectFullscreenmode(); if(showSidebarOnScreenMode) showSidebarOnMouseOver(); } if(isBottomChatEnabled) useBottomChat(); if(isMakePauseButtonEnabled) detectPlayerChangeAndAppendPauseButton(); if(isMakeSharpModeShortcutEnabled) toggleSharpModeShortcut(); if(isMakeLowLatencyShortcutEnabled) toggleLowLatencyShortcut(); if(isRemainingBufferTimeEnabled){ waitForElement('#livePlayer', function (elementSelector, element) { insertRemainingBuffer(element); }); } if(isAutoClaimGemEnabled){ setInterval(autoClaimGem, 30000); } if(isVideoSkipHandlerEnabled){ waitForElement('#livePlayer', function (elementSelector, element) { window.addEventListener('keydown', videoSkipHandler); }); } registerVisibilityChangeHandler(); checkPlayerPageHeaderAd(); if(!isOpenNewtabEnabled){ homePageCurrentTab(); } runCommonFunctions(); // LIVE 채팅창 waitForElement('#chat_area', function (elementSelector, element) { observeChat(elementSelector,element); }); } //============================ VOD 페이지 실행 ============================// if (CURRENT_URL.startsWith("https://vod.sooplive.co.kr/player/")) { // vodCore 변수가 선언될 때까지 대기하는 함수 function waitForVodCore() { // vodCore 변수가 선언될 때까지 주기적으로 확인 const checkVodCore = setInterval(function() { if (vodCore && vodCore.playerController && vodCore.playerController._currentMediaInfo && vodCore.playerController._currentMediaInfo.name) { // vodCore 및 playerController가 정의되어 있는지 확인 clearInterval(checkVodCore); // setInterval 정지 checkMediaInfo(vodCore.playerController._currentMediaInfo.name); // vodCore 변수가 정의되면 미디어 정보 확인 함수 호출 } }, 500); // 500ms 주기로 확인 } // 미디어 정보 확인 함수 function checkMediaInfo(mediaName) { if (mediaName !== 'original') { // 원본 화질로 설정되지 않은 경우 waitForElement('#player', function (elementSelector, element) { element.className = 'video mouseover ctrl_output'; setTimeout(function () { waitForElement('#player.video.mouseover.ctrl_output', function (elementSelector, element) { setTimeout(function () { waitForElement('#player > div.player_ctrlBox > div.ctrlBox > div.right_ctrl .setting_box > button.btn_setting', function (elementSelector, element) { element.click(); setTimeout(function () { waitForElement('#player > div.player_ctrlBox > div.ctrlBox > div.right_ctrl .setting_box.on .setting_list', function (elementSelector, element) { const spanElement = Array.from(document.querySelectorAll('span')).find(el => el.textContent.includes("화질 변경")); const buttonElement = spanElement.closest('button'); buttonElement.click(); setTimeout(function () { waitForElement('#player > div.player_ctrlBox > div.ctrlBox > div.right_ctrl .setting_box .setting_list_subLayer ul > li:nth-child(2) > button', function (elementSelector, element) { element.click(); element.className = 'video'; }); }, 150); // 마지막 지연 }); }, 150); // 두 번째 지연 }); }, 150); // 첫 번째 지연 }); }, 150); // 초기 지연 }); } } if(isSelectBestQualityEnabled){ waitForVodCore(); } GM_addStyle(CommonStyles); // VOD 채팅창 waitForElement('#webplayer_contents', function (elementSelector, element) { observeChat(elementSelector,element); }); waitForElement('div.serviceUtil', function (elementSelector, element) { addModalSettings(); }); } })();