아카라이브 미리보기 이미지, 모두 열기

아카라이브 미리보기 이미지 생성, 모두 열기 생성, 그 외 잡다한 기능..

// ==UserScript==
// @name         아카라이브 미리보기 이미지, 모두 열기
// @version      1.29
// @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
// @grant        GM_xmlhttpRequest
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @namespace Violentmonkey Scripts
// ==/UserScript==

(function() {
    'use strict';

    var config = {
        openAllButton: true,
        downAllButton: false,
        countImages: true,
        compressFiles: true,
        originalImage: false,
        downNumber: false,
        thumbnail: true,
        thumbWidth: 100,
        thumbHeight: 62,
        thumbHover: true,
        thumbBlur: true,
        blurAmount: 2,
        origThumb: true,
        thumbHoverBest: true,
        closeButton: false,
        bookmarkButton: true,
        downButton: false,
        scrapList: true,
        filterBold: true,
        test01: false,
        test02: false
    };

    function handleSettings() {
        var descriptions = {
            openAllButton: '모두 열기 버튼 생성',
            downAllButton: '모든 이미지 다운로드 버튼 생성',
            countImages: '모든 이미지의 총 개수를 구하고 진행률 표시',
            compressFiles: '모든 이미지를 압축해서 다운로드',
            originalImage: '원본 이미지로 다운로드(체크 해제시 webp저장)',
            downNumber: '게시글 번호를 누르면 해당 게시글 이미지 다운로드',
            thumbnail: '미리보기 이미지 생성',
            thumbWidth: '미리보기 이미지 너비',
            thumbHeight: '미리보기 이미지 높이',
            thumbHover: '미리보기 이미지 마우스 오버시 보이게',
            thumbBlur: '블러 효과를 적용할지 여부',
            blurAmount: '블러 효과의 정도',
            origThumb: '개념글 미리보기 이미지 클릭 시 원본 이미지 불러오기',
            thumbHoverBest: '개념글 미리보기 이미지 마우스 오버시 보이게',
            closeButton: '하단 우측 창닫기 버튼 생성',
            bookmarkButton: '하단 우측 스크랩 버튼 생성',
            downButton: '하단 우측 다운로드 버튼 생성',
            scrapList: '스크랩한 게시글 채널별, 탭별 필터링',
            filterBold: '필터링한 게시글 볼드체로 변경',
            test01: '채널 기본 이미지로 된 미리보기 이미지 마우스 오버시 해당 게시글 다른 이미지 가져오기',
            test02: '채널 기본 이미지로 된 미리보기 이미지를 해당 게시글 다른 이미지로 대체'
        };

        var mainConfigKeys = Object.keys(descriptions).filter(key =>
            !['thumbWidth', 'thumbHeight', 'thumbHover', 'thumbBlur', 'blurAmount', 'origThumb'].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 createThumbnailSettingsWindow() {
            var existingSettingsWindow = document.getElementById('thumbnailSettingsWindow');
            if (existingSettingsWindow) {
                existingSettingsWindow.remove();
            }

            var settingsWindow = document.createElement('div');
            settingsWindow.id = 'thumbnailSettingsWindow';
            settingsWindow.style.position = 'fixed';
            settingsWindow.style.top = '50%';
            settingsWindow.style.left = '50%';
            settingsWindow.style.transform = 'translate(-50%, -50%)';
            settingsWindow.style.width = '250px';
            settingsWindow.style.padding = '20px';
            settingsWindow.style.background = '#ffffff';
            settingsWindow.style.border = '1px solid #cccccc';
            settingsWindow.style.borderRadius = '10px';
            settingsWindow.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.3)';
            settingsWindow.style.zIndex = '9999';
            settingsWindow.style.textAlign = 'left';
            settingsWindow.style.display = 'flex';
            settingsWindow.style.flexDirection = 'column';
            settingsWindow.style.alignItems = 'center';

            var settingsTitle = document.createElement('div');
            settingsTitle.innerHTML = 'Thumbnail';
            settingsTitle.style.fontSize = '24px';
            settingsTitle.style.fontWeight = 'bold';
            settingsTitle.style.marginBottom = '10px';
            settingsTitle.style.textAlign = 'center'; // Add this line to center-align the title
            settingsWindow.appendChild(settingsTitle);

            var keys = ['thumbWidth', 'thumbHeight', 'thumbHover', 'thumbBlur', 'blurAmount', 'origThumb'];

            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);
                settingsWindow.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();
                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);
        }

        function createSettingsWindow() {
            var existingSettingsWindow = document.getElementById('settingsWindow');
            if (existingSettingsWindow) {
                existingSettingsWindow.remove();
            }

            var settingsWindow = document.createElement('div');
            settingsWindow.id = 'settingsWindow';
            settingsWindow.style.position = 'fixed';
            settingsWindow.style.top = '50%';
            settingsWindow.style.left = '50%';
            settingsWindow.style.transform = 'translate(-50%, -50%)';
            settingsWindow.style.width = '250px';
            settingsWindow.style.padding = '20px';
            settingsWindow.style.background = '#ffffff';
            settingsWindow.style.border = '1px solid #cccccc';
            settingsWindow.style.borderRadius = '10px';
            settingsWindow.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.3)';
            settingsWindow.style.zIndex = '9999';
            settingsWindow.style.textAlign = 'left';
            settingsWindow.style.display = 'flex';
            settingsWindow.style.flexDirection = 'column';
            settingsWindow.style.alignItems = 'center';

            var settingsTitle = document.createElement('div');
            settingsTitle.innerHTML = 'Settings';
            settingsTitle.style.fontSize = '24px';
            settingsTitle.style.fontWeight = 'bold';
            settingsTitle.style.marginBottom = '10px';
            settingsWindow.appendChild(settingsTitle);

            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 === 'thumbnail') {
                    var settingsIcon = document.createElement('span');
                    settingsIcon.innerHTML = '⚙️';
                    settingsIcon.style.cursor = 'pointer';
                    settingsIcon.style.marginLeft = '3px';
                    settingsIcon.style.marginBottom = '2px';
                    settingsIcon.addEventListener('click', function() {
                        createThumbnailSettingsWindow();
                    });
                    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() {
        // Add the filter div before the list header
        const header = document.querySelector('.list-table ');
        const filterContainerDiv = document.createElement('div');
        filterContainerDiv.style.display = 'flex';
        filterContainerDiv.style.marginBottom = '10px';

        // 페이징 요소의 HTML을 가져옵니다.
        const paginationHTML = document.querySelector('.pagination.justify-content-center');
        if (paginationHTML) {
            // 새로운 div 요소를 생성하고 HTML 코드를 삽입합니다.
            const newDiv = document.createElement('div');
            newDiv.innerHTML = paginationHTML.outerHTML;

            // 헤더 요소 앞에 새로운 HTML 코드를 추가합니다.
            header.parentNode.insertBefore(newDiv, header);
        }

        // Create filter div
        const filterDiv = document.createElement('div');
        filterDiv.className = 'filterDiv'; // 클래스 이름을 여기에 추가하세요
        filterDiv.style.display = 'flex';
        filterDiv.style.marginRight = '10px';

        // Create "모두 열기" button
        const openLinksButton = document.createElement('a');
        openLinksButton.className = 'btn btn-sm btn-primary float-left';
        openLinksButton.href = '#';
        openLinksButton.innerHTML = '<span class="ion-android-list" style="margin-right: 5px;"></span><span> 모두 열기 </span>';
        openLinksButton.style.display = 'flex'; // flexbox 사용
        openLinksButton.style.alignItems = 'center'; // 세로축 중앙 정렬
        openLinksButton.style.justifyContent = 'center'; // 가로축 중앙 정렬
        filterContainerDiv.appendChild(openLinksButton);

        // Create channel filter
        const channelFilter = document.createElement('select');
        channelFilter.className = 'form-control select-list-type';
        channelFilter.name = 'sort';
        channelFilter.style.width = 'auto'; // 채널 필터의 폭을 150px로 지정합니다.
        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.width = 'auto'; // 채널 필터의 폭을 150px로 지정합니다.
        const defaultTabOption = document.createElement('option');
        defaultTabOption.value = '';
        defaultTabOption.text = '탭 선택';
        tabFilter.appendChild(defaultTabOption);
        filterDiv.appendChild(tabFilter);

        filterContainerDiv.appendChild(filterDiv);
        header.parentNode.insertBefore(filterContainerDiv, 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';
                    }
                }
            });
        }

        // Open visible links in new windows
        function openVisibleLinks(event) {
            event.preventDefault();
            const visiblePosts = document.querySelectorAll('.vrow.column');
            visiblePosts.forEach(post => {
                if (post.style.display !== 'none') {
                    const link = post
                    if (link && link.href) {
                        window.open(link.href, '_blank');
                    }
                }
            });
        }

        channelFilter.addEventListener('change', updateTabFilter);
        tabFilter.addEventListener('change', filterPosts);
        openLinksButton.addEventListener('click', openVisibleLinks);
    }

    function arcaLive() {
        // 모두 열기 버튼 생성
        if (config.openAllButton) {
            var openAllButton = document.createElement('a');
            openAllButton.className = 'btn btn-sm btn-primary float-left';
            openAllButton.href = '#';
            openAllButton.innerHTML = '<span class="ion-android-list"></span><span> 모두 열기 </span>';

            openAllButton.addEventListener('click', function(event) {
                event.preventDefault();
                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')) {
                        window.open(href, '_blank');
                    }
                });
            });

            var targetElement = document.querySelector('.form-control.select-list-type');
            targetElement.parentNode.insertBefore(openAllButton, targetElement);
        }

        // 이미지와 동영상 다운로드 버튼 생성
        if (config.downAllButton) {
            var downloadMediaButton = document.createElement('a');
            downloadMediaButton.className = 'btn btn-sm btn-success float-left';
            downloadMediaButton.href = '#';
            downloadMediaButton.innerHTML = '<span class="ion-android-download"></span><span> 다운로드 </span>';
            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을 배열에 추가
                    }
                });

                if (config.countImages) {
                    // 페이지의 상단부터 순서대로 다운로드 시작
                    const totalImages = await getTotalImages(mediaUrls);
                    const confirmMessage = `다운로드해야 할 이미지의 총 개수는 ${totalImages}개입니다.
        계속해서 다운로드 하시겠습니까?`;
                    if (confirm(confirmMessage)) {
                        progressBar.style.width = '0%'; // 초기 너비는 0%
                        progressBar.style.backgroundColor = 'yellow'; // 프로그레스 바 색상
                        await downloadMediaSequentially(mediaUrls, totalImages, config.compressFiles); // config.compressFiles 변수 전달
                    }
                } else {
                    // 프로그레스 바를 사용하지 않을 경우에는 다운로드 여부를 확인하는 창 띄우기
                    const confirmMessage = "모든 게시글의 이미지를 한 번에 다운로드합니다.\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);
        }

        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();
        }

        document.addEventListener("DOMContentLoaded", function() {
            if (config.downNumber) {
                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();
                    }
                }
            }
        });

        function checkBlackEdge(image, callback) {
            var img = new Image();
            img.crossOrigin = 'anonymous';
            img.onload = function() {
                var canvas = document.createElement('canvas');
                var ctx = canvas.getContext('2d');
                canvas.width = img.width;
                canvas.height = img.height;
                ctx.drawImage(img, 0, 0, img.width, img.height);

                var edgeSize = Math.min(img.width, img.height) * 0.1;
                var imageData = ctx.getImageData(0, 0, img.width, img.height);

                var totalPixels = 0;
                var blackPixels = 0;

                for (var x = 0; x < img.width; x++) {
                    for (var y = 0; y < img.height; y++) {
                        if (x < edgeSize || x >= img.width - edgeSize || y < edgeSize || y >= img.height - edgeSize) {
                            totalPixels++;
                            var index = (y * img.width + x) * 4;
                            var pixelData = [
                                imageData.data[index],     // Red
                                imageData.data[index + 1], // Green
                                imageData.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);
                }
                // console.log(blackPercentage);
            };
            img.onerror = function() {
                // 이미지 로드 실패 시에도 콜백 호출하여 처리
                callback(false);
            };
            img.src = image.src + "&type=list"; // img.src = image.src + "&type=list";
        }

        function setSecondImg(element, img , type) {
            var href = element.href;
            // GM_xmlhttpRequest를 사용하여 링크의 페이지 내용을 가져옵니다.
            GM_xmlhttpRequest({
                method: "GET",
                url: href,
                onload: function(response) {
                    // 가져온 페이지의 내용을 HTML 요소로 변환합니다.
                    var parser = new DOMParser();
                    var htmlDoc = parser.parseFromString(response.responseText, "text/html");

                    // "fr-view article-content" 클래스를 가진 div 요소를 찾습니다.
                    var contentDiv = htmlDoc.querySelector('div.fr-view.article-content');

                    // console.log(contentDiv);
                    // 모든 p 태그를 가져옵니다.
                    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;
                        }
                    }

                    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;
                        }
                    } else {
                      // console.log("???");
                    }

                    if(config.test02){
                        img.src = changeImgUrl;
                    }
                    // console.log(firstTag.tagName);
                    element.querySelector('.vrow-preview img').src = changeImgUrl;
                }
            });
        }

        document.addEventListener('DOMContentLoaded', function() {
            if (config.thumbnail) {
                document.querySelectorAll('a.vrow.column:not(.notice)').forEach(function(element) {
                    var vcolId = element.querySelector('.vrow-top .vcol.col-id');
                    var vcolTitle = element.querySelector('.vrow-top .vcol.col-title');
                    vcolId.style.margin = '0';

                    var vcolThumb = document.createElement('span');
                    vcolThumb.className = 'vcol col-thumb';
                    vcolThumb.style.borderRadius = '3px';
                    element.querySelector('.vrow-inner').appendChild(vcolThumb);
                    vcolTitle.parentNode.insertBefore(vcolThumb, vcolTitle);

                    var vrowPreview = element.querySelector('.vrow-preview');

                    function createThumbnail() {
                        var vrowPreviewImg = vrowPreview ? vrowPreview.querySelector('img') : null;
                        if (!vrowPreviewImg) return;

                        element.style.height = 'auto';
                        element.style.paddingTop = '3.75px';
                        element.style.paddingBottom = '3.75px';

                        var thumbImage = document.createElement('img');
                        thumbImage.src = vrowPreviewImg.src;
                        thumbImage.style.width = config.thumbWidth + 'px';
                        thumbImage.style.height = config.thumbHeight + 'px';
                        thumbImage.style.objectFit = 'cover';

                        vcolId.style.height = config.thumbHeight + 'px';
                        vcolId.style.display = 'flex';
                        vcolId.style.alignItems = 'center'; // 세로 가운데 정렬
                        vcolId.style.justifyContent = 'center'; // 가로 가운데 정렬

                        // console.log(thumbImage);
                        if (config.test01 || config.test02){
                            checkBlackEdge(thumbImage, function(hasBlackEdge) {
                                if (hasBlackEdge){
                                    setSecondImg(element, thumbImage, true);
                                }
                            });
                        }

                        if (config.thumbBlur) {
                            thumbImage.style.filter = 'blur(' + config.blurAmount + 'px)';
                            thumbImage.addEventListener('mouseenter', function() {
                                thumbImage.style.filter = 'none';
                            });
                            thumbImage.addEventListener('mouseleave', function() {
                                thumbImage.style.filter = 'blur(' + config.blurAmount + 'px)';
                            });
                        }

                        vcolThumb.appendChild(thumbImage);

                        vrowPreview.style.display = 'none';
                        vrowPreview.style.width = '30rem';
                        vrowPreview.style.height = 'auto';
                        vrowPreview.style.top = 'auto';
                        vrowPreview.style.left = '13.5rem';

                        var thumbImageValue = false;
                        thumbImage.addEventListener('mouseenter', function() {
                            if (thumbImageValue == false) {
                                vrowPreviewImg.src = vrowPreviewImg.src.replace("&type=list", '');
                                thumbImageValue = true;
                            }
                            vrowPreview.style.display = null;
                        });
                        thumbImage.addEventListener('mouseleave', function() {
                            vrowPreview.style.display = 'none';
                        });
                    }

                    function tryCreateThumbnail(retryCount) {
                        if (retryCount >= 3) 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.closeButton || config.bookmarkButton || config.downButton) {
            'use strict';

            var navControl = document.querySelector('.nav-control');
            var originalScrapButton = document.querySelector('.scrap-btn');

            // 새로운 리스트 아이템 요소를 생성하는 함수
            function createNewItem(iconClass, clickHandler, hoverHandler) {
                var newItem = document.createElement('li');
                newItem.innerHTML = '<span class="' + iconClass + '"></span>';
                newItem.addEventListener('click', clickHandler);
                if (hoverHandler) {
                    newItem.addEventListener('mouseenter', hoverHandler);
                    newItem.addEventListener('mouseleave', function() {
                        newItem.style.backgroundColor = ''; // 마우스를 뗐을 때 배경색 초기화
                    });
                }
                return newItem;
            }

            // 새로운 아이템을 내비게이션 컨트롤 리스트에 추가하거나 업데이트하는 함수
            function appendOrUpdateItem(newItem) {
                if (navControl) {
                    if (navControl.children.length > 0) {
                        navControl.insertBefore(newItem, navControl.firstElementChild);
                    } else {
                        navControl.appendChild(newItem);
                    }
                } else {
                    console.error('내비게이션 컨트롤 리스트를 찾을 수 없습니다.');
                }
            }

            // 닫기 버튼 클릭 핸들러
            function closeButtonClickHandler() {
                window.close();
            }

            // 닫기 버튼 호버 핸들러
            function closeButtonHoverHandler() {
                this.style.backgroundColor = 'red';
            }

            // 북마크 버튼 클릭 핸들러
            function bookmarkButtonClickHandler() {
                if (originalScrapButton) {
                    originalScrapButton.click();
                } else {
                    console.error('원래의 스크랩 버튼을 찾을 수 없습니다.');
                }
            }

            // 다운로드 버튼 클릭 핸들러
            function downloadButtonClickHandler() {
                if (imageToZipBtn) {
                    imageToZipBtn.click();
                } else {
                    console.error('이미지를 Zip으로 변환하는 버튼을 찾을 수 없습니다.');
                }
            }
            if (config.bookmarkButton) {
                if (originalScrapButton) {
                    var bookmarkButton = createNewItem('ion-android-bookmark', bookmarkButtonClickHandler);
                    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 });
                }
            }

            // 닫기 버튼 생성 및 추가
            if (config.closeButton) {
                var closeButton = createNewItem('ion-close-round', closeButtonClickHandler, closeButtonHoverHandler);
                appendOrUpdateItem(closeButton);
            }

            // DOMContentLoaded 이벤트 사용하여 다운로드 버튼 생성
            document.addEventListener('DOMContentLoaded', function() {
                if (config.downButton) {
                    setTimeout(function() {
                        var imageToZipBtn = document.querySelector('#imageToZipBtn');
                        if (imageToZipBtn) {
                            var downloadButton = createNewItem('ion-android-download', downloadButtonClickHandler);
                            appendOrUpdateItem(downloadButton);
                        }
                    }, 100); // 1000 밀리초 (1초) 지연
                }
            });
        }


        document.addEventListener('DOMContentLoaded', function() {
            // 요소를 찾음
            var targetElement = document.getElementById('imageToZipBtn');
            var downloadButton = document.querySelector('li span.ion-android-download');
            if (targetElement && downloadButton) {
                // 이벤트 핸들러 등록
                targetElement.addEventListener('click', function() {
                    // 0.1초마다 실행되는 함수
                    var intervalId = setInterval(function() {
                        // 요소의 자식 노드 확인
                        var downloadProgress = targetElement.querySelector('.download-progress');
                        if (downloadProgress) {
                            var width = parseFloat(downloadProgress.style.width);
                            // 다운로드 버튼이 있는 리스트 아이템에 아래서 위로 채워지는 효과를 줍니다.
                            downloadButton.parentElement.style.background = `linear-gradient(to top, green ${width}%, transparent ${width}%)`;
                        } else {
                            // downloadProgress가 없으면 intervalId를 사용하여 반복을 중지합니다.
                            downloadButton.parentElement.style.background = `linear-gradient(to top, green 100%, transparent 100%)`;
                            clearInterval(intervalId);
                        }
                    }, 100);
                });
            }
        });
    }

    function filterBold() {
        const filters = document.querySelectorAll('.vrow.column.filtered.filtered-keyword .title');

        filters.forEach(element => {
            // 타이틀의 텍스트를 볼드체로 변경
            element.style.fontWeight = 'bold';
        });
    }

    handleSettings();

    if (config.filterBold) {
        document.addEventListener('DOMContentLoaded', filterBold);
    }

    if (window.location.href.includes('https://arca.live/u/scrap_list')) {
        if (config.scrapList){
            arcaLiveScrapList();
        }
    } else {
        arcaLive();
    }
})();