您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
코네용 갤러리 뷰어
// ==UserScript== // @name Kone gg Gallery Viewer // @description 코네용 갤러리 뷰어 // @namespace http://tampermonkey.net/ // @version 2.3 // @author Mowa // @match https://kone.gg/s/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license MIT // ==/UserScript== (async function() { 'use strict'; // Singleton class Kgv { // static createImageFromArrayBuffer(arrayBuffer) { // // ArrayBuffer를 Uint8Array로 변환 // const uint8Array = new Uint8Array(arrayBuffer); // // Base64로 인코딩 // const base64String = btoa(String.fromCharCode(...uint8Array)); // // Data URL 생성 // const dataUrl = `data:image/jpeg;base64,${base64String}`; // // img element 생성 // const img = document.createElement('img'); // img.src = dataUrl; // return img; // } // static arrayBufferToString(arrayBuffer) { // // ArrayBuffer를 Uint8Array로 변환 // const uint8Array = new Uint8Array(arrayBuffer); // // 문자열로 변환 // let str = ''; // for (let i = 0; i < uint8Array.length; i++) { // str += String.fromCharCode(uint8Array[i]); // } // return str; // } // static async getCanvasImage (imgSrc, maxSize = 200) { // return new Promise((resolve, reject) => { // GM_xmlhttpRequest({ // method: 'GET', // url: imgSrc, // responseType: 'arraybuffer', // // headers: { // // 'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8', // // 'Accept-Encoding': 'gzip, deflate, br', // // 'Accept-Language': navigator.language || navigator.userLanguage, // // 'Cookie': document.cookie, // // 'Host': window.location.host, // // 'Referer': window.location.href, // // 'User-Agent': navigator.userAgent, // // }, // onload: (response) => { // console.log(response.response); // console.log(Kgv.arrayBufferToString(response.response)); // const imgElement = Kgv.createImageFromArrayBuffer(response.response) // imgElement.onload = () => { // const canvas = document.createElement('canvas'); // const ctx = canvas.getContext('2d'); // const originalWidth = imgElement.naturalWidth || imgElement.width; // const originalHeight = imgElement.naturalHeight || imgElement.height; // const ratio = Math.min(maxSize / originalWidth, maxSize / originalHeight); // const newWidth = Math.round(originalWidth * ratio); // const newHeight = Math.round(originalHeight * ratio); // canvas.width = newWidth; // canvas.height = newHeight; // ctx.imageSmoothingEnabled = true; // ctx.imageSmoothingQuality = 'high'; // ctx.drawImage(imgElement, 0, 0, newWidth, newHeight); // resolve(canvas); // }; // imgElement.onerror = (e) => { // console.error('Error XHR loading image:', e); // reject(e); // }; // document.body.appendChild(imgElement); // Append to body to avoid CORS issues // }, // onerror: (e) => { // console.error('Error XHR fetching image:', e); // reject(e); // } // }) // }); // } // static async resizeImageToBase64 (imgSrc, maxSize = 200, outputFormat = 'image/jpeg', quality = 0.8) { // const canvas = await Kgv.getCanvasImage(imgSrc, maxSize); // return canvas.toDataURL(outputFormat, quality); // } // static async getLargeImageData (imgSrc, maxSize = 200) { // const canvas = await Kgv.getCanvasImage(imgSrc, maxSize); // return [canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height]; // } // static async calculateImageDiffHash (imgSrc, hashSize = 16) { // const getBrightness = (imageData, width, x, y) => { // const idx = (y * width + x) * 4; // return (imageData[idx] + imageData[idx + 1] + imageData[idx + 2]) / 3; // Average RGB // } // const [imageData, width, height] = await Kgv.getLargeImageData(imgSrc, hashSize); // let hash = new Array(hashSize*hashSize).fill(0).join(''); // for (let y = 0; y < height; y += 1) { // for (let x = 0; x < width-1; x += 1) { // hash[y * hashSize + x] = Math.floor( // ( // (getBrightness(imageData, width, x, y) - getBrightness(imageData, width, x + 1, y)) // + 256 // ) / 512 * 10 // ); // } // } // return hash; // } // static diffHashHammingDistanceNormal(hash1, hash2) { // const min = Math.min(hash1.length, hash2.length); // let distance = 0; // for (let i = 0; i < min; i++) { // if (hash1[i] !== hash2[i]) { // distance++; // } // } // return distance / min; // } // static async calculateImageHistogram (imgSrc, hashSize = 16) { // const [imageData, width, height] = await Kgv.getLargeImageData(imgSrc, hashSize); // const histogram = { r: new Array(256).fill(0), g: new Array(256).fill(0), b: new Array(256).fill(0) }; // for (let i = 0; i < imageData.length; i += 4) { // histogram.r[imageData[i]]++; // histogram.g[imageData[i + 1]]++; // histogram.b[imageData[i + 2]]++; // } // return histogram; // } // static histogramToString (histogram) { // const combined = [...histogram.r, ...histogram.g, ...histogram.b]; // const uint16Array = new Uint16Array(combined); // const bytes = new Uint8Array(uint16Array.buffer); // let binary = ''; // for (let i = 0; i < bytes.length; i++) { // binary += String.fromCharCode(bytes[i]); // } // return btoa(binary); // } // static stringToHistogram (str) { // // Base64 디코딩 // const binary = atob(str); // const bytes = new Uint8Array(binary.length); // for (let i = 0; i < binary.length; i++) { // bytes[i] = binary.charCodeAt(i); // } // const uint16Array = new Uint16Array(bytes.buffer); // const combined = Array.from(uint16Array); // return { // r: combined.slice(0, 256), // g: combined.slice(256, 512), // b: combined.slice(512, 768) // }; // } // static compareHistogram (hist1, hist2) { // let correlation = 0; // const channels = ['r', 'g', 'b']; // for (const channel of channels) { // let sum1 = 0, sum2 = 0, sum1Sq = 0, sum2Sq = 0, pSum = 0; // for (let i = 0; i < 256; i++) { // sum1 += hist1[channel][i]; // sum2 += hist2[channel][i]; // sum1Sq += hist1[channel][i] * hist1[channel][i]; // sum2Sq += hist2[channel][i] * hist2[channel][i]; // pSum += hist1[channel][i] * hist2[channel][i]; // } // const num = pSum - (sum1 * sum2 / 256); // const den = Math.sqrt((sum1Sq - sum1 * sum1 / 256) * (sum2Sq - sum2 * sum2 / 256)); // if (den === 0) continue; // correlation += num / den; // } // return correlation / 3; // } // Credit: https://gf.qytechs.cn/en/scripts/536425-kone-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EB%8C%93%EA%B8%80-%EA%B0%9C%EC%84%A0 static async handleModalsInIframeKone(doc) { try { const nsfwOverlayContainer = doc.querySelector('div.relative.min-h-60 > div.absolute.w-full.h-full.backdrop-blur-2xl'); if (nsfwOverlayContainer && nsfwOverlayContainer.offsetParent !== null) { const viewContentButton = nsfwOverlayContainer.querySelector('div.flex.gap-4 button:nth-child(2)'); if (viewContentButton && viewContentButton.textContent?.includes('콘텐츠 보기')) { viewContentButton.click(); await new Promise(resolve => setTimeout(resolve, 500)); } else { Kgv.hideElementInIframe(doc, '.age-verification-popup'); Kgv.hideElementInIframe(doc, '.content-overlay.block'); } } else { Kgv.hideElementInIframe(doc, '.age-verification-popup'); Kgv.hideElementInIframe(doc, '.content-overlay.block'); } } catch (e) { } } // Credit: https://gf.qytechs.cn/en/scripts/536425-kone-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EB%8C%93%EA%B8%80-%EA%B0%9C%EC%84%A0 static hideElementInIframe(doc, selector) { try { const elements = doc.querySelectorAll(selector); elements.forEach(el => { if (el.offsetParent !== null) { el.style.setProperty('display', 'none', 'important'); } }); } catch (e) { } } // Credit: https://gf.qytechs.cn/en/scripts/536425-kone-%EC%8D%B8%EB%84%A4%EC%9D%BC-%EB%8C%93%EA%B8%80-%EA%B0%9C%EC%84%A0 static extractImagesFromIframeDocument(doc) { const proseContainer = doc.querySelector('div.prose-container'); if (!proseContainer || !proseContainer.shadowRoot) { return []; } const contentInShadow = proseContainer.shadowRoot.querySelector('div.dark'); if (!contentInShadow) { return []; } return [...contentInShadow.querySelectorAll('img')] .filter(img => ( img.src && !/kone-logo|default|placeholder|data:image/.test(img.src) )); } static relativeUrlToAbsolute (relativeUrl) { if (!relativeUrl) return ''; try { const baseUrl = window.location.origin + window.location.pathname; return new URL(relativeUrl, baseUrl).href; } catch (e) { console.error('Invalid relative URL:', relativeUrl, e); return ''; } } static filterOnlyPathUrl (url) { if (!url) return ''; try { const parsedUrl = new URL(url); return parsedUrl.pathname; } catch (e) { console.error('Invalid URL:', url, e); return ''; } } static kgvCSS = /* css */ ` .kgv-list { width: 100%; display: flex; flex-direction: row; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; align-content: flex-start; gap: 0.2em; } .kgv-gallery { display: inline-block; width: 10.5em; } .kgv-gallery-good { color: var(--color-red-400); } .kgv-gallery-bad { color: #444; } .kgv-gallery-preview { display: flex; justify-content: center; align-items: center; width: 10.5em; height: 10.5em; overflow: hidden; background-color: #777; border-radius: 5px; } .kgv-gallery-preview img { object-fit: cover; width: 100%; height: 100%; } .kgv-gallery-bad .kgv-gallery-preview > * { filter: grayscale(100%) blur(10px); } .kgv-gallery-preview video { object-fit: contain; width: 100%; height: 100%; display: flex; flex-direction: column; } .kgv-gallery-info { width: auto; padding: 5px 0 0 0; font-size: 0.8rem; line-height: 1.1; } .kgv-gallery-info-1 { display: block; } .kgv-gallery-info-2 { display: flex; flex-direction: row; gap: 0.2em; } .kgv-gallery-info-3 { display: flex; flex-direction: row; align-items: center; gap: 0.2em; margin-top: 0.2em; color: #777; } .kgv-gallery-info-2 svg, .kgv-gallery-info-3 svg { display: inline-block !important; } .kgv-gallery-info-3 > * { margin: 0 !important; padding: 0 !important; } .kgv-title { display: inline; font-weight: bold; line-height: 1.2; } .kgv-comment { display: inline; color: #777; } .kgv-author { height: 0.8rem; overflow: hidden; } .kgv-view { color: #777; } .kgv-vote { color: #777; } .kgv-time { color: #777; } .kgv-gallery-bad .kgv-vote { color: #f00; } .kgv-block { display: none; } .kgv-gallery-bad .kgv-block { display: inline-block; color: #f00; } .kgv-menu { display: flex; flex-direction: row; gap: 0.5em; flex: 0 0 auto; } .kgv-menu-container { box-sizing: border-box; boder: 0 solid; margin: 0; padding: 0; display: flex; flex-direction: row; border: 1px solid #777; border-radius: 5px; } .kgv-menu-container > .kgv-menu-container-btn { flex: 1; padding: 0.4em 0.2em; border-left: 1px solid #777; cursor: pointer; text-align: center; font-size: 0.7em; } .kgv-menu-container > .kgv-menu-container-btn:first-child { border-left: none; } .kgv-menu-container > .kgv-menu-container-btn.active { background-color: #007bff; color: #fff; } .kgv-menu-btn { padding: 0.4em 0.2em; border: 1px solid #777; border-radius: 5px; cursor: pointer; font-size: 0.7em; } .kgv-mibang-handle { border: 5px solid #007bff !important; cursor: pointer; } `; // Instance start static key = 'mowkgv' static keyCacheImgUrls = `${Kgv.key}_cacheImgUrls`; static keyMibang = `${Kgv.key}_mibang`; static instance = null; static defaultConfig = { viewerType: 1, // 0: default(List), 1: Gallery maxCacheImgUrls: 100000, }; static qMain = 'main > div.mx-auto > div.mx-auto > div.flex > div.flex'; static qSubTitle = 'main > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1)'; static qSubMenu = 'main .py-2 .p-3:nth-child(1)'; static qSubMenuBtns = 'main .py-2 .p-3:nth-child(1) > .items-center'; static qSubMenuSearch = 'main .py-2 .p-3:nth-child(2)'; static qSubCategory = 'main div[dir=ltr]'; static qSubMain = 'div.h-full.flex-col'; static qSubMainListContainer = 'div.h-full.flex-col > div.grow.flex-col > div.grow'; static qSubMainList = 'div.h-full.flex-col > div.grow.flex-col > div.grow > div.w-full'; static async getInstance () { return Kgv.instance || (Kgv.instance = await Object.create(Kgv.prototype)).init(); } constructor () { throw new Error(); } listeners = {}; config = {}; cacheImgUrls = new Map(); mibang = []; previewIframe = null; queuePreviewImgUrls = []; queueTimeoutUid = null; galleryViewListElement = null; originListMutationObserver = null; popupMibang = null; async init () { // Due to Object.create this.listeners = {}; this.config = {}; this.cacheImgUrls = new Map(); this.mibang = []; if (this.previewIframe) { this.previewIframe.remove(); } this.previewIframe = null; this.queuePreviewImgUrls = []; this.queueTimeoutUid = null; this.galleryViewListElement = null; this.originListMutationObserver = null; this.popupMibang = null; this.loadAllConfig(); // this.loadAllMibang(); this.loadCacheImgUrls(); GM_addStyle(Kgv.kgvCSS); return this; } addEventListener(type, listener, once = false) { if (!this.listeners[type]) { this.listeners[type] = []; } if (once) { const wrappedListener = (...args) => { listener.apply(this, args); this.removeEventListener(type, wrappedListener); }; this.listeners[type].push(wrappedListener); } else { this.listeners[type].push(listener); } } removeEventListener(type, listener) { if (!this.listeners[type]) return; const index = this.listeners[type].indexOf(listener); if (index > -1) { this.listeners[type].splice(index, 1); } } dispatchEvent(event) { if (!this.listeners[event.type]) return true; this.listeners[event.type].forEach(listener => { listener.call(this, event); }); return true; } loadAllConfig () { for (const [key, value] of Object.entries(Kgv.defaultConfig)) this.config[key] = GM_getValue(`${Kgv.key}_${key}`, value); } saveConfig (key, value) { GM_setValue(`${Kgv.key}_${key}`, this.config[key] = value); } ensureCacheImgUrls () { if (!this.cacheImgUrls) this.loadCacheImgUrls(); if (this.cacheImgUrls.size > this.config.maxCacheImgUrls) { console.warn(`Cache size exceeded limit (${this.config.maxCacheImgUrls}), trimming cache.`); } while (this.cacheImgUrls.size > this.config.maxCacheImgUrls) { this.cacheImgUrls.delete(this.cacheImgUrls.keys().next().value); } } loadCacheImgUrls() { try { this.cacheImgUrls = new Map(JSON.parse(localStorage.getItem(Kgv.keyCacheImgUrls) || '[]')); } catch (e) { console.error('Failed to parse cacheImgUrls:', e); this.cacheImgUrls = new Map(); } } saveCacheImgUrls() { if (!this.cacheImgUrls) return; try { localStorage.setItem(Kgv.keyCacheImgUrls, JSON.stringify([...this.cacheImgUrls.entries()])); } catch (e) { if (e instanceof DOMException && e.name === 'QuotaExceededError') { if (this.config.maxCacheImgUrls > 100) { let nextLimit = Math.floor(this.cacheImgUrls.size * 0.9); if (nextLimit < 100) nextLimit = 100; this.saveConfig('maxCacheImgUrls', nextLimit); return this.saveCacheImgUrls(); } } console.error('Failed to save cacheImgUrls:', e); } } // null: no image, undefined: not cached getCacheImgUrl (url) { if (!url) return undefined; return this.cacheImgUrls.get(Kgv.filterOnlyPathUrl(url)); } // loadAllMibang () { // try { // this.mibang = JSON.parse(localStorage.getItem(Kgv.keyMibang) || '[]'); // } catch (e) { // console.error('Failed to parse mibang:', e); // this.mibang = []; // } // } // saveAllMibang () { // if (!this.mibang) return; // try { // localStorage.setItem(Kgv.keyMibang, JSON.stringify(this.mibang)); // } catch (e) { // console.error('Failed to save mibang:', e); // } // } // saveMibang(imgSrc) { // const previewBase64 = Kgv.resizeImageToBase64(imgSrc); // const diffHash = Kgv.calculateImageDiffHash(imgSrc); // const histogram = Kgv.histogramToString(Kgv.calculateImageHistogram(imgSrc)); // this.mibang.push({ // previewBase64, // diffHash, // histogram, // }); // this.saveAllMibang(); // } // checkIsMibang (imgSrc) { // if (!this.mibang || this.mibang.length === 0) { // return false; // } // const diffHash = Kgv.calculateImageDiffHash(imgSrc); // const histogram = Kgv.calculateImageHistogram(imgSrc); // for (const item of this.mibang) { // if (Kgv.diffHashHammingDistanceNormal(item.diffHash, diffHash) < 0.1 && // Kgv.compareHistogram(Kgv.stringToHistogram(item.histogram), histogram) > 0.9) { // return true; // } // } // } buildMenu () { const menuLoop = (max_retries = 20, delay = 100) => { const subMenuBtns = document.querySelector(Kgv.qSubMenuBtns); if (!subMenuBtns) { if (max_retries > 0) { return setTimeout(() => menuLoop(max_retries - 1, delay), delay); } else { console.warn('Max retries reached, subMenuBtns not found.'); return; } } Object.assign(subMenuBtns.style, { display: 'flex', flexDirection: 'row', flexWrap: 'wrap', }) let customMenu = subMenuBtns.querySelector('.kgv-menu'); if (!customMenu) { customMenu = document.createElement('div'); customMenu.classList.add('kgv-menu'); subMenuBtns.appendChild(customMenu); } else { customMenu.innerHTML = ''; } const viewStyleContainer = document.createElement('div'); viewStyleContainer.classList.add('kgv-menu-container'); const viewStyleElem0 = document.createElement('button'); viewStyleElem0.classList.add('kgv-menu-container-btn'); if (this.config.viewerType === 0) { viewStyleElem0.classList.add('active'); } viewStyleElem0.textContent = '📜 리스트' viewStyleElem0.addEventListener('click', async () => { if (this.config.viewerType === 0) { console.debug('Already in list view, no action taken.'); return; } this.saveConfig('viewerType', 0); // Refresh page window.location.reload(); }); viewStyleContainer.appendChild(viewStyleElem0); const viewStyleElem1 = document.createElement('button'); viewStyleElem1.classList.add('kgv-menu-container-btn'); if (this.config.viewerType === 1) { viewStyleElem1.classList.add('active'); } viewStyleElem1.textContent = '🖼️ 갤러리'; viewStyleElem1.addEventListener('click', async () => { if (this.config.viewerType === 1) { console.debug('Already in gallery view, no action taken.'); return; } this.saveConfig('viewerType', 1); // Refresh page window.location.reload(); }); viewStyleContainer.appendChild(viewStyleElem1); customMenu.appendChild(viewStyleContainer); const cacheClearBtn = document.createElement('button'); cacheClearBtn.classList.add('kgv-menu-btn'); cacheClearBtn.textContent = '🗑️ 캐시제거'; cacheClearBtn.addEventListener('click', () => { if (confirm('캐시된 이미지 URL을 모두 삭제하시겠습니까?')) { this.cacheImgUrls.clear(); this.saveCacheImgUrls(); // Refresh page window.location.reload(); } }); customMenu.appendChild(cacheClearBtn); subMenuBtns.appendChild(customMenu); // const mibandAddBtn = document.createElement('button'); // mibandAddBtn.classList.add('kgv-menu-btn'); // mibandAddBtn.textContent = '➕ 미방추가'; // mibandAddBtn.addEventListener('click', () => { // if (mibandAddBtn.classList.contains('active')) { // return; // } // mibandAddBtn.classList.add('active'); // this.addMibangState(); // }); // customMenu.appendChild(mibandAddBtn); // const mibangBtn = document.createElement('button'); // mibangBtn.classList.add('kgv-menu-btn'); // mibangBtn.textContent = '🪄 미방'; // mibangBtn.addEventListener('click', () => { // if (this.popupMibang) { // this.popupMibang.remove(); // this.popupMibang = null; // } else { // this.buildPopupMibang(); // } // }); // customMenu.appendChild(mibangBtn); }; menuLoop(); } // addMibangState () { // alert('이미지 클릭 시 미방에 추가되며, 미리보기에서 제외됩니다.\n취소하려면 새로고침하세요.'); // const allImgElements = document.querySelectorAll('img'); // if (!allImgElements || allImgElements.length === 0) { // console.warn('No image elements found to add Mibang state.'); // return; // } // allImgElements.forEach((imgElement) => { // if (imgElement.classList.contains('kgv-mibang-handle')) return; // imgElement.classList.add('kgv-mibang-handle'); // imgElement.onclick = (e) => { // e.stopPropagation(); // e.preventDefault(); // this.saveMibang(imgElement.src); // // refresh page // window.location.reload(); // }; // }); // } // buildPopupMibang () { // if (this.popupMibang) { // this.popupMibang.remove(); // this.popupMibang = null; // } // this.popupMibang = document.createElement('div'); // this.popupMibang.classList.add('kgv-popup-mibang'); // this.popupMibang.style.position = 'fixed'; // this.popupMibang.style.top = '50%'; // this.popupMibang.style.left = '50%'; // this.popupMibang.style.transform = 'translate(-50%, -50%)'; // this.popupMibang.style.backgroundColor = '#fff'; // this.popupMibang.style.padding = '1em'; // this.popupMibang.style.borderRadius = '5px'; // this.popupMibang.style.zIndex = '1000'; // const title = document.createElement('h2'); // title.textContent = '미방 설정'; // this.popupMibang.appendChild(title); // const mibangList = document.createElement('ul'); // mibangList.classList.add('kgv-mibang-list'); // } pickPreviewCandidate (imgElements) { console.debug('Picking preview candidate from:', imgElements); if (!imgElements || imgElements.length === 0) return null; // for (const imgElement of imgElements) { // if (this.checkIsMibang(imgElement.src)) { // console.debug('Found Mibang image, skipping:', imgElement); // continue; // } // return imgElement; // } return imgElements[0]; } async crawlPreviewImgUrls (url) { return new Promise((resolve, _) => { const finalize = (resultUrl) => { this.previewIframe.remove(); this.previewIframe = null; return resolve(resultUrl); } url = Kgv.relativeUrlToAbsolute(url); if (this.previewIframe) { this.previewIframe.remove(); this.previewIframe = null; } this.previewIframe = document.createElement('iframe'); Object.assign(this.previewIframe.style, { position: 'fixed', left: '-9999px', width: '1px', height: '1px', visibility: 'hidden', }); document.body.appendChild(this.previewIframe); this.previewIframe.onload = async () => { console.debug('Preview iframe loaded:', url); const retryLoop = async (maxRetries = 20, delay = 100) => { try { const doc = this.previewIframe.contentDocument || this.previewIframe.contentWindow.document; const shadowRoot = doc?.querySelector('.prose-container') if (shadowRoot) { await Kgv.handleModalsInIframeKone(doc); const previewElement = this.pickPreviewCandidate(Kgv.extractImagesFromIframeDocument(doc)); if (!previewElement || !previewElement.src) { console.warn('No valid preview image found in iframe document:', url); return finalize(null); } return finalize(previewElement.src); } else { return setTimeout(() => { if (maxRetries > 0) { console.debug('Retrying to load iframe content, remaining retries:', maxRetries); return retryLoop(maxRetries - 1, delay); } else { console.warn('Max retries reached, no valid content found in iframe document:', url); return finalize(null); } }, delay); } } catch (e) { console.error('Error loading iframe document:', e); return finalize(null); } }; await retryLoop(); }; this.previewIframe.onerror = (e) => { console.error('Error loading iframe:', e); return finalize(null); }; this.previewIframe.src = url; }); } async runQueuePreviewImgUrls () { if (this.queueTimeoutUid) { return; } else if (this.queuePreviewImgUrls.length === 0) { console.debug('No URLs in queue to process.'); return; } this.queueTimeoutUid = -1; const nextUrl = this.queuePreviewImgUrls.shift(); if (nextUrl) { console.debug('Processing URL from queue:', nextUrl); try { const previewUrl = await this.crawlPreviewImgUrls(nextUrl); this.cacheImgUrls.set(Kgv.filterOnlyPathUrl(nextUrl), previewUrl || null); this.ensureCacheImgUrls(); this.saveCacheImgUrls(); this.dispatchEvent(new CustomEvent('previewImgUrlCrawled', { detail: { url: nextUrl, previewUrl: previewUrl } })); } catch (e) { console.error('Error processing URL in queue:', nextUrl, e); } } this.queueTimeoutUid = setTimeout(() => { this.queueTimeoutUid = null; this.runQueuePreviewImgUrls(); }, 0); } requestQueuePreviewImgUrl (url) { if (!url) { console.warn('Invalid URL requested for preview image:', url); return; } const cachedImgUrl = this.getCacheImgUrl(url); if (cachedImgUrl !== undefined) { this.dispatchEvent(new CustomEvent('previewImgUrlCrawled', { detail: { url: url, previewUrl: cachedImgUrl } })); } else { this.queuePreviewImgUrls.push(url); this.runQueuePreviewImgUrls(); } } koneParseGalleryInfoList (list) { const resultGalleryInfo = []; for (const item of list) { const galleryInfo = { link: item.querySelector('a')?.href || '', badgeHtml: item.querySelector('a .contents > div:nth-child(1) > div:nth-child(1) > div:nth-child(1)')?.outerHTML || '', title: item.querySelector('a .contents > div:nth-child(1) div.flex.items-center > span.text-ellipsis')?.innerHTML || '', commentCountStr: item.querySelector('a .contents > div:nth-child(1) div.flex > span.text-xs')?.innerHTML || '', author: item.querySelector('a .contents > div:nth-child(1) > div:nth-child(3) .text-ellipsis')?.innerHTML || '', timeStr: item.querySelector('a .contents > div:nth-child(1) > div:nth-child(4)')?.innerHTML || '', viewStr: item.querySelector('a .contents > div:nth-child(1) > div:nth-child(5)')?.innerHTML || '', rating: parseInt(item.querySelector('a .contents > div:nth-child(1) > div:nth-child(6)')?.innerHTML.replace(/[^0-9\-]/g, '')) || 0, isRatingHigh: item.querySelector('a .contents > div:nth-child(1) > div:nth-child(6)')?.classList.contains('text-red-500') || false, isRatingLow: false, }; if (galleryInfo.rating < 0) { galleryInfo.isRatingLow = true; } resultGalleryInfo.push(galleryInfo); } return resultGalleryInfo; } buildGalleryCard (galleryInfo) { const card = document.createElement('a'); card.href = galleryInfo.link; card.classList.add('kgv-gallery'); if (galleryInfo.isRatingHigh) { card.classList.add('kgv-gallery-good'); } if (galleryInfo.isRatingLow) { card.classList.add('kgv-gallery-bad'); } card.innerHTML = /*html*/ ` <div class="kgv-gallery-preview"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 -960 960 960" fill="currentColor"><path d="M320-160h320v-120q0-66-47-113t-113-47q-66 0-113 47t-47 113v120Zm160-360q66 0 113-47t47-113v-120H320v120q0 66 47 113t113 47ZM160-80v-80h80v-120q0-61 28.5-114.5T348-480q-51-32-79.5-85.5T240-680v-120h-80v-80h640v80h-80v120q0 61-28.5 114.5T612-480q51 32 79.5 85.5T720-280v120h80v80H160Z"/></svg> </div> <div class="kgv-gallery-info"> <div class="kgv-gallery-info-1"> <span class="kgv-title">${galleryInfo.title}</span> <span class="kgv-comment"> ${galleryInfo.commentCountStr} </span> </div> <div class="kgv-gallery-info-2"> <span class="kgv-author"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -960 960 960" fill="currentColor"><path d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z"/></svg> ${galleryInfo.author} </span> </div> <div class="kgv-gallery-info-3"> <span class="kgv-category"> ${galleryInfo.badgeHtml || ''} </span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512"><circle cx="256" cy="256" r="64" fill="currentColor"/><path fill="currentColor" d="M490.84 238.6c-26.46-40.92-60.79-75.68-99.27-100.53C349 110.55 302 96 255.66 96c-42.52 0-84.33 12.15-124.27 36.11c-40.73 24.43-77.63 60.12-109.68 106.07a31.92 31.92 0 0 0-.64 35.54c26.41 41.33 60.4 76.14 98.28 100.65C162 402 207.9 416 255.66 416c46.71 0 93.81-14.43 136.2-41.72c38.46-24.77 72.72-59.66 99.08-100.92a32.2 32.2 0 0 0-.1-34.76ZM256 352a96 96 0 1 1 96-96a96.11 96.11 0 0 1-96 96Z"/></svg> <span class="kgv-view"> ${galleryInfo.viewStr} </span> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512"><path fill="currentColor" d="M456 128a40 40 0 0 0-37.23 54.6l-84.17 84.17a39.86 39.86 0 0 0-29.2 0l-60.17-60.17a40 40 0 1 0-74.46 0L70.6 306.77a40 40 0 1 0 22.63 22.63L193.4 229.23a39.86 39.86 0 0 0 29.2 0l60.17 60.17a40 40 0 1 0 74.46 0l84.17-84.17A40 40 0 1 0 456 128Z"/></svg> <span class="kgv-vote"> ${galleryInfo.rating} </span> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 512 512"><path fill="currentColor" d="M256 48C141.31 48 48 141.31 48 256s93.31 208 208 208s208-93.31 208-208S370.69 48 256 48zm0 368c-88.22 0-160-71.78-160-160s71.78-160 160-160s160 71.78 160 160s-71.78 160-160 160z"/><path fill="currentColor" d="M272 144h-32v144l96.97 58.18l16.03-26.28L272 240V144z"/></svg> <span class="kgv-time"> ${galleryInfo.timeStr} </span> </div> </div> `; const onCrawled = (e) => { if (Kgv.filterOnlyPathUrl(e.detail.url) === Kgv.filterOnlyPathUrl(galleryInfo.link)) { this.removeEventListener('previewImgUrlCrawled', onCrawled); const previewElement = card.querySelector('.kgv-gallery-preview'); if (previewElement) { previewElement.innerHTML = ''; if (e.detail.previewUrl) { const imgElement = document.createElement('img'); imgElement.loading = 'lazy'; imgElement.src = e.detail.previewUrl; previewElement.appendChild(imgElement); } } } } this.addEventListener('previewImgUrlCrawled', onCrawled); this.requestQueuePreviewImgUrl(galleryInfo.link); return card; } renderGalleryList () { const loadingLoop = (max_retries = 20, delay = 100) => { const listContainer = document.querySelector(Kgv.qSubMainListContainer); const list = document.querySelectorAll(Kgv.qSubMainList); if (listContainer && list && list.length > 0) { if (this.originListMutationObserver) { this.originListMutationObserver.disconnect(); this.originListMutationObserver = null; } this.originListMutationObserver = new MutationObserver(() => { console.debug('Gallery list changed, re-rendering gallery list.'); this.buildMenu(); this.renderGalleryList(); }); this.originListMutationObserver.observe(listContainer, { childList: true, subtree: true, }); // Reset queue this.queuePreviewImgUrls = []; const galleryInfoList = this.koneParseGalleryInfoList(list); if (galleryInfoList.length === 0) { console.warn('No gallery info found.'); return; } if (this.galleryViewListElement) { this.galleryViewListElement.remove(); this.galleryViewListElement = null; } this.galleryViewListElement = document.createElement('div'); this.galleryViewListElement.classList.add('kgv-list'); galleryInfoList.map(this.buildGalleryCard.bind(this)).forEach(card => { this.galleryViewListElement.appendChild(card); }); listContainer.after(this.galleryViewListElement); listContainer.style.display = 'none'; } else { if (max_retries > 0) { return setTimeout(() => loadingLoop(max_retries - 1, delay), delay); } else { console.warn('Max retries reached, gallery list not found.'); } } } if (this.config.viewerType === 1) { loadingLoop(); } } observeURLChange() { let lastUrl = location.href; const onURLChange = () => { setTimeout(() => { console.debug('URL changed, re-rendering gallery list:', lastUrl); kgvInstance.buildMenu() kgvInstance.renderGalleryList(); }, 100); } const urlChangeHandler = () => { if (location.href !== lastUrl && location.href.includes('/s/')) { lastUrl = location.href; onURLChange(); } }; const urlObserver = new MutationObserver(urlChangeHandler); urlObserver.observe(document.body, { childList: true, subtree: true }); const originalPush = history.pushState; history.pushState = function () { originalPush.apply(this, arguments); urlChangeHandler(); }; window.addEventListener('popstate', urlChangeHandler); onURLChange(); // Initial call to render on script load } } // Initialize the Kgv instance const kgvInstance = await Kgv.getInstance(); if (document.readyState === 'complete' || document.readyState === 'interactive') { kgvInstance.observeURLChange(); } else { document.addEventListener('DOMContentLoaded', () => { kgvInstance.observeURLChange(); }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址