// ==UserScript==
// @name 이터널 리턴 갤러리 통합 스크립트 (Beta)
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 이터널 리턴 마이너 갤러리와 인방 미니 갤러리를 통합해서 보여줍니다
// @author ㅇㅇ
// @match https://gall.dcinside.com/mgallery/board/lists*
// @grant GM_xmlhttpRequest
// ==/UserScript==
(function () {
'use strict';
// URL 파라미터 파싱 함수
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
return {
id: params.get('id'),
list_num: parseInt(params.get('list_num') || '50'),
page: parseInt(params.get('page') || '1'),
exception_mode: params.get('exception_mode'),
search_head: params.get('search_head'),
s_type: params.get('s_type')
};
}
// 게시글 파싱 함수 수정
function parsePostsList(html, isMini) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const posts = [];
const notices = [];
doc.querySelectorAll('.ub-content').forEach(row => {
const isNotice = row.classList.contains('notice');
const writerInfo = row.querySelector('.gall_writer');
const isAdmin = writerInfo.querySelector('b')?.textContent === '운영자';
const subject = row.querySelector('.gall_subject')?.textContent;
const isNoticeSubject = subject === '공지';
// 제목 색상 처리
const title = row.querySelector('.gall_tit').innerHTML;
const modifiedTitle = title.replace(
'gall_subject',
`gall_subject" style="color: ${isMini ? '#6f6dd8' : '#000'}`
);
const post = {
number: parseInt(row.querySelector('.gall_num').textContent.trim()),
title: modifiedTitle,
author: writerInfo.innerHTML,
date: parseDate(row.querySelector('.gall_date').textContent.trim()),
views: parseInt(row.querySelector('.gall_count').textContent.trim()),
recommend: parseInt(row.querySelector('.gall_recommend').textContent.trim()),
html: row.outerHTML.replace(
'gall_subject',
`gall_subject" style="color: ${isMini ? '#6f6dd8' : '#000'}`
),
isMini: isMini,
isNotice: isNotice,
isAdmin: isAdmin,
isNoticeSubject: isNoticeSubject
};
// 공지글이나 운영자 글이나 공지 말머리를 가진 글은 notices 배열에 추가
if (isNotice || isAdmin || isNoticeSubject) {
notices.push(post);
} else {
posts.push(post);
}
});
return { posts, notices };
}
// 날짜 파싱 함수 수정
function parseDate(dateStr) {
const now = new Date();
const seoulDate = new Date(now.toLocaleString("en-US", { timeZone: "Asia/Seoul" }));
// "HH:MM" 형식 (오늘)
if (dateStr.includes(':')) {
const [hours, minutes] = dateStr.split(':').map(Number);
const date = new Date(seoulDate.getFullYear(), seoulDate.getMonth(), seoulDate.getDate(), hours, minutes);
// 밤 12시 이후면 내일 날짜로 변경
if (hours < seoulDate.getHours() || (hours === seoulDate.getHours() && minutes <= seoulDate.getMinutes())) {
date.setDate(date.getDate() - 1);
}
return date;
}
// "MM.DD" 형식 (올해)
if (dateStr.match(/^\d{2}\.\d{2}$/)) {
const [month, day] = dateStr.split('.').map(Number);
return new Date(seoulDate.getFullYear(), month - 1, day);
}
// "YY.MM.DD" 형식
if (dateStr.match(/^\d{2}\.\d{2}\.\d{2}$/)) {
const [year, month, day] = dateStr.split('.').map(Number);
return new Date(2000 + year, month - 1, day);
}
return new Date(0); // 파싱 실패시 가장 오래된 날짜로
}
// 갤러리 크롤링 함수
async function crawlGallery(gallId, isMini, listNum, exceptionMode) {
const baseUrl = isMini ?
'https://gall.dcinside.com/mini/board/lists' :
'https://gall.dcinside.com/mgallery/board/lists/';
const posts = [];
const notices = [];
for (let page = 1; page <= 5; page++) {
const url = `${baseUrl}?id=${gallId}&page=${page}&list_num=${listNum}${exceptionMode ? '&exception_mode=' + exceptionMode : ''}`;
try {
const response = await fetch(url);
const html = await response.text();
const parsed = parsePostsList(html, isMini); // isMini 파라미터 추가
posts.push(...parsed.posts);
if (page === 1) notices.push(...parsed.notices);
} catch (error) {
console.error(`Error crawling page ${page}:`, error);
}
}
return { posts, notices };
}
// 메인 함수 수정
async function init() {
const params = getUrlParams();
// search_head나 s_type이 있으면 실행하지 않음
if (params.search_head || params.s_type) return;
// 현재 갤러리가 본갤이나 인갤이 아니면 실행하지 않음
if (params.id !== 'bser' && params.id !== 'ertv') return;
// 개념글 모드에서는 1페이지만 작동
if (params.exception_mode && params.page > 1) return;
// 일반 모드에서는 6페이지 이상이면 원본 페이지 그대로 표시
if (!params.exception_mode && params.page > 5) return;
const currentIsMinor = params.id === 'ertv';
// 본갤과 인갤 크롤링
const [mainGall, streamGall] = await Promise.all([
crawlGallery('bser', false, params.list_num, params.exception_mode),
crawlGallery('ertv', true, params.list_num, params.exception_mode)
]);
// 게시글 통합 및 정렬
const allPosts = [...mainGall.posts, ...streamGall.posts].sort((a, b) => {
// 날짜가 같은 경우 게시글 번호로 정렬
const dateCompare = b.date - a.date;
if (dateCompare === 0) {
// 같은 갤러리면 번호로, 다른 갤러리면 시간순
if (a.isMini === b.isMini) {
return b.number - a.number;
}
return b.number / (b.isMini ? 100 : 1) - a.number / (a.isMini ? 100 : 1);
}
return dateCompare;
});
// 현재 갤러리에 해당하는 공지글만 필터링
const currentGalleryNotices = currentIsMinor ?
streamGall.notices : mainGall.notices;
// 현재 페이지에 해당하는 게시글만 필터링
const startIdx = (params.page - 1) * params.list_num;
const endIdx = startIdx + params.list_num;
const currentPagePosts = allPosts.slice(startIdx, endIdx);
// 게시글 목록 갱신
const tbody = document.querySelector('.gall_list tbody');
if (!tbody) return;
tbody.innerHTML = '';
// 1페이지일 때만 공지글 표시
if (params.page === 1) {
// 공지글 먼저 추가 (운영자 공지 포함)
let noticeHtml = '';
document.querySelectorAll('.notice').forEach(notice => {
noticeHtml += notice.outerHTML;
});
tbody.insertAdjacentHTML('beforeend', noticeHtml);
// 현재 갤러리 공지글 추가
currentGalleryNotices.forEach(notice => {
tbody.insertAdjacentHTML('beforeend', notice.html);
});
}
// 일반 게시글 추가
currentPagePosts.forEach(post => {
tbody.insertAdjacentHTML('beforeend', post.html);
});
// 페이지네이션 업데이트 (개념글일 경우 1페이지만, 일반글일 경우 5페이지까지)
const maxPosts = params.exception_mode ?
Math.min(allPosts.length, params.list_num) :
Math.min(allPosts.length, params.list_num * 5);
updatePagination(maxPosts, params.list_num);
}
// 페이지네이션 업데이트 함수
function updatePagination(totalPosts, listNum) {
const maxPages = Math.min(Math.ceil(totalPosts / listNum), 10);
const pagination = document.querySelector('.bottom_paging_box');
if (!pagination) return;
// 페이지네이션 HTML 생성
let html = '';
for (let i = 1; i <= maxPages; i++) {
const currentUrl = new URL(window.location.href);
currentUrl.searchParams.set('page', i);
html += `<a href="${currentUrl.toString()}">${i}</a>`;
}
pagination.innerHTML = html;
}
// 스크립트 실행
init();
})();