Greasy Fork

Greasy Fork is available in English.

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

Extract image URL from specific element and log it to console

当前为 2024-04-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         아카라이브 미리보기 이미지, 모두 열기
// @namespace    Violentmonkey Scripts
// @version      1.11
// @icon         https://www.google.com/s2/favicons?sz=64&domain=arca.live
// @description  Extract image URL from specific element and log it to console
// @author       You
// @match        https://arca.live/b/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// ==/UserScript==

// 설정 변수 false로 비활성화
var config = {
    openAllButton: true,      // 모두 열기 버튼 생성
    thumbnail: true,          // 미리보기 이미지 생성
    thumbWidth: 100,          // 미리보기 이미지 너비
    thumbHeight: 62,          // 미리보기 이미지 높이
    thumbnailHover: true,     // 미리보기 이미지 마우스 오버시 보이게
    thumbnailBlur: true,      // 블러 효과를 적용할지 여부
    blurAmount: 2,            // 블러 효과의 정도
    originalThumbnail: true,  // 개념글 미리보기 이미지 클릭 시 원본 이미지 불러오기
    thumbnailHoverBest: true, // 개념글 미리보기 이미지 마우스 오버시 보이게
    test01: false,            // 채널 기본 이미지로 된 미리보기 이미지 마우스 오버시 해당 게시글 다른 이미지 가져오기
    test02: false             // 채널 기본 이미지로 된 미리보기 이미지를 해당 게시글 다른 이미지로 대체
};

// 설정 창을 생성하는 함수
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 = '200px'; // 너비 지정
    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 = '18px'; // 제목 폰트 크기
    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.addEventListener('mouseover', function() {
                var labelText = this.innerText.split(':')[0].trim(); // 레이블의 텍스트에서 변수 이름 추출
            });

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

    // 확인과 취소 버튼을 담을 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);
}

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.width = config.thumbWidth + 'px';
            vcolThumb.style.height = config.thumbHeight + 'px';
            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 = '100%';
                thumbImage.style.height = '100%';
                thumbImage.style.objectFit = 'cover';
                // 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();
            });
        });
    });
}