Soop 채널(방송국)의 프로필/배너 이미지를 닉네임 파일명으로 다운로드 (커스텀 토스트, 기본이미지 판별, toast-box 내부 append)
当前为
// ==UserScript==
// @name SOOP 채널 프로필 & 배너 이미지 다운로드
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Soop 채널(방송국)의 프로필/배너 이미지를 닉네임 파일명으로 다운로드 (커스텀 토스트, 기본이미지 판별, toast-box 내부 append)
// @author WakViewer
// @match https://www.sooplive.co.kr/station/*
// @icon https://res.sooplive.co.kr/afreeca.ico
// @grant GM_download
// @grant unsafeWindow
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-end
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let shortcutKey = GM_getValue('shortcutKey', 'Y').toLowerCase();
// ===== 정확한 위치(네가 준 셀렉터) =====
const SELECTORS = {
bannerDiv : '#layout_container__S7ueh > div.ChannelVisual_channelVisual__a2JA_ > div.TopBanner_TopBannerWrap__1Z87K',
profileImg: '#layout_container__S7ueh > div.ChannelVisual_channelVisual__a2JA_ > div.ChannelVisual_channelInfoWrapper__ovnvh > div > div.StreamerInfo_streamer__ZhKoB > div.StreamerInfo_profileImg__EC1En > span > div > img',
nicknameP : '#layout_container__S7ueh > div.ChannelVisual_channelVisual__a2JA_ > div.ChannelVisual_channelInfoWrapper__ovnvh > div > div.StreamerInfo_streamer__ZhKoB > div:nth-child(2) > div.StreamerInfo_nicknameWrapper__NFtU2 > p'
};
// 기본 이미지(있으면 "없음" 처리)
const DEFAULTS = {
profilePrefix: 'https://res.sooplive.co.kr/images/svg/thumb_profile.svg',
bannerLight : 'https://res.sooplive.co.kr/images/channel/ChannelVisualImageLight.jpg',
bannerDark : 'https://res.sooplive.co.kr/images/channel/ChannelVisualImageDark.jpg'
};
// ===== 토스트: body > div.toast-box 안에 자식으로 <div><p>message</p></div> 생성 =====
function ensureToastBox() {
let box = document.querySelector('body > div.toast-box');
if (!box) {
box = document.createElement('div');
box.className = 'toast-box';
document.body.appendChild(box);
}
return box;
}
function showToast(message) {
const box = ensureToastBox();
const item = document.createElement('div');
const p = document.createElement('p');
p.textContent = message;
item.appendChild(p);
box.appendChild(item);
// 3초 뒤 사라짐
setTimeout(() => {
if (item && item.parentNode) item.parentNode.removeChild(item);
}, 3000);
}
// ===== 유틸 =====
function parseBgUrlFromStyle(styleStr) {
if (!styleStr) return '';
const m = styleStr.match(/url\((['"]?)(https?:\/\/[^)]+)\1\)/i);
return m ? m[2] : '';
}
function ensureExtByUrl(baseName, url) {
const m = url.match(/\.([a-z0-9]+)(?:[?#]|$)/i);
const ext = (m ? m[1] : 'jpg').toLowerCase();
return baseName.endsWith('.' + ext) ? baseName : `${baseName}.${ext}`;
}
function sanitizeFilenameBase(s) {
// 윈도우 금지문자만 치환 (♥ 등 유니코드는 유지)
return s.replace(/[\\/:*?"<>|]/g, '_').trim();
}
function getChannelIdFromUrl() {
const parts = location.pathname.split('/').filter(Boolean); // ["station","<id>"]
return parts[1] || 'streamer';
}
function getNickname() {
// 닉네임 우선
const nick = document.querySelector(SELECTORS.nicknameP)?.textContent?.trim();
if (nick) return nick;
// 폴백: 프로필 alt
const alt = document.querySelector(SELECTORS.profileImg)?.getAttribute('alt')?.trim();
if (alt) return alt;
// 최종: station/<id>
return getChannelIdFromUrl();
}
function downloadImage(url, filenameBase) {
const safeBase = sanitizeFilenameBase(filenameBase);
const name = ensureExtByUrl(safeBase, url);
GM_download({
url,
name,
onerror: (err) => {
console.error(`Failed to download ${name}:`, err);
// 토스트는 요청된 6개 메시지만 사용
}
});
}
// ===== 프로필 =====
function downloadProfile() {
const nick = getNickname(); // 예: 꽃유이♥
const imgEl = document.querySelector(SELECTORS.profileImg);
const src = imgEl?.src || '';
// 기본 이미지면 없음 처리
if (!src || src.startsWith(DEFAULTS.profilePrefix)) {
showToast('프로필 이미지가 없습니다!');
return;
}
downloadImage(src, `${nick} 프로필`);
showToast('프로필 이미지 다운로드 완료!');
}
// ===== 배너 =====
function downloadBanner() {
const nick = getNickname();
const el = document.querySelector(SELECTORS.bannerDiv);
if (!el) {
showToast('배너 이미지가 없습니다!');
return;
}
const styleBg = el.getAttribute('style') || getComputedStyle(el).backgroundImage || '';
const url = parseBgUrlFromStyle(styleBg);
// 기본 배너면 없음 처리(라이트/다크)
if (!url || url === DEFAULTS.bannerLight || url === DEFAULTS.bannerDark) {
showToast('배너 이미지가 없습니다!');
return;
}
downloadImage(url, `${nick} 배너`);
showToast('배너 이미지 다운로드 완료!');
}
function downloadBoth() {
downloadProfile();
downloadBanner();
}
// ===== 단축키 설정 =====
function setShortcutKey() {
const newKey = prompt('새로운 단축키를 입력하세요!\n\n( 예: Y, Ctrl+Y, Alt+Y, F7 )', shortcutKey);
if (newKey) {
GM_setValue('shortcutKey', newKey.toLowerCase());
shortcutKey = newKey.toLowerCase();
showToast(`단축키 설정 완료: ${newKey}`);
} else {
showToast('단축키 설정이 취소되었습니다.');
}
}
// ===== GM 메뉴 =====
GM_registerMenuCommand('프로필/배너 모두 다운로드', downloadBoth);
GM_registerMenuCommand('프로필 이미지만 다운로드', downloadProfile);
GM_registerMenuCommand('배너 이미지만 다운로드', downloadBanner);
GM_registerMenuCommand('단축키 설정', setShortcutKey);
// ===== 단축키 =====
document.addEventListener('keydown', function(event) {
let keyPressed = '';
if (event.ctrlKey) keyPressed += 'ctrl+';
if (event.shiftKey) keyPressed += 'shift+';
if (event.altKey) keyPressed += 'alt+';
keyPressed += event.key.toLowerCase();
if (keyPressed === shortcutKey) {
downloadBoth();
}
});
})();