Greasy Fork

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

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

目前为 2024-05-28 提交的版本。查看 最新版本

// ==UserScript==
// @name         아카라이브 미리보기 이미지, 모두 열기
// @version      1.19
// @icon         https://www.google.com/s2/favicons?sz=64&domain=arca.live
// @description  아카라이브 미리보기 이미지 생성, 모두 열기 생성, 그 외 잡다한 기능..
// @author       ChatGPT
// @match        https://arca.live/b/*
// @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==

// 설정 변수 false로 비활성화
var config = {
    openAllButton: true,      // 모두 열기 버튼 생성
    downloadImages: false,    // 모든 이미지 다운로드 버튼 생성
    countImages: true,        // 모든 이미지의 총 개수를 구하고 진행률 표시
    compressFiles: true,      // 모든 이미지를 압축해서 다운로드
    originalImage: false,     // 원본 이미지로 다운로드(체크 해제시 webp저장)
    numberDownload: false,    // 게시글 번호를 누르면 해당 게시글 이미지 다운로드
    thumbnail: true,          // 미리보기 이미지 생성
    thumbWidth: 100,          // 미리보기 이미지 너비
    thumbHeight: 62,          // 미리보기 이미지 높이
    thumbnailHover: true,     // 미리보기 이미지 마우스 오버시 보이게
    thumbnailBlur: true,      // 블러 효과를 적용할지 여부
    blurAmount: 2,            // 블러 효과의 정도
    originalThumbnail: true,  // 개념글 미리보기 이미지 클릭 시 원본 이미지 불러오기
    thumbnailHoverBest: true, // 개념글 미리보기 이미지 마우스 오버시 보이게
    closeButton: false,       // 하단 우측 창닫기 버튼 생성
    bookmarkButton: false,    // 하단 우측 스크랩 버튼 생성
    downloadButton: false,    // 하단 우측 다운로드 버튼 생성
    test01: false,            // 채널 기본 이미지로 된 미리보기 이미지 마우스 오버시 해당 게시글 다른 이미지 가져오기
    test02: false             // 채널 기본 이미지로 된 미리보기 이미지를 해당 게시글 다른 이미지로 대체
};

// 설정 변수에 대한 설명
var descriptions = {
    openAllButton: '모두 열기 버튼 생성',
    downloadImages: '모든 이미지 다운로드 버튼 생성',
    countImages: '모든 이미지의 총 개수를 구하고 진행률 표시',
    compressFiles: '모든 이미지를 압축해서 다운로드',
    originalImage: '원본 이미지로 다운로드(체크 해제시 webp저장)',
    numberDownload: '게시글 번호를 누르면 해당 게시글 이미지 다운로드',
    thumbnail: '미리보기 이미지 생성',
    thumbWidth: '미리보기 이미지 너비',
    thumbHeight: '미리보기 이미지 높이',
    thumbnailHover: '미리보기 이미지 마우스 오버시 보이게',
    thumbnailBlur: '블러 효과를 적용할지 여부',
    blurAmount: '블러 효과의 정도',
    originalThumbnail: '개념글 미리보기 이미지 클릭 시 원본 이미지 불러오기',
    thumbnailHoverBest: '개념글 미리보기 이미지 마우스 오버시 보이게',
    closeButton: '하단 우측 창닫기 버튼 생성',
    bookmarkButton: '하단 우측 스크랩 버튼 생성',
    downloadButton: '하단 우측 다운로드 버튼 생성',
    test01: '채널 기본 이미지로 된 미리보기 이미지 마우스 오버시 해당 게시글 다른 이미지 가져오기',
    test02: '채널 기본 이미지로 된 미리보기 이미지를 해당 게시글 다른 이미지로 대체'
};

// 설정 창을 생성하는 함수
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'; // flexbox 사용
    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);

    // 설정 변수를 반복하여 설정 입력 요소를 생성
    for (var key in config) {
        if (config.hasOwnProperty(key)) {
            // 변수를 담을 div 요소 생성
            var configDiv = document.createElement('div');
            configDiv.style.marginBottom = '5px'; // 하단 간격 지정
            configDiv.style.display = 'flex'; // flexbox 사용
            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]; // 체크박스의 경우 checked 속성 사용
            if (input.type === 'text') {
                input.style.width = '40px'; // 입력 창의 너비를 40px로 설정
                input.style.height = '20px'; // 입력 창의 높이를 15px로 설정
                input.style.padding = '0 5px'; // 입력 창의 좌우 패딩을 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));

            // div에 레이블과 인풋 추가
            configDiv.appendChild(label);
            configDiv.appendChild(input);

            // 설정 창에 div 추가
            settingsWindow.appendChild(configDiv);
        }
    }

    var tooltip = document.createElement('div');
    tooltip.innerHTML = '마우스를 올리면 설명이 나옵니다'; // 설정 창 제목
    tooltip.style.fontSize = '12px'; // 제목 폰트 크기
    // tooltip.style.fontWeight = 'bold'; // 제목 폰트 굵기
    tooltip.style.marginTop = '5px'; // 하단 간격 지정
    tooltip.style.marginBottom = '10px'; // 하단 간격 지정
    tooltip.style.color = 'gray'; // 글자색 회색으로 설정
    settingsWindow.appendChild(tooltip);

    // 확인과 취소 버튼을 담을 div 요소 생성
    var buttonContainer = document.createElement('div');
    buttonContainer.style.display = 'flex'; // flexbox 사용
    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);

    // 설정 창을 body에 추가
    document.body.appendChild(settingsWindow);
}

// 설정 값을 로컬 저장소에 저장
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]);
        }
    }
}

// 저장된 설정 값을 로드
loadConfig();

// 설정 버튼 생성
var settingsButtonCommand = GM_registerMenuCommand('설정', function() {
    createSettingsWindow();
});

// 모두 열기 버튼 생성
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.downloadImages) {
    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 ? `${sanitizedTitle}_${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 .ion-android-star');
            let title = '';
            if (titleElement) {
                const titleText = titleElement.nextSibling.nodeValue.trim();
                title = titleText;
            }

            // 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.numberDownload) {
        document.querySelectorAll('.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 .ion-android-star');
            let title = '';
            if (titleElement) {
                const titleText = titleElement.nextSibling.nodeValue.trim();
                title = titleText;
            }

            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 sanitizedTitle = title.replace(/[^a-zA-Z0-9가-힣\s]/g, '_'); // 파일 이름에 사용할 수 있도록 제목을 정제
                const numberedFileName = compressFiles ? `${sanitizedTitle}_${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.thumbnailBlur) {
                    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.originalThumbnail) {
    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.thumbnailHoverBest) {
    // 이미지 요소 선택
    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();
            });
        });
    });
}

document.addEventListener('DOMContentLoaded', function() {
    if (config.closeButton || config.bookmarkButton || config.downloadButton) {
        'use strict';

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

        // Function to create a new list item element
        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 = ''; // Reset background color on mouse leave
                });
            }
            return newItem;
        }

        // Function to append or insert a new item into the navigation control list
        function appendOrUpdateItem(newItem) {
            if (navControl) {
                if (navControl.children.length > 0) {
                    navControl.insertBefore(newItem, navControl.firstElementChild);
                } else {
                    navControl.appendChild(newItem);
                }
            } else {
                console.error('Navigation control list not found.');
            }
        }

        // Close button click handler
        function closeButtonClickHandler() {
            window.close();
        }

        // Close button hover handler
        function closeButtonHoverHandler() {
            this.style.backgroundColor = 'red';
        }

        // Bookmark button click handler
        function bookmarkButtonClickHandler() {
            if (originalScrapButton) {
                originalScrapButton.click();
            } else {
                console.error('Original scrap button not found.');
            }
        }

        // Download button click handler
        function downloadButtonClickHandler() {
            if (imageToZipBtn) {
                imageToZipBtn.click();
            } else {
                console.error('Image to Zip button not found.');
            }
        }

        if (config.downloadButton) {
            if (imageToZipBtn) {
                var downloadButton = createNewItem('ion-android-download', downloadButtonClickHandler);
                appendOrUpdateItem(downloadButton);
            }
        }

        if (config.bookmarkButton) {
            if (originalScrapButton) {
                var bookmarkButton = createNewItem('ion-android-bookmark', bookmarkButtonClickHandler);
                appendOrUpdateItem(bookmarkButton);

                // Update bookmark button color
                function updateButtonColor() {
                    var buttonText = originalScrapButton.querySelector('.result').textContent.trim();
                    bookmarkButton.style.backgroundColor = (buttonText === "스크랩 됨") ? '#007bff' : '';
                }

                // Call updateButtonColor initially and set up mutation observer
                updateButtonColor();
                var observer = new MutationObserver(updateButtonColor);
                observer.observe(originalScrapButton.querySelector('.result'), { childList: true, subtree: true });
            }
        }

        // Create and append/close buttons
        if (config.closeButton) {
            var closeButton = createNewItem('ion-close-round', closeButtonClickHandler, closeButtonHoverHandler);
            appendOrUpdateItem(closeButton);
        }
    }
});

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