您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds infinite scroll, hover previews, and sorting to Bunkr Albums pages
// ==UserScript== // @name Bunkr Albums Enhanced with Sorting // @namespace https://github.com/WendysBro/bunkr-albums-autoload-previews // @version 1.2.2 // @description Adds infinite scroll, hover previews, and sorting to Bunkr Albums pages // @author WendysBro // @match https://bunkr-albums.io/?search=* // @match https://bunkr-albums.io/topalbums* // @license MIT // @homepageURL https://github.com/WendysBro/bunkr-albums-autoload-previews // @supportURL https://github.com/WendysBro/bunkr-albums-autoload-previews/issues // @grant none // @icon https://www.google.com/s2/favicons?sz=64&domain=bunkrrr.org // ==/UserScript== (function () { 'use strict'; const CONFIG = { previewDelay: 200, maxPreviews: 15, previewSize: '180px', scrollThreshold: 500, }; function addSortingControls() { const controls = document.createElement('div'); controls.style = 'margin: 10px 0; display: flex; gap: 10px; align-items: center;'; const label = document.createElement('label'); label.textContent = 'Sort albums by:'; label.style = 'color: white;'; const select = document.createElement('select'); select.innerHTML = ` <option value="default">Default</option> <option value="name">Name (A–Z)</option> <option value="count">File Count</option> `; select.style = 'padding: 4px; border-radius: 6px;'; select.addEventListener('change', () => sortAlbums(select.value)); controls.append(label, select); const gridContainer = document.querySelector('div.grid.auto-rows-max.gap-1\\.5'); if (gridContainer && gridContainer.parentElement) { gridContainer.parentElement.insertBefore(controls, gridContainer); } } function sortAlbums(method) { const container = document.querySelector('div.grid.auto-rows-max.gap-1\\.5'); if (!container) return; const albums = Array.from(container.children); if (method === 'name') { albums.sort((a, b) => { const nameA = a.querySelector('p.text-subs span')?.textContent.trim().toLowerCase() || ''; const nameB = b.querySelector('p.text-subs span')?.textContent.trim().toLowerCase() || ''; return nameA.localeCompare(nameB); }); } else if (method === 'count') { albums.sort((a, b) => { const countA = parseInt(a.querySelector('p.text-xs span')?.textContent.match(/\d+/)?.[0] || '0'); const countB = parseInt(b.querySelector('p.text-xs span')?.textContent.match(/\d+/)?.[0] || '0'); return countB - countA; }); } else { return; } albums.forEach((album) => container.appendChild(album)); } class BunkrAlbumsEnhanced { constructor() { this.nextPage = 2; this.loading = false; this.previewTimeout = null; this.isTopAlbumsPage = window.location.pathname.includes('/topalbums'); this.init(); } init() { this.setupInfiniteScroll(); this.setupAllHoverPreviews(); addSortingControls(); } qs(selector, el = document) { return el.querySelector(selector); } qsa(selector, el = document) { return Array.from(el.querySelectorAll(selector)); } getCurrentParams() { const params = new URLSearchParams(window.location.search); return this.isTopAlbumsPage ? { lapse: params.get('lapse') || '24h', page: this.nextPage } : { search: params.get('search') || '', page: this.nextPage }; } getAlbumDomain(albumUrl) { const match = albumUrl.match(/https:\/\/(bunkr\.[a-z]+)/); return match ? match[1] : 'bunkr.cr'; } setupInfiniteScroll() { if (!this.isLastPage()) { window.addEventListener('scroll', this.handleScroll.bind(this)); } } isLastPage() { return !!this.qs('.text-center.text-xs.text-subtle'); } handleScroll() { if (this.loading) return; const scrollPosition = window.innerHeight + window.scrollY; if (scrollPosition >= document.body.offsetHeight - CONFIG.scrollThreshold) { this.loadNextPage(); } } async loadNextPage() { this.loading = true; const params = this.getCurrentParams(); const url = this.isTopAlbumsPage ? `/topalbums?lapse=${params.lapse}&page=${params.page}` : `/?search=${encodeURIComponent(params.search)}&page=${params.page}`; try { const response = await fetch(url); if (!response.ok) throw new Error(`Failed to load page ${params.page}`); const doc = new DOMParser().parseFromString(await response.text(), 'text/html'); const newAlbums = this.qsa('.rounded-xl.bg-mute.border-b', doc); const container = this.qs('div.grid.auto-rows-max.gap-1\\.5'); if (newAlbums.length && container) { newAlbums.forEach((album) => this.setupHoverPreview(album)); container.append(...newAlbums); this.nextPage++; // reapply sort if user selected a sort option const sortMethod = document.querySelector('select')?.value; if (sortMethod && sortMethod !== 'default') sortAlbums(sortMethod); } else { window.removeEventListener('scroll', this.handleScroll); } } catch (error) { console.error('[Bunkr Albums Enhanced]', error); } finally { this.loading = false; } } setupAllHoverPreviews() { this.qsa('.rounded-xl.bg-mute.border-b').forEach((album) => { this.setupHoverPreview(album); }); } setupHoverPreview(album) { const albumLink = this.qs("a[href^='https://bunkr.']", album); if (!albumLink) return; const albumUrl = albumLink.href; const albumTextContainer = this.qs('.flex-1.grid.auto-rows-max', album); if (!albumTextContainer) return; const previewContainer = this.createPreviewContainer(); albumTextContainer.appendChild(previewContainer); album.addEventListener('mouseenter', () => this.showPreview(albumUrl, previewContainer)); album.addEventListener('mouseleave', () => this.hidePreview(previewContainer)); album.addEventListener('click', (e) => this.handleAlbumClick(e, albumUrl, previewContainer)); } createPreviewContainer() { const container = document.createElement('div'); container.className = 'album-preview-flex'; container.style = ` display: none; flex-wrap: wrap; justify-content: center; gap: 8px; padding: 10px; background: #222; border-radius: 5px; margin-top: 10px; max-width: 100%; overflow: hidden; `; return container; } async showPreview(albumUrl, container) { this.previewTimeout = setTimeout(async () => { if (container.innerHTML.trim()) { container.style.display = 'flex'; return; } try { const response = await fetch(albumUrl); if (!response.ok) throw new Error('Failed to load album contents'); const doc = new DOMParser().parseFromString(await response.text(), 'text/html'); const albumDomain = this.getAlbumDomain(albumUrl); const items = this.qsa('.grid-images .theItem', doc); container.innerHTML = ''; items.slice(0, CONFIG.maxPreviews).forEach((item) => { const img = this.qs('.grid-images_box-img', item); const fileLink = this.qs("a[href^='/f/']", item); if (img && fileLink) this.addPreviewItem(img, fileLink, albumDomain, container); }); container.style.display = 'flex'; } catch (error) { console.error('[Bunkr Albums Enhanced]', error); container.innerHTML = '<p style="color:white">Preview unavailable</p>'; container.style.display = 'flex'; } }, CONFIG.previewDelay); } addPreviewItem(img, fileLink, domain, container) { const thumb = document.createElement('img'); thumb.src = img.src; thumb.style = `width: ${CONFIG.previewSize}; height: auto; border-radius: 3px; cursor: pointer;`; thumb.alt = 'Preview'; thumb.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); window.open(`https://${domain}${fileLink.getAttribute('href')}`, '_blank'); }); container.appendChild(thumb); } hidePreview(container) { clearTimeout(this.previewTimeout); container.style.display = 'none'; } handleAlbumClick(e, albumUrl, previewContainer) { if (!e.target.closest('.album-preview-flex img')) { window.location.href = albumUrl; } } } new BunkrAlbumsEnhanced(); })();