// ==UserScript== // @name SOOP (숲) - 사이드바 UI 변경 // @name:ko SOOP (숲) - 사이드바 UI 변경 // @namespace https://greasyfork.org/ko/scripts/484713 // @version 20250429 // @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 unsafeWindow // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @connect sooplive.co.kr // @connect naver.com // @run-at document-end // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/484713/SOOP%20%28%EC%88%B2%29%20-%20%EC%82%AC%EC%9D%B4%EB%93%9C%EB%B0%94%20UI%20%EB%B3%80%EA%B2%BD.user.js // @updateURL https://update.greasyfork.icu/scripts/484713/SOOP%20%28%EC%88%B2%29%20-%20%EC%82%AC%EC%9D%B4%EB%93%9C%EB%B0%94%20UI%20%EB%B3%80%EA%B2%BD.meta.js // ==/UserScript== (function() { 'use strict'; const NEW_UPDATE_DATE = 20250427; 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 wordMenuIds = {}; 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 blockedWords = GM_getValue('blockedWords', []); // 방송 목록 차단 단어 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 showSidebarOnScreenModeAlways = GM_getValue("showSidebarOnScreenModeAlways", 0); 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); let isDocumentTitleUpdateEnabled = GM_getValue("isDocumentTitleUpdateEnabled", 1); let isRemoveRedistributionTagEnabled = GM_getValue("isRemoveRedistributionTagEnabled", 1); let isRemoveWatchLaterButtonEnabled = GM_getValue("isRemoveWatchLaterButtonEnabled", 1); let isRemoveBroadStartTimeTagEnabled = GM_getValue("isRemoveBroadStartTimeTagEnabled", 0); let isBroadTitleTextEllipsisEnabled = GM_getValue("isBroadTitleTextEllipsisEnabled", 0); let isUnlockCopyPasteEnabled = GM_getValue("isUnlockCopyPasteEnabled", 0); let isAlignNicknameRightEnabled = GM_getValue("isAlignNicknameRightEnabled", 0); let isPreviewModalEnabled = GM_getValue("isPreviewModalEnabled", 1); let isReplaceEmptyThumbnailEnabled = GM_getValue("isReplaceEmptyThumbnailEnabled", 1); let isAutoScreenModeEnabled = GM_getValue("isAutoScreenModeEnabled", 0); let isAdjustDelayNoGridEnabled = GM_getValue("isAdjustDelayNoGridEnabled", 0); let ishideButtonsAboveChatInputEnabled = GM_getValue("ishideButtonsAboveChatInputEnabled", 0); let isExpandVODChatEnabled = GM_getValue("isExpandVODChatEnabled", 0); let isAutoExpandVODChatEnabled = GM_getValue("isAutoExpandVODChatEnabled", 0); let isExpandLiveChatEnabled = GM_getValue("isExpandLiveChatEnabled", 0); let isAutoExpandLiveChatEnabled = GM_getValue("isAutoExpandLiveChatEnabled", 0); let isOpenExternalPlayerEnabled = GM_getValue("isOpenExternalPlayerEnabled", 0); let isOpenExternalPlayerFromSidebarEnabled = GM_getValue("isOpenExternalPlayerFromSidebarEnabled", 0); let isRemoveShadowsFromCatchEnabled = GM_getValue("isRemoveShadowsFromCatchEnabled", 0); let isChzzkTopChannelsEnabled = GM_getValue("isChzzkTopChannelsEnabled", 0); let isChzzkFollowChannelsEnabled = GM_getValue("isChzzkFollowChannelsEnabled", 0); let isAdaptiveSpeedControlEnabled = GM_getValue("isAdaptiveSpeedControlEnabled", 0); let isShowDeletedMessagesEnabled = GM_getValue("isShowDeletedMessagesEnabled", 0); const WEB_PLAYER_SCROLL_LEFT = isSidebarMinimized ? 52 : 240; function loadHlsScript() { // hls.js 동적 로드 const hlsScript = document.createElement('script'); hlsScript.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest'; hlsScript.onload = function() { console.log('hls.js가 성공적으로 로드되었습니다.'); }; hlsScript.onerror = function() { console.error('hls.js 로드 중 오류가 발생했습니다.'); }; document.head.appendChild(hlsScript); } 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); } const getHiddenbjList = () => { return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: "https://live.sooplive.co.kr/api/hiddenbj/hiddenbjController.php", onload: response => { try { const data = JSON.parse(response.responseText); response.status === 200 && data.RESULT === 1 ? resolve(data.DATA) : resolve([]); // 실패 시 빈 배열 반환 } catch (error) { resolve([]); // 파싱 오류 시 빈 배열 반환 } }, onerror: () => resolve([]) // 요청 오류 시 빈 배열 반환 }); }); }; const getStationFeed = () => { return new Promise((resolve) => { if (!isChannelFeedEnabled) { resolve([]); // 채널 피드가 비활성화된 경우 빈 배열 반환 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: response => { try { const responseData = JSON.parse(response.responseText); resolve(responseData.data || []); } catch (error) { console.error("Error parsing response data:", error); resolve([]); } }, onerror: error => { console.error("Error while loading data:", error); resolve([]); } }); }); }; function loadCategoryData() { // 현재 시간 기록 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."); } } 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; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { if (isChangeFontEnabled) applyFontStyles(); loadCategoryData(); }); } else { if (isChangeFontEnabled) applyFontStyles(); loadCategoryData(); } const CommonStyles = ` .screen_mode #player, .fullScreen_mode #player, .screen_mode #videoLayerCover, .fullScreen_mode #videoLayerCover { cursor: default !important; } .screen_mode #player.hide-cursor, .fullScreen_mode #player.hide-cursor, .screen_mode #videoLayerCover.hide-cursor, .fullScreen_mode #videoLayerCover.hide-cursor { cursor: none !important; } .customSidebar #serviceLnb { display: none !important; } .left_navbar { display: flex; align-items: center; justify-content: flex-end; position: fixed; flex-direction: row-reverse; top: 0px; left: 136px; z-index: 9999; } .left_navbar button.left_nav_button { position: relative; width: 68px; height: 64px; padding: 0; border: 0; cursor: pointer; z-index: 3001; font-size: 1.25em !important; font-weight: 600; } @media (max-width: 1280px) { .left_navbar { left: 130px !important; } .left_nav_button { width: 58px !important; font-size: 1.2em; } } @media (max-width: 1100px) { .left_navbar { left: 124px !important; } .left_nav_button { width: 46px !important; font-size: 1.15em; } } #sidebar { top: 64px; display: flex !important; flex-direction: column !important; } #sidebar .top-section.follow { order: 1; } #sidebar .users-section.follow { order: 2; } #sidebar .top-section.myplus { order: 3; } #sidebar .users-section.myplus { order: 4; } #sidebar .top-section.myplusvod { order: 5; } #sidebar .users-section.myplusvod { order: 6; } #sidebar .top-section.top { order: 7; } #sidebar .users-section.top { order: 8; } .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; } #myModal.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; } #myModal .modal-content { background-color: #fefefe; margin: 15% auto; padding: 20px; border: 1px solid #888; border-radius: 10px; width: clamp(400px, 80%, 550px); } #myModal .myModalClose { color: #aaa; float: right; font-size: 36px; font-weight: bold; margin-top: -12px; } #myModal .myModalClose:hover, #myModal .myModalClose:focus { color: black; text-decoration: none; cursor: pointer; } #myModal .option { margin-bottom: 10px; display: flex; align-items: center; } #myModal .option label { margin-right: 10px; font-size: 15px; } #myModal .switch { position: relative; display: inline-block; width: 60px; height: 34px; transform: scale(0.9); /* 축소 */ } #myModal .switch input { display: none; } #myModal .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } #myModal .slider:before { position: absolute; content: ""; height: 26px; width: 26px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } #myModal .slider.round { border-radius: 34px; min-width: 60px; } #myModal .slider.round:before { border-radius: 50%; } #myModal input:checked + .slider { background-color: #2196F3; } #myModal input:focus + .slider { box-shadow: 0 0 1px #2196F3; } #myModal input:checked + .slider:before { transform: translateX(26px); } #myModal #range { width: 100%; } #myModal #rangeValue { display: inline-block; margin-left: 10px; } #myModal .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-top:16px; padding-bottom:12px; } #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 { max-width: 80px !important; font-size: 14px !important; line-height: 24px !important; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } #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; } html[dark="true"] .users-section .user.user-offline span { filter: grayscale(1) brightness(0.8); /* 다크모드: 완전 흑백과 약간 어둡게 */ } html .users-section .user.user-offline span { opacity: 0.7; /* 밝은 모드: 투명하게 */ } `; const mainPageCommonStyles = ` ._moreDot_layer button { text-align: left; } /*----- preview-modal 시작 -----*/ .preview-modal { display: none; position: fixed; z-index: 10000; left: 0; top: 0; width: 100%; height: 100%; overflow: hidden; background-color: rgba(0, 0, 0, 0.9); backdrop-filter: blur(5px); } .preview-modal-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 0; width: 80%; max-width: 800px; max-height: 800px; border-radius: 10px; border: 1px solid #cccccc52; overflow: hidden; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.7); pointer-events: auto; } .preview-modal .preview-close { position: absolute; top: 10px; right: 15px; color: #fff; font-size: 30px; font-weight: bold; cursor: pointer; transition: color 0.3s ease; z-index: 10; } .preview-modal .preview-close:hover, .preview-modal .preview-close:focus { color: #e50914; } .preview-modal .thumbnail-container { position: relative; width: 100%; height: 450px; background-color: black; display: flex; justify-content: center; align-items: center; } .preview-modal .thumbnail-container img { max-width: 100%; max-height: 100%; object-fit: cover; } .preview-modal .preview-modal-content video { width: clamp(100%, 50vw, 800px); height: 449px; display: none; } .preview-modal .info { color: white; text-align: left; padding: 28px; background-color: rgba(0, 0, 0, 0.65); } .preview-modal .streamer-name { font-size: 50px; font-weight: bold; letter-spacing: -2px; } .preview-modal .video-title { font-size: 20px; margin: 20px 0 30px 0; } .preview-modal .tags { display: flex; justify-content: left; flex-wrap: wrap; flex-direction: row; margin-left: -3px; } .preview-modal .tags a { margin: 5px; color: white; text-decoration: none; border: 1px solid #fff; padding: 5px 10px; border-radius: 5px; transition: background-color 0.3s; } .preview-modal .tags a:hover { background-color: rgba(255, 255, 255, 0.2); } .preview-modal .start-button { background-color: #2d6bffba; color: white; padding: 12px 20px; border: none; border-radius: 5px; font-size: 22px; cursor: pointer; display: inline-block; /* inline-block으로 변경 */ width: auto; /* 너비는 자동으로 */ text-align: center; text-decoration: none; transition: background-color 0.3s; } .preview-modal .start-button:hover { background-color: #2d6bff8f; } /*----- preview-modal 끝 -----*/ .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; } body.customSidebar main { padding-left: 238px !important; } body.customSidebar .catch_webplayer_wrap { margin-left: 24px !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 #player, .customSidebar #webplayer #webplayer_contents #player_area .float_box, .customSidebar #webplayer #webplayer_contents #player_area { min-width: 180px !important; } .customSidebar.screen_mode #webplayer, .customSidebar.screen_mode #sidebar { 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; } .customSidebar.top_hide #webplayer_contents, .customSidebar.top_hide #sidebar { top: 0 !important; margin-top: 0 !important; min-height: 100vh !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; } .customSidebar.screen_mode #webplayer #webplayer_contents, .customSidebar.fullScreen_mode #webplayer #webplayer_contents { top: 0 !important; left: 0 !important; width: 100vw; height: 100vh !important; margin: 0 !important; } .customSidebar.screen_mode #sidebar{ display: none !important; top: 0 !important; } .customSidebar.screen_mode #sidebar .button-fold-sidebar, .customSidebar.screen_mode #sidebar .button-unfold-sidebar { display: none !important; } .customSidebar.screen_mode.showSidebar #sidebar{ display: flex !important; } .customSidebar.screen_mode #webplayer_contents, .customSidebar.fullScreen_mode #webplayer_contents{ width: 100vw !important } .customSidebar.screen_mode.showSidebar:has(#sidebar.min) #webplayer_contents { width: calc(100vw - 52px) !important } .customSidebar.screen_mode.showSidebar:has(#sidebar.max) #webplayer_contents { width: calc(100vw - 240px) !important } .screen_mode.bottomChat #webplayer #webplayer_contents { top: 0 !important; margin: 0 !important; } .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.showSidebar:has(#sidebar.min) #webplayer #webplayer_contents .wrapping.side { width: 100% !important; max-height: calc(100vh - ((100vw - 52px) * 9 / 16)) !important; } .screen_mode.bottomChat.showSidebar:has(#sidebar.max) #webplayer #webplayer_contents .wrapping.side { width: 100% !important; max-height: calc(100vh - ((100vw - 240px) * 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; min-height: 10vh !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; } .customSidebar #player { max-height: 100vh !important; } `; 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; } `; //======================================공용 함수======================================// const hideCursor = (element) => { let hideCursorTimeout; element.classList.add('hide-cursor'); element.addEventListener('mousemove', () => { element.classList.remove('hide-cursor'); // 마우스가 움직이면 다시 보여줌 clearTimeout(hideCursorTimeout); // 기존 타이머 초기화 hideCursorTimeout = setTimeout(() => { element.classList.add('hide-cursor'); // 1초 후 커서 숨김 }, 1000); }); } const checkIfTimeover = (timestamp) => { const now = Date.now(); const inputTime = timestamp * 1000; // 초 단위 타임스탬프를 밀리초로 변환 // 24시간(1일) = 86400000 밀리초 return (now - inputTime) > 86400000; }; const 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}년 전`; } if (days > 30) { const months = Math.floor(days / 30); return `${months}개월 전`; } if (days > 0) return `${days}일 전`; if (hours > 0) return `${hours}시간 전`; if (minutes > 0) return `${minutes}분 전`; return `${seconds}초 전`; }; const waitForElement = (elementSelector, callBack, maxAttempts = 200, interval = 200) => { let attempts = 0; const checkElement = () => { const element = document.body.querySelector(elementSelector); if (element) { callBack(elementSelector, element); } else if (attempts < maxAttempts) { attempts++; setTimeout(checkElement, interval); // 반복 검사 } else { console.warn(`Reached maximum attempts. ${elementSelector} not found.`); } }; checkElement(); // 첫 번째 검사 호출 }; const waitForElementAsync = (elementSelector, maxAttempts = 200, attemptInterval = 200) => { return new Promise((resolve, reject) => { let attempts = 0; const checkElement = () => { const element = document.body.querySelector(elementSelector); if (element) { resolve(element); // 요소를 찾으면 resolve } else if (attempts < maxAttempts) { attempts += 1; // attempts 증가 setTimeout(checkElement, attemptInterval); // 반복 검사 } else { reject(`Reached maximum attempts. ${elementSelector} not found.`); // 최대 시도 횟수 초과 } }; checkElement(); // 첫 번째 검사 호출 }); }; const updateElementWithContent = (targetElement, newContent) => { // DocumentFragment 생성 const createFragment = (content) => { const fragment = document.createDocumentFragment(); const tempDiv = document.createElement('div'); tempDiv.innerHTML = content; // tempDiv의 자식 요소를 fragment에 추가 while (tempDiv.firstChild) { fragment.appendChild(tempDiv.firstChild); } return fragment; }; // 기존 내용을 지우고 DocumentFragment를 적용 const applyFragment = (fragment) => { targetElement.innerHTML = ''; // 기존 내용을 모두 지움 targetElement.appendChild(fragment); // 새로운 내용 추가 }; // DocumentFragment 생성 후 적용 applyFragment(createFragment(newContent)); }; const manageRedDot = () => { const RED_DOT_CLASS = 'red-dot'; const style = document.createElement('style'); style.textContent = ` .${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'); // 빨간 점 추가 함수 const 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); }; // 빨간 점 제거 함수 const 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(); }); }; const addNumberSeparator = (number) => { number = Number(number); // 숫자가 10,000 이상일 때 if (number >= 10000) { const displayNumber = (number / 10000).toFixed(1); return displayNumber.endsWith('.0') ? displayNumber.slice(0, -2) + '만' : displayNumber + '만'; } return number.toLocaleString(); }; const addNumberSeparatorAll = (number) => { number = Number(number); // 숫자가 10,000 이상일 때 if (number >= 10000) { const displayNumber = (number / 10000).toFixed(1); return displayNumber.endsWith('.0') ? displayNumber.slice(0, -2) + '만' : displayNumber + '만'; } // 숫자가 1,000 이상일 때 else if (number >= 1000) { const displayNumber = (number / 1000).toFixed(1); return displayNumber.endsWith('.0') ? displayNumber.slice(0, -2) + '천' : displayNumber + '천'; } // 기본적으로 쉼표 추가 return number.toLocaleString(); }; const getCategoryName = (targetCateNo) => { const searchCategory = (categories) => { for (const category of categories) { if (category.cate_no === targetCateNo) { return category.cate_name; } if (category.child?.length) { const result = searchCategory(category.child); if (result) return result; } } return targetCateNo === "ADULT_BROAD_CATE" ? "연령제한" : null; }; return searchCategory(savedCategory.CHANNEL.BROAD_CATEGORY); }; const getCategoryNo = (targetCateName) => { const searchCategory = (categories) => { for (const category of categories) { if (category.cate_name === targetCateName) { return category.cate_no; } if (category.child?.length) { const result = searchCategory(category.child); if (result) return result; } } return targetCateName === "연령제한" ? "ADULT_BROAD_CATE" : null; }; return searchCategory(savedCategory.CHANNEL.BROAD_CATEGORY); }; // 차단 목록을 저장합니다. function saveBlockedUsers() { GM_setValue('blockedUsers', blockedUsers); } // 사용자를 차단 목록에 추가합니다. function blockUser(userName, userId) { // 이미 차단된 사용자인지 확인 if (!isUserBlocked(userId)) { blockedUsers.push({ userName, userId }); saveBlockedUsers(); alert(`사용자 ${userName}(${userId})를 차단했습니다.\n차단 해제 메뉴는 템퍼몽키 아이콘을 누르면 있습니다.`); 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 saveBlockedWords() { GM_setValue('blockedWords', blockedWords); } // 단어를 차단 목록에 추가합니다. function blockWord(word) { // 단어의 양쪽 공백 제거 word = word.trim(); // 단어가 두 글자 이상인지 확인 if (word.length < 2) { alert("단어는 두 글자 이상이어야 합니다."); return; } // 이미 차단된 단어인지 확인 if (!isWordBlocked(word)) { blockedWords.push(word); saveBlockedWords(); alert(`단어 "${word}"를 차단했습니다.`); registerWordUnblockMenu(word); } else { alert(`단어 "${word}"는 이미 차단되어 있습니다.`); } } // 함수: 단어 차단 해제 function unblockWord(word) { // 차단된 단어 목록에서 해당 단어 찾기 let unblockedWord = blockedWords.find(blockedWord => blockedWord === word); // 단어를 찾았을 때만 차단 해제 및 메뉴 삭제 수행 if (unblockedWord) { // 차단된 단어 목록에서 해당 단어 제거 blockedWords = blockedWords.filter(blockedWord => blockedWord !== word); // 변경된 목록을 저장 saveBlockedWords(); alert(`단어 "${word}"의 차단이 해제되었습니다.`); unregisterWordUnblockMenu(word); } } // 단어가 이미 차단되어 있는지 확인합니다. function isWordBlocked(word) { const lowerCaseWord = word.toLowerCase(); return blockedWords.map(word => word.toLowerCase()).includes(lowerCaseWord); } // 함수: 동적으로 단어 차단 해제 메뉴 등록 function registerWordUnblockMenu(word) { // GM_registerMenuCommand로 단어 차단 해제 메뉴를 등록하고 메뉴 ID를 기록 let menuId = GM_registerMenuCommand(`💔 단어 차단 해제 - ${word}`, function() { unblockWord(word); }); // 메뉴 ID를 기록 wordMenuIds[word] = menuId; } // 함수: 동적으로 단어 차단 해제 메뉴 삭제 function unregisterWordUnblockMenu(word) { // word를 기반으로 저장된 메뉴 ID를 가져와서 삭제 let menuId = wordMenuIds[word]; if (menuId) { GM_unregisterMenuCommand(menuId); delete wordMenuIds[word]; // 삭제된 메뉴 ID를 객체에서도 제거 } } function registerMenuBlockingWord() { // GM 메뉴에 단어 차단 등록 메뉴를 추가합니다. GM_registerMenuCommand('단어 등록 | 방제에 포함시 차단', function() { // 사용자에게 차단할 단어 입력을 요청 let word = prompt('차단할 단어 (2자 이상): '); // 입력한 단어가 있을 때만 처리 if (word) { blockWord(word); } }); } const desc_order = (selector) => { // Get the container element const container = document.body.querySelector(selector); // Get all user elements const userElements = container.children; // Directly get the children for performance // Create arrays for each category const categories = [[], [], [], [], []]; // Categorize users for (let i = 0; i < userElements.length; i++) { const user = userElements[i]; 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) { categories[0].push(user); // category1 } else if (isPin) { categories[1].push(user); // category2 } else if (isMobilePush && !isOffline) { categories[2].push(user); // category3 } else if (!isMobilePush && !isOffline) { categories[3].push(user); // category4 } else { categories[4].push(user); // category5 } } // Sort each category by watchers categories.forEach(category => category.sort(compareWatchers)); // Clear container and append sorted elements container.innerHTML = ''; // Clear container // Use DocumentFragment for improved performance when appending const fragment = document.createDocumentFragment(); categories.forEach(category => { category.forEach(user => fragment.appendChild(user)); }); container.appendChild(fragment); // Append all sorted users at once } const compareWatchers = (a, b) => { // Get watchers data only once for each element const watchersA = a.dataset.watchers ? +a.dataset.watchers : 0; // Use dataset for better performance const watchersB = b.dataset.watchers ? +b.dataset.watchers : 0; // Use dataset for better performance return watchersB - watchersA; // Sort by watchers } const makeTopNavbarAndSidebar = (page) => { // .left_navbar를 찾거나 생성 let leftNavbar = document.body.querySelector('.left_navbar'); if (!leftNavbar) { leftNavbar = document.createElement('div'); leftNavbar.className = 'left_navbar'; // 페이지의 적절한 위치에 추가 waitForElement('#serviceHeader', function (elementSelector, element) { element.prepend(leftNavbar); }); } const buttonData = [ { href: 'https://www.sooplive.co.kr/live/all', text: 'LIVE', onClickTarget: '#live > a' }, { href: 'https://www.sooplive.co.kr/my/favorite', text: 'MY', onClickTarget: '#my > a' }, { href: 'https://www.sooplive.co.kr/directory/category', text: '탐색', onClickTarget: '#cate > a' }, { href: 'https://vod.sooplive.co.kr/player/catch', text: '캐치', onClickTarget: '#catch > a' } ]; // 버튼을 미리 만들어 DocumentFragment에 추가 const buttonFragment = document.createDocumentFragment(); buttonData.reverse().forEach(data => { const newButton = document.createElement('a'); newButton.innerHTML = ``; const isTargetUrl = CURRENT_URL.startsWith("https://www.sooplive.co.kr"); // 이벤트 리스너 함수 정의 const triggerClick = (event) => { event.preventDefault(); const targetElement = isTargetUrl && data.onClickTarget ? document.querySelector(data.onClickTarget) : null; if (targetElement) { targetElement.click(); // 타겟 요소 클릭 } else { console.warn("타겟 요소를 찾을 수 없음:", data.onClickTarget); } }; // MutationObserver 설정: 타겟 요소가 로드될 때까지 기다림 if (isTargetUrl && data.onClickTarget) { const observer = new MutationObserver((mutations, observer) => { const targetElement = document.querySelector(data.onClickTarget); if (targetElement) { observer.disconnect(); // 요소가 확인되면 Observer 중지 newButton.addEventListener('click', triggerClick); } }); observer.observe(document.body, { childList: true, subtree: true }); } else { // 기본 링크 설정 newButton.href = data.href; newButton.target = isOpenNewtabEnabled ? "_blank" : "_self"; } buttonFragment.appendChild(newButton); }); leftNavbar.appendChild(buttonFragment); // 한 번에 추가 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); } document.body.appendChild(tooltipContainer); } if (page === "player") { const sidebarHtml = ` `; document.body.insertAdjacentHTML('beforeend', sidebarHtml); document.body.appendChild(tooltipContainer); } } const createUserElementChzzk = (channel, is_mobile_push) => { const { liveTitle: liveTitle, liveImageUrl: liveImageUrl, concurrentUserCount: concurrentUserCount, openDate: openDate, liveCategoryValue: liveCategoryValue, liveCategory: liveCategory, channel: channelInfo, liveInfo: liveInfo } = channel; const userId = channelInfo.channelId; const playerLink = `https://chzzk.naver.com/live/${channelInfo.channelId}`; const broadThumbnail = liveImageUrl ? liveImageUrl.split('{type}').join('360') : "data:image/jpeg;base64,/9j/4AAQSkZJRgABAgEASABIAAD/2wBDAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARCAEOAeADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+7OuwkKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA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AUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFABQAUAFAA/9k="; const profileImg = channelInfo?.channelImageUrl; const channelPage = 'https://chzzk.naver.com/'+userId; const channelName = channelInfo?.channelName; const userElement = document.createElement('a'); userElement.classList.add('user'); if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); userElement.setAttribute('href', playerLink); if (isOpenNewtabEnabled) { userElement.setAttribute('target', '_blank'); } else { userElement.setAttribute('target', '_self'); } userElement.setAttribute('data-watchers', concurrentUserCount ?? liveInfo.concurrentUserCount); userElement.setAttribute('broad_thumbnail', broadThumbnail); userElement.setAttribute('tooltip', liveTitle ?? liveInfo.liveTitle); userElement.setAttribute('user_id', userId); userElement.setAttribute('broad_start', openDate ?? 'NotAvailable'); userElement.setAttribute('is_mobile_push', is_mobile_push === "Y" ? 'Y' : 'N'); userElement.setAttribute('is_pin', 'N'); const profilePicture = document.createElement('img'); profilePicture.src = profileImg; const profileClickHandler = ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { if(event.ctrlKey) { window.open('${playerLink}', '_blank'); return; } location.href = '${playerLink}'; } else { window.open('${channelPage}', '_blank'); } `; // 프로필 클릭 & 새 탭 열기: 최소화 시 생방송, 최대화 시 방송국 const profileClickHandlerForNewtab = ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { window.open('${playerLink}', '_blank'); } else { window.open('${channelPage}', '_blank'); } ` profilePicture.setAttribute('onclick', isOpenNewtabEnabled === 1 ? profileClickHandlerForNewtab : profileClickHandler ); profilePicture.setAttribute('onmousedown', ` if (event.button === 1) { event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth !== 52) { window.open('${channelPage}', '_blank'); } } `); profilePicture.classList.add('profile-picture'); const username = document.createElement('span'); username.classList.add('username'); username.textContent = (is_mobile_push === "Y") ? `🖈${channelName}` : channelName; username.setAttribute('title', is_mobile_push === "Y" ? '고정됨(알림 받기 켜짐)' : ''); username.title = username.textContent; const description = document.createElement('span'); description.classList.add('description'); description.textContent = liveCategoryValue ?? liveInfo.liveCategoryValue; description.title = description.textContent; userElement.setAttribute('broad_cate_no', liveCategory ?? ''); const watchers = document.createElement('span'); watchers.classList.add('watchers'); watchers.innerHTML = `🟢${addNumberSeparator(concurrentUserCount ?? liveInfo.concurrentUserCount)}`; userElement.append(profilePicture, username, description, watchers); return userElement; } const createUserElement = (channel, is_mobile_push, is_pin) => { const { user_id: userId, broad_no: broadNo, total_view_cnt: totalViewCnt, broad_title: broadTitle, user_nick: userNick, broad_start: broadStart, broad_cate_no: catNo, category_name: categoryName, } = channel; const playerLink = `https://play.sooplive.co.kr/${userId}/${broadNo}`; const broadThumbnail = `https://liveimg.sooplive.co.kr/m/${broadNo}`; const userElement = document.createElement('a'); userElement.classList.add('user'); if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); userElement.setAttribute('href', playerLink); if (isOpenNewtabEnabled) { userElement.setAttribute('target', '_blank'); } else if (isSendLoadBroadEnabled) { userElement.setAttribute('onclick', ` if(event.ctrlKey || (window.location.href.indexOf('play.sooplive.co.kr') === -1) ) 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('target', '_self'); } userElement.setAttribute('data-watchers', totalViewCnt); userElement.setAttribute('broad_thumbnail', broadThumbnail); userElement.setAttribute('tooltip', broadTitle); userElement.setAttribute('user_id', userId); userElement.setAttribute('broad_start', broadStart); if (isOpenExternalPlayerFromSidebarEnabled) { userElement.setAttribute('oncontextmenu', ` event.preventDefault(); event.stopPropagation(); (async () => { const aid = await getBroadAid2('${userId}', ${broadNo}); if (aid) openHlsStream('${userNick}', 'https://live-global-cdn-v02.sooplive.co.kr/live-stm-12/auth_playlist.m3u8?aid=' + aid); })(); `); } if (is_mobile_push) { userElement.setAttribute('is_mobile_push', is_mobile_push); userElement.setAttribute('is_pin', is_pin ? 'Y' : '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}'`); const profileClickHandler = isSendLoadBroadEnabled ? // 프로필 클릭 & 현재 탭 & 빠른 전환 ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { if(event.ctrlKey) 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'); } ` : // 프로필 클릭 & 현재 탭 & 새로고침 ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { if(event.ctrlKey) { window.open('${playerLink}', '_blank'); return; } location.href = '${playerLink}'; } else { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } `; // 프로필 클릭 & 새 탭 열기: 최소화 시 생방송, 최대화 시 방송국 const profileClickHandlerForNewtab = ` event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth === 52) { window.open('${playerLink}', '_blank'); } else { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } ` profilePicture.setAttribute('onclick', isOpenNewtabEnabled === 1 ? profileClickHandlerForNewtab : profileClickHandler ); profilePicture.setAttribute('onmousedown', ` if (event.button === 1) { event.preventDefault(); event.stopPropagation(); if (document.getElementById('sidebar').offsetWidth !== 52) { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } } `); profilePicture.classList.add('profile-picture'); const username = document.createElement('span'); username.classList.add('username'); username.textContent = (is_pin || is_mobile_push === "Y") ? `🖈${userNick}` : userNick; username.setAttribute('title', is_pin ? '고정됨(상단 고정 켜짐)' : is_mobile_push === "Y" ? '고정됨(알림 받기 켜짐)' : ''); username.title = username.textContent; const description = document.createElement('span'); description.classList.add('description'); description.textContent = categoryName || getCategoryName(catNo); description.title = description.textContent; userElement.setAttribute('broad_cate_no', catNo); const watchers = document.createElement('span'); watchers.classList.add('watchers'); watchers.innerHTML = `🔴${addNumberSeparator(totalViewCnt)}`; userElement.append(profilePicture, username, description, watchers); return userElement; }; const createUserElement_vod = (channel) => { const { user_id: userId, title_no: broadNo, view_cnt: totalViewCnt, title: broadTitle, user_nick: userNick, vod_duration: vodDuration, reg_date: regDate, thumbnail, } = channel; const playerLink = `https://vod.sooplive.co.kr/player/${broadNo}`; const broadThumbnail = thumbnail.replace("http://", "https://"); const userElement = document.createElement('a'); userElement.classList.add('user'); if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); userElement.setAttribute('href', playerLink); if (isOpenNewtabEnabled) { userElement.setAttribute('target', '_blank'); } userElement.setAttribute('data-watchers', totalViewCnt); userElement.setAttribute('broad_thumbnail', broadThumbnail); userElement.setAttribute('tooltip', broadTitle); userElement.setAttribute('user_id', userId); userElement.setAttribute('vod_duration', vodDuration); 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}'`); const profileClickHandler = ` event.preventDefault(); event.stopPropagation(); const sidebarWidth = document.getElementById('sidebar').offsetWidth; if (sidebarWidth === 52) { location.href = '${playerLink}'; } else { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } `; profilePicture.setAttribute('onclick', isOpenNewtabEnabled ? `event.preventDefault(); event.stopPropagation(); window.open('${playerLink}', '_blank');` : profileClickHandler ); 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', 'profile-grayscale'); const username = document.createElement('span'); username.classList.add('username'); username.textContent = userNick; username.title = username.textContent; const description = document.createElement('span'); description.classList.add('description'); description.textContent = vodDuration; description.title = vodDuration; const watchers = document.createElement('span'); watchers.classList.add('watchers'); watchers.innerHTML = timeSince(regDate); userElement.append(profilePicture, username, description, watchers); return userElement; }; const createUserElement_offline = (channel, isFeeditem) => { const { user_id: userId, total_view_cnt: totalViewCnt, user_nick: userNick, is_mobile_push: isMobilePush, is_pin: isPin, } = channel; const playerLink = isFeeditem ? isFeeditem.url : `https://ch.sooplive.co.kr/${userId}`; const isOffline = "Y"; const feedTimestamp = isFeeditem ? isFeeditem.reg_timestamp : false; const feedRegDate = isFeeditem ? isFeeditem.reg_date : false; const userElement = document.createElement('a'); userElement.classList.add('user'); userElement.classList.add('user-offline'); if (isSmallUserLayoutEnabled) userElement.classList.add('small-user-layout'); userElement.setAttribute('href', playerLink); userElement.setAttribute('target', '_blank'); userElement.setAttribute('broad_start', feedRegDate || ''); userElement.setAttribute('data-watchers', isFeeditem ? feedTimestamp : totalViewCnt); userElement.setAttribute('user_id', userId); if (isFeeditem) { if (isFeeditem.photo_cnt) { userElement.setAttribute('broad_thumbnail', `https:${isFeeditem.photos[0].url}`); } else { userElement.setAttribute('data-tooltip-listener', 'false'); } userElement.setAttribute('tooltip', isFeeditem.title_name); } else { userElement.setAttribute('data-tooltip-listener', 'false'); } if (isMobilePush) { userElement.setAttribute('is_mobile_push', isMobilePush); userElement.setAttribute('is_pin', isPin ? 'Y' : 'N'); } userElement.setAttribute('is_offline', isOffline); 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); const profileClickHandler = ` event.preventDefault(); event.stopPropagation(); const sidebarWidth = document.getElementById('sidebar').offsetWidth; if (sidebarWidth === 52) { window.open('${playerLink}', '_blank'); } else { window.open('https://ch.sooplive.co.kr/${userId}', '_blank'); } `; profilePicture.setAttribute('onclick', profileClickHandler); profilePicture.classList.add('profile-picture', 'profile-grayscale'); const username = document.createElement('span'); username.classList.add('username'); username.textContent = isPin ? `🖈${userNick}` : userNick; username.title = username.textContent; if (isPin) username.setAttribute('title', '고정됨(상단 고정 켜짐)'); const description = document.createElement('span'); description.classList.add('description'); description.textContent = isFeeditem ? isFeeditem.title_name : ''; description.title = isFeeditem ? isFeeditem.title_name : ''; const watchers = document.createElement('span'); watchers.classList.add('watchers'); watchers.innerHTML = isFeeditem ? timeSince(isFeeditem.reg_date) : '🔴오프라인'; userElement.append(profilePicture, username, description, watchers); return userElement; }; const isUserInFollowSection = (userid) => { const followUsers = document.body.querySelectorAll('.users-section.follow .user'); // 유저가 포함되어 있는지 확인 return Array.from(followUsers).some(user => user.getAttribute('user_id') === userid); } const insertFoldButton = () => { const foldButton = ` `; const webplayer_scroll = document.getElementById('webplayer_scroll') || document.getElementById('list-container'); const serviceLnbElement = document.getElementById('sidebar'); if (serviceLnbElement) { serviceLnbElement.insertAdjacentHTML('beforeend', foldButton); // 클릭 이벤트 리스너를 정의 const toggleSidebar = () => { isSidebarMinimized = !isSidebarMinimized; // max 클래스가 있으면 제거하고 min 클래스 추가 if (serviceLnbElement.classList.toggle('max')) { serviceLnbElement.classList.remove('min'); webplayer_scroll.style.left = '240px'; } else { serviceLnbElement.classList.remove('max'); serviceLnbElement.classList.add('min'); webplayer_scroll.style.left = '52px'; } // isSidebarMinimized 값을 저장 GM_setValue("isSidebarMinimized", isSidebarMinimized ? 1 : 0); }; // 버튼에 클릭 이벤트 리스너 추가 const buttons = serviceLnbElement.querySelectorAll('.button-fold-sidebar, .button-unfold-sidebar'); for (const button of buttons) { button.addEventListener('click', toggleSidebar); } } }; const fetchBroadList = async (url, timeout) => { return new Promise((resolve, reject) => { let timeoutId; // 타임아웃 설정된 경우 타임아웃 처리 if (timeout) { timeoutId = setTimeout(() => { console.error(url, `Request timed out after ${timeout} ms`); resolve([]); // 타임아웃 발생 시 빈 배열 반환 }, timeout); } GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Content-Type': 'application/json' }, onload: (response) => { if (timeoutId) clearTimeout(timeoutId); // 타임아웃 클리어 try { //console.log(response.status); if (response.status >= 200 && response.status < 300) { const jsonResponse = JSON.parse(response.responseText); if (jsonResponse?.code === 401) { console.error(url, "Unauthorized: 401 error - possibly invalid credentials"); resolve([]); // 빈 배열로 반환 } //console.log(jsonResponse); resolve(jsonResponse); } else if (response.status === 401) { console.error(url, "Unauthorized: 401 error - possibly invalid credentials"); resolve([]); // 빈 배열로 반환 } else { console.error(url, `Error: ${response.status}`); resolve([]); } } catch (error) { console.error(url, "Parsing error: ", error); resolve([]); } }, onerror: (error) => { if (timeoutId) clearTimeout(timeoutId); // 타임아웃 클리어 console.error(url, "Request error: " + error.message); resolve([]); } }); }); }; const insertTopChannels = async (update) => { const topIcon = IS_DARK_MODE ? `