// ==UserScript== // @name 아카라이브 썸네일 이미지, 모두 열기 // @version 1.42 // @icon https://www.google.com/s2/favicons?sz=64&domain=arca.live // @description 아카라이브 썸네일 이미지 생성, 모두 열기 생성, 그 외 잡다한 기능.. // @author ChatGPT // @match https://arca.live/b/* // @match https://arca.live/u/scrap_list // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getValue // @grant GM_setValue // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js // @namespace Violentmonkey Scripts // @downloadURL none // ==/UserScript== (function() { 'use strict'; var config = { openAllButton: true, downAllButton: false, compressFiles: true, countImages: false, originalImage: false, downNumber: false, thumbnail: true, thumbWidth: 100, thumbHeight: 62, thumbHover: true, thumbBlur: true, thumbShadow: false, blurAmount: 2, origThumb: false, thumbHoverBest: true, controlButtons: false, closeButton: false, bookmarkButton: false, downButton: false, scrapList: true, test01: false, test02: false }; function handleSettings() { var descriptions = { openAllButton: '모두 열기 버튼 생성', downAllButton: '모든 이미지 다운로드 버튼 생성', compressFiles: '모든 이미지를 압축해서 다운로드', countImages: '모든 이미지의 총 개수를 구하고 진행률 표시', originalImage: '원본 이미지로 다운로드(체크 해제시 webp저장)', downNumber: '게시글 번호를 누르면 해당 게시글 이미지 다운로드', thumbnail: '썸네일 이미지 생성', thumbWidth: '썸네일 이미지 너비', thumbHeight: '썸네일 이미지 높이', thumbHover: '썸네일 이미지에 마우스 오버시 미리보기 이미지 출력', thumbBlur: '썸네일 이미지 블러 효과', thumbShadow: '썸네일 이미지 그림자 효과', blurAmount: '블러 효과의 정도', origThumb: '썸네일 이미지 클릭 시 원본 이미지 불러오기(유머 채널 개념글)', thumbHoverBest: '썸네일 이미지에 마우스 오버시 미리보기 이미지 출력(유머 채널 개념글)', controlButtons: '하단 우측 조작 버튼 생성', closeButton: '창닫기 버튼 생성', bookmarkButton: '스크랩 버튼 생성', downButton: '다운로드 버튼 생성', scrapList: '스크랩한 게시글 채널별, 탭별 필터링', test01: '미리보기 이미지를 해당 게시글 다른 이미지 가져오기', test02: '썸네일 이미지를 해당 게시글 다른 이미지로 대체' }; var mainConfigKeys = Object.keys(descriptions).filter(key => !['compressFiles', 'countImages', 'originalImage', 'downNumber', 'thumbWidth', 'thumbHeight', 'thumbHover', 'thumbBlur', 'blurAmount', 'thumbShadow', 'origThumb', 'thumbHoverBest', 'test01', 'test02', 'closeButton', 'bookmarkButton', 'downButton'].includes(key) ); function saveConfig() { for (var key in config) { if (config.hasOwnProperty(key)) { GM_setValue(key, config[key]); } } } function loadConfig() { for (var key in config) { if (config.hasOwnProperty(key)) { config[key] = GM_getValue(key, config[key]); } } } function createBaseWindow(id, titleText) { var existingWindow = document.getElementById(id); if (existingWindow) { existingWindow.remove(); } var window = document.createElement('div'); window.id = id; window.style.position = 'fixed'; window.style.top = '50%'; window.style.left = '50%'; window.style.transform = 'translate(-50%, -50%)'; window.style.width = '250px'; window.style.padding = '20px'; window.style.background = '#ffffff'; window.style.border = '1px solid #cccccc'; window.style.borderRadius = '10px'; window.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.3)'; window.style.zIndex = '9999'; window.style.textAlign = 'left'; window.style.display = 'flex'; window.style.flexDirection = 'column'; window.style.alignItems = 'center'; var title = document.createElement('div'); title.innerHTML = titleText; title.style.fontSize = '24px'; title.style.fontWeight = 'bold'; title.style.marginBottom = '10px'; window.appendChild(title); return window; } function createDownloadSettingsWindow() { var thumbnailSettingsWindow = createBaseWindow('thumbnailSettingsWindow', 'Download'); var keys = ['compressFiles', 'countImages', 'originalImage', 'downNumber']; keys.forEach(function(key) { var configDiv = document.createElement('div'); configDiv.style.marginBottom = '5px'; configDiv.style.display = 'flex'; configDiv.style.alignItems = 'center'; var label = document.createElement('label'); label.innerHTML = key + ': '; label.style.marginRight = '5px'; label.style.marginBottom = '3px'; label.title = descriptions[key]; var input = document.createElement('input'); input.type = (typeof config[key] === 'boolean') ? 'checkbox' : 'text'; input.value = config[key]; input.checked = config[key]; if (input.type === 'text') { input.style.width = '40px'; input.style.height = '20px'; input.style.padding = '0 5px'; } input.addEventListener('input', (function(key) { return function(event) { if (key === 'blurAmount') { event.target.value = event.target.value.replace(/\D/g, ''); } config[key] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; }; })(key)); configDiv.appendChild(label); configDiv.appendChild(input); thumbnailSettingsWindow.appendChild(configDiv); }); var buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.marginTop = '10px'; var confirmButton = document.createElement('button'); confirmButton.innerHTML = '확인'; confirmButton.style.marginRight = '15px'; confirmButton.style.border = '1px solid #cccccc'; confirmButton.style.borderRadius = '5px'; confirmButton.addEventListener('click', function() { saveConfig(); thumbnailSettingsWindow.remove(); }); confirmButton.addEventListener('mouseover', function() { confirmButton.style.background = '#007bff'; confirmButton.style.color = '#ffffff'; }); confirmButton.addEventListener('mouseout', function() { confirmButton.style.background = ''; confirmButton.style.color = '#000000'; }); buttonContainer.appendChild(confirmButton); var cancelButton = document.createElement('button'); cancelButton.innerHTML = '취소'; cancelButton.style.border = '1px solid #cccccc'; cancelButton.style.borderRadius = '5px'; cancelButton.addEventListener('click', function() { thumbnailSettingsWindow.remove(); }); cancelButton.addEventListener('mouseover', function() { cancelButton.style.background = '#ff0000'; cancelButton.style.color = '#ffffff'; }); cancelButton.addEventListener('mouseout', function() { cancelButton.style.background = ''; cancelButton.style.color = '#000000'; }); buttonContainer.appendChild(cancelButton); thumbnailSettingsWindow.appendChild(buttonContainer); document.body.appendChild(thumbnailSettingsWindow); } function createThumbnailSettingsWindow() { var thumbnailSettingsWindow = createBaseWindow('thumbnailSettingsWindow', 'Thumbnail'); var keys = ['thumbWidth', 'thumbHeight', 'thumbHover', 'thumbBlur', 'blurAmount', 'thumbShadow', 'origThumb', 'thumbHoverBest', 'test01', 'test02']; keys.forEach(function(key) { var configDiv = document.createElement('div'); configDiv.style.marginBottom = '5px'; configDiv.style.display = 'flex'; configDiv.style.alignItems = 'center'; var label = document.createElement('label'); label.innerHTML = key + ': '; label.style.marginRight = '5px'; label.style.marginBottom = '3px'; label.title = descriptions[key]; var input = document.createElement('input'); input.type = (typeof config[key] === 'boolean') ? 'checkbox' : 'text'; input.value = config[key]; input.checked = config[key]; if (input.type === 'text') { input.style.width = '40px'; input.style.height = '20px'; input.style.padding = '0 5px'; } input.addEventListener('input', (function(key) { return function(event) { if (key === 'blurAmount') { event.target.value = event.target.value.replace(/\D/g, ''); } config[key] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; }; })(key)); configDiv.appendChild(label); configDiv.appendChild(input); if (key === 'test01') { var settingsIcon = document.createElement('span'); settingsIcon.innerHTML = '⚙️'; settingsIcon.style.cursor = 'pointer'; settingsIcon.style.marginLeft = '3px'; settingsIcon.style.marginBottom = '2px'; settingsIcon.addEventListener('click', function() { if (key === 'test01') { createLinkSettingsWindow(); } }); configDiv.appendChild(settingsIcon); } thumbnailSettingsWindow.appendChild(configDiv); }); var buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.marginTop = '10px'; var confirmButton = document.createElement('button'); confirmButton.innerHTML = '확인'; confirmButton.style.marginRight = '15px'; confirmButton.style.border = '1px solid #cccccc'; confirmButton.style.borderRadius = '5px'; confirmButton.addEventListener('click', function() { saveConfig(); thumbnailSettingsWindow.remove(); }); confirmButton.addEventListener('mouseover', function() { confirmButton.style.background = '#007bff'; confirmButton.style.color = '#ffffff'; }); confirmButton.addEventListener('mouseout', function() { confirmButton.style.background = ''; confirmButton.style.color = '#000000'; }); buttonContainer.appendChild(confirmButton); var cancelButton = document.createElement('button'); cancelButton.innerHTML = '취소'; cancelButton.style.border = '1px solid #cccccc'; cancelButton.style.borderRadius = '5px'; cancelButton.addEventListener('click', function() { thumbnailSettingsWindow.remove(); }); cancelButton.addEventListener('mouseover', function() { cancelButton.style.background = '#ff0000'; cancelButton.style.color = '#ffffff'; }); cancelButton.addEventListener('mouseout', function() { cancelButton.style.background = ''; cancelButton.style.color = '#000000'; }); buttonContainer.appendChild(cancelButton); thumbnailSettingsWindow.appendChild(buttonContainer); document.body.appendChild(thumbnailSettingsWindow); } function createControlSettingsWindow() { var controlSettingsWindow = createBaseWindow('controlSettingsWindow', 'Control Buttons'); var keys = ['closeButton', 'bookmarkButton', 'downButton']; keys.forEach(function(key) { var configDiv = document.createElement('div'); configDiv.style.marginBottom = '5px'; configDiv.style.display = 'flex'; configDiv.style.alignItems = 'center'; var label = document.createElement('label'); label.innerHTML = key + ': '; label.style.marginRight = '5px'; label.style.marginBottom = '3px'; label.title = descriptions[key]; var input = document.createElement('input'); input.type = (typeof config[key] === 'boolean') ? 'checkbox' : 'text'; input.value = config[key]; input.checked = config[key]; if (input.type === 'text') { input.style.width = '40px'; input.style.height = '20px'; input.style.padding = '0 5px'; } input.addEventListener('input', (function(key) { return function(event) { if (key === 'blurAmount') { event.target.value = event.target.value.replace(/\D/g, ''); } config[key] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; }; })(key)); configDiv.appendChild(label); configDiv.appendChild(input); controlSettingsWindow.appendChild(configDiv); }); var buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.marginTop = '10px'; var confirmButton = document.createElement('button'); confirmButton.innerHTML = '확인'; confirmButton.style.marginRight = '15px'; confirmButton.style.border = '1px solid #cccccc'; confirmButton.style.borderRadius = '5px'; confirmButton.addEventListener('click', function() { saveConfig(); controlSettingsWindow.remove(); }); confirmButton.addEventListener('mouseover', function() { confirmButton.style.background = '#007bff'; confirmButton.style.color = '#ffffff'; }); confirmButton.addEventListener('mouseout', function() { confirmButton.style.background = ''; confirmButton.style.color = '#000000'; }); buttonContainer.appendChild(confirmButton); var cancelButton = document.createElement('button'); cancelButton.innerHTML = '취소'; cancelButton.style.border = '1px solid #cccccc'; cancelButton.style.borderRadius = '5px'; cancelButton.addEventListener('click', function() { controlSettingsWindow.remove(); }); cancelButton.addEventListener('mouseover', function() { cancelButton.style.background = '#ff0000'; cancelButton.style.color = '#ffffff'; }); cancelButton.addEventListener('mouseout', function() { cancelButton.style.background = ''; cancelButton.style.color = '#000000'; }); buttonContainer.appendChild(cancelButton); controlSettingsWindow.appendChild(buttonContainer); document.body.appendChild(controlSettingsWindow); } function createLinkSettingsWindow() { var linkSettingsWindow = createBaseWindow('linkSettingsWindow', 'Filter Link'); var linkListContainer = document.createElement('div'); linkListContainer.style.marginBottom = '10px'; linkSettingsWindow.appendChild(linkListContainer); // Load saved links and display them var savedLinks = GM_getValue('savedLinks', []); var rowContainer = document.createElement('div'); rowContainer.style.display = 'flex'; rowContainer.style.flexWrap = 'wrap'; rowContainer.style.justifyContent = 'center'; // 가로 중앙 정렬 rowContainer.style.alignItems = 'center'; // 세로 중앙 정렬 rowContainer.style.gap = '10px'; // Adds spacing between items linkListContainer.appendChild(rowContainer); savedLinks.forEach(function (link, index) { var linkDiv = document.createElement('div'); linkDiv.style.display = 'flex'; linkDiv.style.flexDirection = 'column'; linkDiv.style.alignItems = 'center'; linkDiv.style.marginBottom = '10px'; linkDiv.style.width = '60px'; // Ensure all items have a fixed width to align properly // Create a small thumbnail image var thumbnail = document.createElement('img'); thumbnail.src = link; // Set the link as the image source thumbnail.style.width = '50px'; // Adjust thumbnail width thumbnail.style.height = '50px'; // Adjust thumbnail height thumbnail.style.objectFit = 'cover'; // Ensure the image fits nicely thumbnail.style.marginBottom = '5px'; // Add click event to set the link in the input field thumbnail.addEventListener('click', function () { var linkInput = document.querySelector('#linkInput'); // 입력창 선택 if (linkInput) { linkInput.value = link; // 클릭된 이미지의 링크를 입력창에 설정 } }); var deleteButton = document.createElement('button'); deleteButton.textContent = 'Delete'; deleteButton.style.border = '1px solid #cccccc'; deleteButton.style.borderRadius = '5px'; deleteButton.style.marginTop = '5px'; deleteButton.style.fontSize = '12px'; deleteButton.addEventListener('click', function () { savedLinks.splice(index, 1); GM_setValue('savedLinks', savedLinks); createLinkSettingsWindow(); }); linkDiv.appendChild(thumbnail); linkDiv.appendChild(deleteButton); rowContainer.appendChild(linkDiv); }); var addLinkContainer = document.createElement('div'); addLinkContainer.style.display = 'flex'; addLinkContainer.style.alignItems = 'center'; var linkInput = document.createElement('input'); linkInput.type = 'text'; linkInput.id = 'linkInput'; // 입력창에 id를 추가 linkInput.placeholder = '썸네일 이미지 링크 입력'; linkInput.style.flex = '1'; linkInput.style.marginRight = '5px'; linkInput.style.padding = '5px'; linkInput.style.width = '180px' linkInput.style.fontSize = '12px'; // 글자 크기 줄이기 addLinkContainer.appendChild(linkInput); var addLinkButton = document.createElement('button'); addLinkButton.textContent = 'Add'; addLinkButton.style.border = '1px solid #cccccc'; addLinkButton.style.borderRadius = '5px'; addLinkButton.addEventListener('click', function () { var newLink = linkInput.value.trim(); if (newLink && !savedLinks.includes(newLink)) { savedLinks.push(newLink); GM_setValue('savedLinks', savedLinks); createLinkSettingsWindow(); } }); addLinkContainer.appendChild(linkInput); addLinkContainer.appendChild(addLinkButton); linkSettingsWindow.appendChild(addLinkContainer); var tooltip = document.createElement('div'); tooltip.innerHTML = '해당 게시글의 다른 이미지로 대체'; tooltip.style.fontSize = '12px'; tooltip.style.marginTop = '15px'; tooltip.style.marginBottom = '5px'; tooltip.style.color = 'gray'; linkSettingsWindow.appendChild(tooltip); var buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.marginTop = '10px'; var confirmButton = document.createElement('button'); confirmButton.innerHTML = '확인'; confirmButton.style.marginRight = '15px'; confirmButton.style.border = '1px solid #cccccc'; confirmButton.style.borderRadius = '5px'; confirmButton.addEventListener('click', function() { saveConfig(); linkSettingsWindow.remove(); }); confirmButton.addEventListener('mouseover', function() { confirmButton.style.background = '#007bff'; confirmButton.style.color = '#ffffff'; }); confirmButton.addEventListener('mouseout', function() { confirmButton.style.background = ''; confirmButton.style.color = '#000000'; }); buttonContainer.appendChild(confirmButton); var cancelButton = document.createElement('button'); cancelButton.innerHTML = '취소'; cancelButton.style.border = '1px solid #cccccc'; cancelButton.style.borderRadius = '5px'; cancelButton.addEventListener('click', function() { linkSettingsWindow.remove(); }); cancelButton.addEventListener('mouseover', function() { cancelButton.style.background = '#ff0000'; cancelButton.style.color = '#ffffff'; }); cancelButton.addEventListener('mouseout', function() { cancelButton.style.background = ''; cancelButton.style.color = '#000000'; }); buttonContainer.appendChild(cancelButton); linkSettingsWindow.appendChild(buttonContainer); document.body.appendChild(linkSettingsWindow); } function createSettingsWindow() { var settingsWindow = createBaseWindow('settingsWindow', 'Settings'); mainConfigKeys.forEach(function(key) { var configDiv = document.createElement('div'); configDiv.style.marginBottom = '5px'; configDiv.style.display = 'flex'; configDiv.style.alignItems = 'center'; var label = document.createElement('label'); label.innerHTML = key + ': '; label.style.marginRight = '5px'; label.style.marginBottom = '3px'; label.title = descriptions[key]; var input = document.createElement('input'); input.type = (typeof config[key] === 'boolean') ? 'checkbox' : 'text'; input.value = config[key]; input.checked = config[key]; if (input.type === 'text') { input.style.width = '40px'; input.style.height = '20px'; input.style.padding = '0 5px'; } input.addEventListener('input', (function(key) { return function(event) { config[key] = event.target.type === 'checkbox' ? event.target.checked : event.target.value; }; })(key)); configDiv.appendChild(label); configDiv.appendChild(input); if (key === 'downAllButton' || key === 'thumbnail' || key === 'controlButtons' || key === 'test01') { var settingsIcon = document.createElement('span'); settingsIcon.innerHTML = '⚙️'; settingsIcon.style.cursor = 'pointer'; settingsIcon.style.marginLeft = '3px'; settingsIcon.style.marginBottom = '2px'; settingsIcon.addEventListener('click', function() { if (key === 'downAllButton') { createDownloadSettingsWindow(); } else if (key === 'thumbnail') { createThumbnailSettingsWindow(); } else if (key === 'controlButtons') { createControlSettingsWindow(); } else if (key === 'test01') { createLinkSettingsWindow(); } }); configDiv.appendChild(settingsIcon); } settingsWindow.appendChild(configDiv); }); var tooltip = document.createElement('div'); tooltip.innerHTML = '마우스를 올리면 설명이 나옵니다'; tooltip.style.fontSize = '12px'; tooltip.style.marginTop = '5px'; tooltip.style.marginBottom = '10px'; tooltip.style.color = 'gray'; settingsWindow.appendChild(tooltip); var buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.marginTop = '10px'; var confirmButton = document.createElement('button'); confirmButton.innerHTML = '확인'; confirmButton.style.marginRight = '15px'; confirmButton.style.border = '1px solid #cccccc'; confirmButton.style.borderRadius = '5px'; confirmButton.addEventListener('click', function() { saveConfig(); settingsWindow.remove(); location.reload(); }); confirmButton.addEventListener('mouseover', function() { confirmButton.style.background = '#007bff'; confirmButton.style.color = '#ffffff'; }); confirmButton.addEventListener('mouseout', function() { confirmButton.style.background = ''; confirmButton.style.color = '#000000'; }); buttonContainer.appendChild(confirmButton); var cancelButton = document.createElement('button'); cancelButton.innerHTML = '취소'; cancelButton.style.border = '1px solid #cccccc'; cancelButton.style.borderRadius = '5px'; cancelButton.addEventListener('click', function() { settingsWindow.remove(); }); cancelButton.addEventListener('mouseover', function() { cancelButton.style.background = '#ff0000'; cancelButton.style.color = '#ffffff'; }); cancelButton.addEventListener('mouseout', function() { cancelButton.style.background = ''; cancelButton.style.color = '#000000'; }); buttonContainer.appendChild(cancelButton); settingsWindow.appendChild(buttonContainer); document.body.appendChild(settingsWindow); } loadConfig(); GM_registerMenuCommand('설정', function() { createSettingsWindow(); }); } function arcaLiveScrapList() { const header = document.querySelector('.list-table '); // 부모 div (전체 컨테이너) var containerDiv = document.createElement('div'); containerDiv.style.marginBottom = '0.5rem'; // 페이징 요소의 HTML을 가져옵니다. const paginationHTML = document.querySelector('.pagination.justify-content-center'); const paginationDiv = document.createElement('div'); paginationDiv.innerHTML = paginationHTML.outerHTML; paginationDiv.style.marginBottom = '0.5rem'; // Create filter div (채널과 탭 필터) const filterDiv = document.createElement('div'); filterDiv.className = 'filterDiv'; filterDiv.style.display = 'flex'; // Create channel filter const channelFilter = document.createElement('select'); channelFilter.className = 'form-control select-list-type'; channelFilter.name = 'sort'; channelFilter.style.cssText = 'width: auto; height: 2rem; float: left; padding: 0 0 0 .5rem; font-size: .9rem;'; const defaultChannelOption = document.createElement('option'); defaultChannelOption.value = ''; defaultChannelOption.text = '채널 선택'; channelFilter.appendChild(defaultChannelOption); filterDiv.appendChild(channelFilter); // Create tab filter const tabFilter = document.createElement('select'); tabFilter.className = 'form-control select-list-type'; tabFilter.name = 'sort'; tabFilter.style.cssText = 'width: auto; height: 2rem; float: left; padding: 0 0 0 .5rem; font-size: .9rem;'; const defaultTabOption = document.createElement('option'); defaultTabOption.value = ''; defaultTabOption.text = '탭 선택'; tabFilter.appendChild(defaultTabOption); filterDiv.appendChild(tabFilter); // gridContainer에 각 영역 추가 containerDiv.appendChild(paginationDiv); containerDiv.appendChild(filterDiv); // 문서의 body에 추가 (혹은 다른 부모 요소에 추가 가능) header.parentNode.insertBefore(containerDiv, header); // Collect channels and tabs const posts = document.querySelectorAll('.vrow.column.filtered, .vrow.column'); const channelTabMap = {}; posts.forEach(post => { const badges = post.querySelectorAll('.badge'); if (badges.length >= 2) { const channel = badges[0].textContent.trim(); const tab = badges[1].textContent.trim(); if (!channelTabMap[channel]) { channelTabMap[channel] = new Set(); } channelTabMap[channel].add(tab); } }); // Populate channel filter Object.keys(channelTabMap).forEach(channel => { const option = document.createElement('option'); option.value = channel; option.text = channel; channelFilter.appendChild(option); }); // Update tab filter based on selected channel function updateTabFilter() { const selectedChannel = channelFilter.value; tabFilter.innerHTML = ''; const defaultTabOption = document.createElement('option'); defaultTabOption.value = ''; defaultTabOption.text = '탭 선택'; tabFilter.appendChild(defaultTabOption); if (selectedChannel && channelTabMap[selectedChannel]) { channelTabMap[selectedChannel].forEach(tab => { const option = document.createElement('option'); option.value = tab; option.text = tab; tabFilter.appendChild(option); }); } filterPosts(); } // Filter posts based on selected channel and tab function filterPosts() { const selectedChannel = channelFilter.value; const selectedTab = tabFilter.value; posts.forEach(post => { const badges = post.querySelectorAll('.badge'); if (badges.length >= 2) { const postChannel = badges[0].textContent.trim(); const postTab = badges[1].textContent.trim(); if ((selectedChannel === '' || postChannel === selectedChannel) && (selectedTab === '' || postTab === selectedTab)) { post.style.display = ''; } else { post.style.display = 'none'; } } }); } channelFilter.addEventListener('change', updateTabFilter); tabFilter.addEventListener('change', filterPosts); } function arcaLive() { // 모두 열기 버튼 생성 if (config.openAllButton) { var openAllButton = document.createElement('a'); openAllButton.className = 'btn btn-sm btn-primary float-left'; openAllButton.href = '#'; openAllButton.innerHTML = ' 모두 열기 '; openAllButton.style.height = '2rem'; openAllButton.addEventListener('click', function(event) { event.preventDefault(); // 게시글의 수를 계산 var posts = document.querySelectorAll('a.vrow.column:not(.notice)'); var postCount = 0; // 필터링된 게시글을 제외한 수를 계산 posts.forEach(function(element) { var href = element.getAttribute('href'); var classes = element.className.split(' '); if (href && !classes.includes('filtered') && !classes.includes('filtered-keyword')) { postCount++; } }); // 게시글 수를 포함한 메시지 const confirmMessage = `총 ${postCount}개의 게시글을 한 번에 엽니다.\n계속 진행하시겠습니까?`; // 확인 메시지 표시 if (confirm(confirmMessage)) { posts.forEach(function(element) { var href = element.getAttribute('href'); var classes = element.className.split(' '); if (href && !classes.includes('filtered') && !classes.includes('filtered-keyword')) { window.open(href, '_blank'); } }); } }); var targetElement = document.querySelector('.form-control.select-list-type'); targetElement.parentNode.insertBefore(openAllButton, targetElement); } // 이미지와 동영상 다운로드 버튼 생성 if (config.downAllButton) { async function getTotalImages(urls) { let totalImages = 0; for (const url of urls) { const response = await fetch(url); const html = await response.text(); const doc = new DOMParser().parseFromString(html, "text/html"); const imageElements = Array.from(doc.querySelectorAll('.article-body img')).filter(img => !img.classList.contains('arca-emoticon')); const gifVideoElements = doc.querySelectorAll('video[data-orig="gif"][data-originalurl]'); totalImages += imageElements.length + gifVideoElements.length; } return totalImages; } async function downloadMediaSequentially(urls, totalImages, compressFiles) { let totalDownloaded = 0; // 프로그레스 바 업데이트 함수 function updateProgressBar(progress) { const progressBar = document.getElementById('progress-bar'); progressBar.style.width = progress + '%'; progressBar.innerHTML = progress + '%'; if (progress === 100) { setTimeout(() => { progressBar.style.backgroundColor = 'orange'; // 100%일 때 배경색을 주황색으로 변경 }, 300); // 잠시 딜레이를 주어 애니메이션 완료 후 색상 변경 } } async function downloadFile(url, index, type, requestUrl, zip, title) { const response = await fetch(url); const blob = await response.blob(); const extension = type === 'img' ? (config.originalImage ? url.split('.').pop().split('?')[0].toLowerCase() : 'webp') : 'gif'; const numbersFromUrl = requestUrl.match(/\d+/)[0]; const fileIndex = index + 1; // Index를 1 증가시킴 // const sanitizedTitle = title.replace(/[^a-zA-Z0-9가-힣\s]/g, '_'); // 파일 이름에 사용할 수 있도록 제목을 정제 const numberedFileName = compressFiles ? `${title}_${String(fileIndex).padStart(2, '0')}.${extension}` : `${numbersFromUrl}_${String(fileIndex).padStart(2, '0')}.${extension}`; if (zip) { zip.file(numberedFileName, blob); } else { const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = numberedFileName; link.click(); } } async function processNextUrl() { for (let index = 0; index < urls.length; index++) { const url = urls[index]; let zip; if (compressFiles) { zip = new JSZip(); } const response = await fetch(url); const html = await response.text(); const doc = new DOMParser().parseFromString(html, "text/html"); const titleElement = doc.querySelector('.title-row .title'); let title = ''; if (titleElement) { const textNodes = Array.from(titleElement.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE && node.parentElement === titleElement); if (textNodes.length > 0) { title = textNodes.map(node => node.textContent.trim()).join(''); } } // arca-emoticon 클래스를 가진 이미지를 제외하고 선택 const mediaElements = Array.from(doc.querySelectorAll('.article-body img, .article-body video[data-orig="gif"]')).filter(media => !media.classList.contains('arca-emoticon')); try { if (mediaElements.length > 0) { for (let i = 0; i < mediaElements.length; i++) { const media = mediaElements[i]; const mediaType = media.tagName.toLowerCase(); const mediaUrl = mediaType === 'img' ? (config.originalImage ? media.getAttribute('src') + "&type=orig" : media.getAttribute('src')) : media.getAttribute('data-originalurl'); if (mediaUrl) { await downloadFile(mediaUrl, i, mediaType, url, zip, title); totalDownloaded++; if (config.countImages) { const progress = Math.round((totalDownloaded / totalImages) * 100); updateProgressBar(progress); } } } if (zip) { const content = await zip.generateAsync({ type: 'blob' }); const numbersFromUrl = url.match(/\d+/)[0]; const zipFileName = `${numbersFromUrl}.zip`; const zipLink = document.createElement('a'); zipLink.href = window.URL.createObjectURL(content); zipLink.download = zipFileName; zipLink.click(); } } } catch (error) { console.error("Error downloading media:", error); } } } await processNextUrl(); } var downloadMediaButton = document.createElement('a'); downloadMediaButton.className = 'btn btn-sm btn-success float-left'; downloadMediaButton.href = '#'; downloadMediaButton.innerHTML = ' 다운로드 '; downloadMediaButton.style.position = 'relative'; // 상대 위치 지정 // 프로그레스 바 스타일을 가진 div 엘리먼트 추가 var progressBar = document.createElement('div'); progressBar.id = 'progress-bar'; // ID 추가 progressBar.style.position = 'absolute'; // 절대 위치 지정 progressBar.style.bottom = '5%'; progressBar.style.left = '0'; progressBar.style.width = '0%'; // 초기 너비는 0% progressBar.style.height = '10%'; progressBar.style.backgroundColor = 'yellow'; // 프로그레스 바 색상 progressBar.style.borderRadius = 'inherit'; progressBar.style.transition = 'width 0.3s ease-in-out'; // 프로그레스 바 애니메이션 downloadMediaButton.appendChild(progressBar); // 프로그레스 바를 버튼에 추가 downloadMediaButton.addEventListener('click', async function(event) { event.preventDefault(); var mediaUrls = []; // 다운로드할 미디어 URL을 저장할 배열 document.querySelectorAll('a.vrow.column:not(.notice)').forEach(function(element) { var href = element.getAttribute('href'); var classes = element.className.split(' '); if (href && !classes.includes('filtered') && !classes.includes('filtered-keyword')) { mediaUrls.push(href); // 미디어 URL을 배열에 추가 } }); const mediaUrlsCount = mediaUrls.length; if (config.countImages) { const initialMessage = `총 ${mediaUrlsCount}개의 게시글을 찾았습니다.\n모든 게시글의 이미지를 확인하여 총 개수를 계산합니다.\n계산하는 데 시간이 오래 걸릴 수 있습니다.\n(설정에서 변경 가능)`; alert(initialMessage); const totalImages = await getTotalImages(mediaUrls); const confirmMessage = `다운로드해야 할 이미지의 총 개수는 ${totalImages}개입니다.\n계속해서 다운로드 하시겠습니까?`; if (confirm(confirmMessage)) { progressBar.style.width = '0%'; // 초기 너비는 0% progressBar.style.backgroundColor = 'yellow'; // 프로그레스 바 색상 await downloadMediaSequentially(mediaUrls, totalImages, config.compressFiles); // config.compressFiles 변수 전달 } } else { // 프로그레스 바를 사용하지 않을 경우에는 다운로드 여부를 확인하는 창 띄우기 const confirmMessage = `총 ${mediaUrlsCount}개의 게시글을 한 번에 다운로드합니다.\n다운로드를 진행하시겠습니까?`; if (confirm(confirmMessage)) { progressBar.style.width = '0%'; // 초기 너비는 0% progressBar.style.backgroundColor = 'yellow'; // 프로그레스 바 색상 await downloadMediaSequentially(mediaUrls, 0, config.compressFiles); // config.compressFiles 변수 전달 progressBar.style.width = '100%'; progressBar.style.backgroundColor = 'orange'; // 100%일 때 배경색을 주황색으로 변경 } } }); var targetElement = document.querySelector('.form-control.select-list-type'); targetElement.parentNode.insertBefore(downloadMediaButton, targetElement); } if (config.downNumber) { // document.addEventListener("DOMContentLoaded", function() { // }); document.querySelectorAll('.vrow.column:not(.notice) .vcol.col-id').forEach(function(link) { link.addEventListener('click', async function(event) { event.preventDefault(); // 기본 동작 방지 link.style.color = 'orange'; // 다운로드 시작 시 노란색으로 변경 const parentHref = link.closest('.vrow.column').getAttribute('href'); await downloadMediaFromUrl(parentHref, config.compressFiles); // compressFiles 변수 전달 link.style.color = 'red'; // 다운로드 완료 시 빨간색으로 변경 }); }); async function downloadMediaFromUrl(url, compressFiles) { // compressFiles 변수 추가 const response = await fetch(url); const html = await response.text(); const doc = new DOMParser().parseFromString(html, "text/html"); const mediaElements = Array.from(doc.querySelectorAll('.article-body img, .article-body video[data-orig="gif"]')).filter(media => !media.classList.contains('arca-emoticon')); let zip; const titleElement = doc.querySelector('.title-row .title'); let title = ''; if (titleElement) { const textNodes = Array.from(titleElement.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE && node.parentElement === titleElement); if (textNodes.length > 0) { title = textNodes.map(node => node.textContent.trim()).join(''); } } async function downloadFile(mediaUrl, index, type) { const response = await fetch(mediaUrl); const blob = await response.blob(); const extension = type === 'img' ? (config.originalImage ? mediaUrl.split('.').pop().split('?')[0].toLowerCase() : 'webp') : 'gif'; const fileIndex = index + 1; const numbersFromUrl = url.match(/\d+/)[0]; // const sanitizedTitle = title.replace(/[^a-zA-Z0-9가-힣\s]/g, '_'); // 파일 이름에 사용할 수 있도록 제목을 정제 const numberedFileName = compressFiles ? `${title}_${String(fileIndex).padStart(2, '0')}.${extension}` : `${numbersFromUrl}_${String(fileIndex).padStart(2, '0')}.${extension}`; if (compressFiles) { zip.file(numberedFileName, blob); } else { const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = numberedFileName; link.click(); } } async function processMedia() { for (let i = 0; i < mediaElements.length; i++) { const media = mediaElements[i]; const mediaType = media.tagName.toLowerCase(); const mediaUrl = mediaType === 'img' ? (config.originalImage ? media.getAttribute('src') + "&type=orig" : media.getAttribute('src')) : media.getAttribute('data-originalurl'); if (mediaUrl) { await downloadFile(mediaUrl, i, mediaType); } } } if (compressFiles) { zip = new JSZip(); } await processMedia(); if (compressFiles) { const content = await zip.generateAsync({ type: 'blob' }); const zipFileName = url.match(/\d+/)[0] + '.zip'; const zipLink = document.createElement('a'); zipLink.href = window.URL.createObjectURL(content); zipLink.download = zipFileName; zipLink.click(); } } } if (config.thumbnail) { document.addEventListener("DOMContentLoaded", function() { function checkBlackEdge(img, callback) { var newImg = new Image(); newImg.crossOrigin = 'anonymous'; newImg.onload = function() { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = newImg.width; canvas.height = newImg.height; ctx.drawImage(newImg, 0, 0, newImg.width, newImg.height); var edgeSize = Math.min(newImg.width, newImg.height) * 0.1; var imgData = ctx.getImageData(0, 0, newImg.width, newImg.height); var totalPixels = 0; var blackPixels = 0; for (var x = 0; x < newImg.width; x++) { for (var y = 0; y < newImg.height; y++) { if (x < edgeSize || x >= newImg.width - edgeSize || y < edgeSize || y >= img.height - edgeSize) { totalPixels++; var index = (y * newImg.width + x) * 4; var pixelData = [ imgData.data[index], // Red imgData.data[index + 1], // Green imgData.data[index + 2] // Blue ]; if (pixelData[0] === 0 && pixelData[1] === 0 && pixelData[2] === 0) { blackPixels++; } } } } var blackPercentage = blackPixels / totalPixels; if (blackPercentage >= 0.33) { callback(true); } else { callback(false); } }; newImg.onerror = function() { // 이미지 로드 실패 시에도 콜백 호출하여 처리 callback(false); }; newImg.src = img.src + "&type=list"; // newImg.src = img.src + "&type=list"; } function setSecondImg(vrow, img) { var href = vrow.href; fetch(href) .then(response => { if (!response.ok) { throw new Error('Request failed with status ' + response.status); } return response.text(); }) .then(responseText => { var parser = new DOMParser(); var htmlDoc = parser.parseFromString(responseText, "text/html"); var contentDiv = htmlDoc.querySelector('div.fr-view.article-content'); if (!contentDiv) { return; } var Tags = contentDiv.querySelectorAll('img, video'); var firstTag = null; for (var i = 0; i < Tags.length; i++) { firstTag = Tags[i]; if (firstTag.style.width == '2px' && firstTag.style.height == '2px') { break; } else if (firstTag.tagName.toLowerCase() === 'img') { if (!(img.src.split("?")[0] === firstTag.src.split("?")[0])) { break; } } else if (firstTag.tagName.toLowerCase() === 'video') { break; } } if (!firstTag) { return; } var videoOriginalSrc = firstTag.getAttribute('data-originalurl'); var videoOriginalSrcType = firstTag.getAttribute('data-orig'); var videoPosterSrc = firstTag.getAttribute('poster'); var changeImgUrl = null; if (firstTag.tagName.toLowerCase() === 'img') { changeImgUrl = firstTag.src + "&type=list"; } else if (firstTag.tagName.toLowerCase() === 'video') { if (videoOriginalSrc && !videoOriginalSrcType) { changeImgUrl = videoOriginalSrc + "&type=list"; } else if (videoPosterSrc) { changeImgUrl = videoPosterSrc + "&type=list"; } else { changeImgUrl = img.src; } } // config.test02가 true일 경우 이미지 변경 if (config.test02) { img.onload = function () { img.parentNode.style.border = '2px solid pink'; // img.parentNode.style.boxShadow = 'rgb(255 155 155) 2px 2px 2px'; }; img.src = changeImgUrl; } var previewImg = vrow.querySelector('.vrow-preview img') previewImg.src = changeImgUrl.replace("&type=list", ''); }) .catch(error => { console.error('Error fetching data:', error); }); } const vrows = document.querySelectorAll('a.vrow.column:not(.notice)') vrows.forEach(function(vrow) { var vcolId = vrow.querySelector('.vcol.col-id'); var vcolTitle = vrow.querySelector('.vcol.col-title'); vcolId.style.margin = '0'; vcolId.style.height = 'auto'; vcolId.style.display = 'flex'; vcolId.style.alignItems = 'center'; // 세로 가운데 정렬 vcolId.style.justifyContent = 'center'; // 가로 가운데 정렬 var vcolThumb = vrow.querySelector('.vcol.col-thumb'); if (!vcolThumb) { vcolThumb = document.createElement('span'); vcolThumb.className = 'vcol col-thumb'; vcolThumb.style.width = config.thumbWidth + 'px'; vcolThumb.style.borderRadius = '3px'; vrow.querySelector('.vrow-inner').appendChild(vcolThumb); vcolTitle.parentNode.insertBefore(vcolThumb, vcolTitle); } var vrowPreview = vrow.querySelector('.vrow-preview'); // vrowPreview가 존재할 때만 썸네일을 추가하도록 조건 추가 if (vrowPreview) { var thumbnailCreated = false; // 썸네일 생성 여부 플래그 function createThumbnail() { if (thumbnailCreated) return; // 이미 썸네일이 생성되었으면 더 이상 생성하지 않음 var previewImg = vrowPreview.querySelector('img'); if (!previewImg) return; vrow.style.height = 'auto'; vrow.style.paddingTop = '3.75px'; vrow.style.paddingBottom = '3.75px'; vcolThumb.style.height = config.thumbHeight + 'px'; var thumbImg = vcolThumb.querySelector('img'); if (!thumbImg) { thumbImg = document.createElement('img'); thumbImg.src = previewImg.src; thumbImg.style.width = '100%'; thumbImg.style.height = '100%'; thumbImg.style.objectFit = 'cover'; if (config.thumbShadow) { thumbImg.onload = function () { vcolThumb.style.boxShadow = 'rgba(0, 0, 0, 0.4) 2px 2px 2px'; } } if (config.test01 || config.test02) { checkBlackEdge(thumbImg, function(hasBlackEdge) { if (hasBlackEdge) { setSecondImg(vrow, thumbImg); } }); function removeQueryString(url) { var parsedUrl = new URL(url); return parsedUrl.origin + parsedUrl.pathname; } var savedLinks = GM_getValue('savedLinks', []); var cleanSrc = removeQueryString(thumbImg.src); if (savedLinks.some(link => cleanSrc.includes(removeQueryString(link)))) { setSecondImg(vrow, thumbImg); console.log("Filtered Image:", vcolId.querySelector('span').textContent, thumbImg.src); } } if (config.thumbBlur) { thumbImg.style.filter = 'blur(' + config.blurAmount + 'px)'; thumbImg.addEventListener('mouseenter', function() { thumbImg.style.filter = 'none'; }); thumbImg.addEventListener('mouseleave', function() { thumbImg.style.filter = 'blur(' + config.blurAmount + 'px)'; }); } if (config.thumbHover) { thumbImg.addEventListener('mouseenter', function() { vrowPreview.style.display = null; }); thumbImg.addEventListener('mouseleave', function() { vrowPreview.style.display = 'none'; }); } vcolThumb.appendChild(thumbImg); thumbnailCreated = true; // 썸네일 생성 완료 } vrowPreview.style.display = 'none'; vrowPreview.style.pointerEvents = 'none'; vrowPreview.style.width = '30rem'; vrowPreview.style.height = 'auto'; vrowPreview.style.top = 'auto'; vrowPreview.style.left = (99) + parseFloat(config.thumbWidth) + 'px'; previewImg.src = previewImg.src.replace("&type=list", ''); } function tryCreateThumbnail(retryCount) { if (retryCount >= 100 || thumbnailCreated) return; // 썸네일이 이미 생성되었으면 더 이상 시도하지 않음 setTimeout(function() { if (retryCount === 0) createThumbnail(); tryCreateThumbnail(retryCount + 1); }, 100); } tryCreateThumbnail(0); } }); }); } // 썸네일 클릭 시 원본 이미지 불러오기 if (config.origThumb) { document.querySelectorAll('a.title.preview-image').forEach(function(link) { link.addEventListener('click', function(event) { event.preventDefault(); // 기본 동작 방지 var imageUrl = link.querySelector('img').getAttribute('src').replace(/&type=list/g, ''); window.location.href = imageUrl; }); }); } // 개념글 미리보기 이미지 마우스 오버시 보이게 if (config.thumbHoverBest) { // 이미지 요소 선택 var vrowPreviewImgs = document.querySelectorAll('.vrow.hybrid .title.preview-image .vrow-preview img'); // 각 이미지 요소에 이벤트 추가 vrowPreviewImgs.forEach(function(vrowPreviewImg) { // 이미지에 호버 이벤트 추가 vrowPreviewImg.addEventListener('mouseenter', function() { // 이미지의 부모 요소 찾기 var parentDiv = vrowPreviewImg.closest('.vrow.hybrid'); // 복제된 이미지 요소 생성 var duplicatevrowPreviewImg = document.createElement('img'); duplicatevrowPreviewImg.src = vrowPreviewImg.src.replace('&type=list', ''); // 복제된 이미지의 스타일 설정 duplicatevrowPreviewImg.style.position = 'absolute'; duplicatevrowPreviewImg.style.width = '30rem'; duplicatevrowPreviewImg.style.height = 'auto'; duplicatevrowPreviewImg.style.top = 'auto'; duplicatevrowPreviewImg.style.left = '7.5rem'; // 오른쪽으로 10rem 이동 duplicatevrowPreviewImg.style.zIndex = '1'; duplicatevrowPreviewImg.style.padding = '5px'; duplicatevrowPreviewImg.style.border = '1px solid'; duplicatevrowPreviewImg.style.borderRadius = '5px'; duplicatevrowPreviewImg.style.boxSizing = 'content-box'; duplicatevrowPreviewImg.style.backgroundColor = '#fff'; // 배경색 duplicatevrowPreviewImg.style.borderColor = '#bbb'; // 테두리 색상 // vrow hybrid 클래스에 align-items: center; 스타일 추가 parentDiv.classList.add('hybrid'); parentDiv.style.alignItems = 'center'; // 수직 가운데 정렬 // 복제된 이미지 요소를 기존 이미지 요소 다음에 추가 parentDiv.appendChild(duplicatevrowPreviewImg); // 마우스를 이미지에서 떼었을 때 복제된 이미지 제거 vrowPreviewImg.addEventListener('mouseleave', function() { duplicatevrowPreviewImg.remove(); }); }); }); } if (config.controlButtons) { if ((config.closeButton || config.bookmarkButton || config.downButton)) { document.addEventListener('DOMContentLoaded', function () { var articleMenu = document.querySelector('.article-menu.mt-2'); var originalScrapButton = articleMenu ? articleMenu.querySelector('.scrap-btn') : null; var originalDownloadButton = articleMenu ? articleMenu.querySelector('#imageToZipBtn') : null; var navControl = document.querySelector('.nav-control'); // 새로운 리스트 아이템 요소를 생성하는 함수 function createNewItem(iconClass, clickHandler, hoverHandler, leaveHandler) { var newItem = document.createElement('li'); newItem.innerHTML = ''; newItem.addEventListener('click', clickHandler); if (hoverHandler) { newItem.addEventListener('mouseenter', hoverHandler); } if (leaveHandler) { newItem.addEventListener('mouseleave', leaveHandler); } return newItem; } // 새로운 아이템을 내비게이션 컨트롤 리스트에 추가하거나 업데이트하는 함수 function appendOrUpdateItem(newItem) { if (navControl) { if (navControl.children.length > 0) { navControl.insertBefore(newItem, navControl.firstElementChild); } else { navControl.appendChild(newItem); } } else { console.error('내비게이션 컨트롤 리스트를 찾을 수 없습니다.'); } } // 다운로드 버튼 생성 if (config.controlButtons && config.downButton) { if (articleMenu) { var downloadButton = createNewItem( 'ion-android-download', downloadButtonClickHandler, downloadButtonHoverHandler, downloadButtonLeaveHandler ); appendOrUpdateItem(downloadButton); } } // 다운로드 버튼 핸들러 function downloadButtonClickHandler() { originalDownloadButton = articleMenu.querySelector('#imageToZipBtn'); if (originalDownloadButton) { // 다운로드 버튼 클릭 originalDownloadButton.click(); var progressChecked = false; // 프로그레스 바가 50% 이상인지 체크하는 변수 var intervalId = setInterval(function () { // 다운로드 진행 상태를 추적할 .download-progress 요소 찾기 var downloadProgress = originalDownloadButton.querySelector('.download-progress'); if (downloadProgress) { // 프로그레스 바가 존재하면 진행 상태의 width 값 확인 var width = parseFloat(downloadProgress.style.width); // 50% 이상이면 완료된 것으로 간주 if (width >= 50) { progressChecked = true; } // 프로그레스 바가 진행되면서 다운로드 버튼의 배경색을 조정 downloadButton.style.background = ` linear-gradient(to top, green ${width}%, transparent ${width}%), #3d414d `; // 프로그레스 바가 100%에 도달했을 때 if (width >= 100) { clearInterval(intervalId); // 애니메이션 종료 downloadButton.style.background = ` linear-gradient(to top, green 100%, transparent 100%), #3d414d `; } } else { // 프로그레스 바가 사라졌을 때 (프로그레스 바가 없을 때) if (progressChecked) { // 프로그레스 바가 50% 이상이었다면 완료된 것으로 간주 downloadButton.style.background = ` linear-gradient(to top, green 100%, transparent 100%), #3d414d `; } else { // 프로그레스 바가 50% 미만이었다면 취소로 간주 downloadButton.style.background = ` linear-gradient(to top, green 0%, transparent 0%), #3d414d `; } clearInterval(intervalId); // 애니메이션 종료 } }, 10); // 10ms마다 확인 } } function downloadButtonHoverHandler() { this.style.backgroundColor = 'green'; } function downloadButtonLeaveHandler() { this.style.backgroundColor = ''; } // 북마크 버튼 생성 if (config.controlButtons && config.bookmarkButton) { if (originalScrapButton) { var bookmarkButton = createNewItem( 'ion-android-bookmark', bookmarkButtonClickHandler, bookmarkButtonHoverHandler, bookmarkButtonLeaveHandler ); appendOrUpdateItem(bookmarkButton); // 북마크 버튼 색상을 업데이트하는 함수 function updateButtonColor() { var buttonText = originalScrapButton.querySelector('.result').textContent.trim(); bookmarkButton.style.backgroundColor = (buttonText === "스크랩 됨") ? '#007bff' : ''; } // 초기 호출 및 MutationObserver 설정 updateButtonColor(); var observer = new MutationObserver(updateButtonColor); observer.observe(originalScrapButton.querySelector('.result'), { childList: true, subtree: true }); } } // 북마크 버튼 핸들러 function bookmarkButtonClickHandler() { if (originalScrapButton) { originalScrapButton.click(); } else { console.error('원래의 스크랩 버튼을 찾을 수 없습니다.'); } } function bookmarkButtonHoverHandler() { this.style.backgroundColor = '#007bff'; } function bookmarkButtonLeaveHandler() { var buttonText = originalScrapButton.querySelector('.result').textContent.trim(); this.style.backgroundColor = (buttonText === "스크랩 됨") ? '#007bff' : ''; } // 닫기 버튼 생성 및 추가 if (config.controlButtons && config.closeButton) { var closeButton = createNewItem( 'ion-close-round', closeButtonClickHandler, closeButtonHoverHandler, closeButtonLeaveHandler ); appendOrUpdateItem(closeButton); } // 닫기 버튼 핸들러 function closeButtonClickHandler() { window.close(); } function closeButtonHoverHandler() { this.style.backgroundColor = 'red'; } function closeButtonLeaveHandler() { this.style.backgroundColor = ''; } }); } } } handleSettings(); if (window.location.href.includes('scrap_list')) { if (config.scrapList){ // arcaLiveScrapList(); setTimeout(arcaLiveScrapList, 0); } } // arcaLive(); setTimeout(arcaLive, 0); })();