// ==UserScript==
// @name 아카라이브 미리보기 이미지, 모두 열기
// @namespace Violentmonkey Scripts
// @version 1.09
// @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: true, // 채널 기본 이미지로 된 미리보기 이미지 마우스 오버시 해당 게시글 다른 이미지 가져오기
test02: true // 채널 기본 이미지로 된 미리보기 이미지를 해당 게시글 다른 이미지로 대체
};
// 설정 창을 생성하는 함수
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(blackPercentage);
} else {
// console.log(blackPercentage);
callback(false);
}
};
img.onerror = function() {
// 이미지 로드 실패 시에도 콜백 호출하여 처리
callback(false);
};
img.src = image.src + "&type=orig";
}
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');
// 모든 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 videoPosterSrc = firstTag.getAttribute('poster');
if (firstTag.tagName.toLowerCase() === 'img') {
img.src = type ? firstTag.src + "&type=list" : firstTag.src.replace("&type=list", '');
} else if (firstTag.tagName.toLowerCase() === 'video') {
if (videoOriginalSrc) {
img.src = type ? videoOriginalSrc + "&type=list" : videoOriginalSrc.replace("&type=list", '');
} else {
if (videoPosterSrc) {
img.src = type ? videoPosterSrc + "&type=list" : videoPosterSrc.replace("&type=list", '');
}
}
} else {
// console.log("???");
}
}
});
}
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');
// console.log(vrowPreviewImg.src);
// thumbImage.src = vrowPreviewImg.src.replace("&type=list", '');
thumbImage.src = vrowPreviewImg.src;
// console.log(thumbImage.src);
thumbImage.style.width = '100%';
thumbImage.style.height = '100%';
thumbImage.style.objectFit = 'cover';
// console.log(thumbImage);
if (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) {
if (config.test01 || config.test02) {
checkBlackEdge(vrowPreviewImg, function(hasBlackEdge) {
if (hasBlackEdge) {
setSecondImg(element, vrowPreviewImg, 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();
});
});
});
}