您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
코네용 갤러리 뷰어
当前为
// ==UserScript== // @name Kone gg Gallery Viewer // @description 코네용 갤러리 뷰어 // @namespace http://tampermonkey.net/ // @version 1.4 // @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 getCanvasImage (imgElement, maxSize = 200) { 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); return canvas; } static resizeImageToBase64(imgElement, maxSize = 200, outputFormat = 'image/jpeg', quality = 0.8) { const canvas = Kgv.getCanvasImage(imgElement, maxSize); return canvas.toDataURL(outputFormat, quality); } static getLargeImageData(imgElement, maxSize = 200) { const canvas = Kgv.getCanvasImage(imgElement, maxSize); return canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data; } static calculateImageKernalHash(imageData, width, height, kernalSize = 10) { const hash = new Uint8Array(256).fill(0); const halfKernal = Math.floor(kernalSize / 2); const totalPixels = kernalSize * kernalSize; for (let y = halfKernal; y < height - halfKernal; y++) { for (let x = halfKernal; x < width - halfKernal; x++) { let rSum = 0, gSum = 0, bSum = 0; for (let ky = -halfKernal; ky <= halfKernal; ky++) { for (let kx = -halfKernal; kx <= halfKernal; kx++) { const idx = ((y + ky) * width + (x + kx)) * 4; rSum += imageData[idx]; gSum += imageData[idx + 1]; bSum += imageData[idx + 2]; } } const rAvg = Math.floor(rSum / totalPixels); const gAvg = Math.floor(gSum / totalPixels); const bAvg = Math.floor(bSum / totalPixels); hash[rAvg]++; hash[gAvg]++; hash[bAvg]++; } } return hash; } static calculateImageHistogram(imageData) { 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; } // 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 { hideElementInIframe(doc, '.age-verification-popup'); hideElementInIframe(doc, '.content-overlay.block'); } } else { hideElementInIframe(doc, '.age-verification-popup'); 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 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; overflow: hidden; 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; gap: 0.2em; margin-top: 0.2em; } .kgv-gallery-info-2 svg, .kgv-gallery-info-3 svg { display: inline-block !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-gallery-bad .kgv-vote { color: #f00; } .kgv-block { display: none; } .kgv-gallery-bad .kgv-block { display: inline-block; color: #f00; } `; // Instance start static key = 'mowkgv' static instance = null; static defaultConfig = { viewerType: 0, // 0: default(List), 1: Gallery maxCacheImgUrls: 100000, }; static async getInstance () { return Kgv.instance || (Kgv.instance = await Object.create(Kgv.prototype)).init(); } constructor () { throw new Error(); } listeners = {}; config = {}; cacheImgUrls = new Map(); previewIframe = null; queuePreviewImgUrls = []; queueTimeoutUid = null; galleryViewListElement = null; async init () { // Due to Object.create this.listeners = {}; this.config = {}; this.cacheImgUrls = new Map(); if (this.previewIframe) { this.previewIframe.remove(); } this.previewIframe = null; this.queuePreviewImgUrls = []; this.queueTimeoutUid = null; this.galleryViewListElement = null; await this.loadAllConfig(); 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; } async loadAllConfig () { for (const [key, value] of Object.entries(Kgv.defaultConfig)) this.config[key] = GM_getValue(`${Kgv.key}_${key}`, value); } async 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.key}_cacheImgUrls`) || '[]')); } catch (e) { console.error('Failed to parse cacheImgUrls:', e); this.cacheImgUrls = new Map(); } } saveCacheImgUrls() { if (!this.cacheImgUrls) return; try { localStorage.setItem(`${Kgv.key}_cacheImgUrls`, 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)); } pickPreviewCandidate (imgElements) { console.debug('Picking preview candidate from:', imgElements); if (!imgElements || imgElements.length === 0) return null; // TODO: remove mibang 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) { console.debug('Queue is already running, skipping this run.'); return; } else if (this.queuePreviewImgUrls.length === 0) { console.debug('No URLs in queue to process.'); return; } this.queueTimeoutUid = -1; const nextUrl = this.queuePreviewImgUrls.pop(); 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) { console.debug('Using cached preview image URL:', url, cachedImgUrl); 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 > div:nth-child(1) > div > div.items-stretch > span.justify-center.border')?.outerHTML || '', title: item.querySelector('a > div:nth-child(1) > div > div.items-stretch > .text-ellipsis')?.innerHTML || '', commentCountStr: item.querySelector('a > div:nth-child(1) > div > div.items-stretch > span.text-xs')?.innerHTML || '', author: item.querySelector('a > div:nth-child(1) > div:nth-child(2)')?.innerHTML || '', timeStr: item.querySelector('a > div:nth-child(1) > div:nth-child(3)')?.innerHTML || '', viewStr: item.querySelector('a > div:nth-child(1) > div:nth-child(4)')?.innerHTML || '', rating: parseInt(item.querySelector('a > div:nth-child(1) > div:nth-child(5)')?.innerHTML.replace(/[^0-9\-]/g, '')) || 0, isRatingHigh: item.querySelector('a > div:nth-child(1) > div:nth-child(5)')?.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> <span class="kgv-view"> <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> ${galleryInfo.viewStr} </span> <span class="kgv-vote"> <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> ${galleryInfo.rating} </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 listContainer = document.querySelector('div.grow.flex.flex-col.overflow-hidden.relative > div.grow'); const list = document.querySelectorAll('div.grow.flex.flex-col.overflow-hidden.relative > div.grow > div.w-full'); if (!listContainer || !list) { console.warn('List container or list not found.'); return; } 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'; } observeURLChange() { let lastUrl = location.href; const onURLChange = () => { setTimeout(() => { console.debug('URL changed, re-rendering gallery list:', lastUrl); kgvInstance.renderGalleryList(); }, 500); } 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或关注我们的公众号极客氢云获取最新地址