// ==UserScript== // @name New Userscript ZEDZ-1 // @namespace https://docs.scriptcat.org/ // @version 0.1.0 // @description try to take over the world! // @author LiuQi // @match https://*/* // @grant none // @noframes // ==/UserScript== (function() { 'use strict'; // 资源缓存 const loadedSet = new Set(); const videoThumbCache = new Map(); const mediaDomMap = new Map(); const mediaConfig = { globalMute: true, autoPlay: true, filterMinDuration: 10 }; let phoneGalleryEl = null; let fullPreviewWrap = null; let controlBar = null; let tabBar = null; let headerWrap = null; let phoneHomeBtn = null; let allMediaList = []; let currentTab = 'all'; let currentMediaIndex = 0; let touchStartX = 0; let touchMoveX = 0; const slideThreshold = 40; let slideThrottle = false; // ===================== 1. 一体化整机DOM结构 ===================== const phoneBox = document.createElement('div'); phoneBox.className = 'ios-blur-card'; const phoneNotch = document.createElement('div'); phoneNotch.className = 'ios-notch'; headerWrap = document.createElement('div'); headerWrap.className = 'ios-header-wrap'; tabBar = document.createElement('div'); tabBar.className = 'ios-segment-control'; const tabAll = document.createElement('button'); tabAll.className = 'seg-btn active'; tabAll.textContent = '全部'; const tabImage = document.createElement('button'); tabImage.className = 'seg-btn'; tabImage.textContent = '图片'; const tabVideo = document.createElement('button'); tabVideo.className = 'seg-btn'; tabVideo.textContent = '视频'; tabBar.append(tabAll, tabImage, tabVideo); controlBar = document.createElement('div'); controlBar.className = 'ios-control-row'; const muteBtn = document.createElement('button'); muteBtn.className = 'ios-small-btn mute-btn'; muteBtn.textContent = '静音开'; const autoPlayBtn = document.createElement('button'); autoPlayBtn.className = 'ios-small-btn autoplay-btn'; autoPlayBtn.textContent = '自动播放开'; controlBar.append(muteBtn, autoPlayBtn); headerWrap.append(tabBar, controlBar); const screenContainer = document.createElement('div'); screenContainer.className = 'ios-screen-wrap'; phoneGalleryEl = document.createElement('ul'); phoneGalleryEl.className = 'media-gallery'; fullPreviewWrap = document.createElement('div'); fullPreviewWrap.className = 'full-preview-layer'; fullPreviewWrap.style.display = 'none'; phoneHomeBtn = document.createElement('div'); phoneHomeBtn.className = 'ios-home-button'; phoneHomeBtn.addEventListener('click', closeFullPreview); screenContainer.append(phoneGalleryEl, fullPreviewWrap); phoneBox.append(phoneNotch, headerWrap, screenContainer, phoneHomeBtn); document.body.prepend(phoneBox); // ===================== 2. Tab切换逻辑 ===================== function switchTab(type) { currentTab = type; document.querySelectorAll('.seg-btn').forEach(btn => btn.classList.remove('active')); if (type === 'all') tabAll.classList.add('active'); if (type === 'image') tabImage.classList.add('active'); if (type === 'video') tabVideo.classList.add('active'); renderFilterMedia(); } tabAll.addEventListener('click', () => switchTab('all')); tabImage.addEventListener('click', () => switchTab('image')); tabVideo.addEventListener('click', () => switchTab('video')); // ===================== 3. 滑动切换 + 节流优化 ===================== function handleSlideSwitch() { if (slideThrottle) return; slideThrottle = true; const diff = touchMoveX - touchStartX; if (diff < -slideThreshold) nextMedia(); if (diff > slideThreshold) prevMedia(); touchStartX = 0; touchMoveX = 0; setTimeout(() => slideThrottle = false, 250); } fullPreviewWrap.addEventListener('touchstart', e => touchStartX = e.touches[0].clientX); fullPreviewWrap.addEventListener('touchmove', e => touchMoveX = e.touches[0].clientX); fullPreviewWrap.addEventListener('touchend', handleSlideSwitch); fullPreviewWrap.addEventListener('mousedown', e => touchStartX = e.clientX); fullPreviewWrap.addEventListener('mousemove', e => { if (e.buttons === 1) touchMoveX = e.clientX; }); fullPreviewWrap.addEventListener('mouseup', handleSlideSwitch); document.addEventListener('keydown', e => { if (fullPreviewWrap.style.display === 'none') return; if (e.key === 'ArrowLeft') prevMedia(); if (e.key === 'ArrowRight') nextMedia(); }); function prevMedia() { if (currentMediaIndex <= 0) return; currentMediaIndex--; openMediaByIndex(currentMediaIndex); } function nextMedia() { if (currentMediaIndex >= allMediaList.length - 1) return; currentMediaIndex++; openMediaByIndex(currentMediaIndex); } function openMediaByIndex(index) { const media = allMediaList[index]; if (media.type === 'image') openFullImage(media.src); if (media.type === 'video') openFullVideo(media.src); if (media.type === 'iframe-video') openFullIframe(media.src); } muteBtn.addEventListener('click', () => { mediaConfig.globalMute = !mediaConfig.globalMute; muteBtn.textContent = mediaConfig.globalMute ? '静音开' : '静音关'; }); autoPlayBtn.addEventListener('click', () => { mediaConfig.autoPlay = !mediaConfig.autoPlay; autoPlayBtn.textContent = mediaConfig.autoPlay ? '自动播放开' : '自动播放关'; }); // ===================== 4. 工具函数(修复视频缩略跨域问题) ===================== function parseIframeVideoSrc(iframeSrc) { let realVideo = ''; if (iframeSrc.includes('bilibili.com/player')) { const bvidMatch = iframeSrc.match(/bvid=([A-Za-z0-9]+)/); const aidMatch = iframeSrc.match(/aid=(\d+)/); if (bvidMatch) realVideo = `https://player.bilibili.com/player.html?bvid=${bvidMatch[1]}`; else if (aidMatch) realVideo = `https://player.bilibili.com/player.html?aid=${aidMatch[1]}`; } if (iframeSrc.includes('youku.com')) { const vidMatch = iframeSrc.match(/vid_([A-Za-z0-9]+)/); if (vidMatch) realVideo = `https://player.youku.com/embed/${vidMatch[1]}`; } return realVideo; } function createVideoThumb(src, callback) { if (videoThumbCache.has(src)) { callback(videoThumbCache.get(src)); return; } const canvas = document.createElement('canvas'); canvas.width = 120; canvas.height = 60; const ctx = canvas.getContext('2d'); const tempVid = document.createElement('video'); tempVid.crossOrigin = 'anonymous'; tempVid.src = src; tempVid.muted = true; tempVid.currentTime = 0.1; tempVid.addEventListener('loadeddata', () => { if (tempVid.duration < mediaConfig.filterMinDuration) { const blankCanvas = document.createElement('canvas'); blankCanvas.width = 120; blankCanvas.height = 60; const bCtx = blankCanvas.getContext('2d'); bCtx.fillStyle = '#eee'; bCtx.fillRect(0,0,120,60); bCtx.fillStyle = '#999'; bCtx.font = "12px sans-serif"; bCtx.fillText("短视频过滤", 10,35); const dataUrl = blankCanvas.toDataURL(); videoThumbCache.set(src, dataUrl); callback(dataUrl); return; } ctx.drawImage(tempVid, 0, 0, canvas.width, canvas.height); const dataUrl = canvas.toDataURL(); videoThumbCache.set(src, dataUrl); callback(dataUrl); }); tempVid.addEventListener('error', () => { const errorCanvas = document.createElement('canvas'); errorCanvas.width = 120; errorCanvas.height = 60; const eCtx = errorCanvas.getContext('2d'); eCtx.fillStyle = '#f0f0f0'; eCtx.fillRect(0,0,120,60); eCtx.fillStyle = '#666'; eCtx.font = "11px sans-serif"; eCtx.fillText("视频", 45,35); const dataUrl = errorCanvas.toDataURL(); videoThumbCache.set(src, dataUrl); callback(dataUrl); }); tempVid.load(); } function bindLongPressDownload(el, mediaSrc, mediaType) { let pressTimer = null; const longPressTime = 600; el.addEventListener('mousedown', () => pressTimer = setTimeout(() => { const a = document.createElement('a'); a.href = mediaSrc; a.download = mediaType === 'image' ? '预览图片' : '预览视频'; document.body.appendChild(a); a.click(); a.remove(); }, longPressTime)); el.addEventListener('mouseup', () => clearTimeout(pressTimer)); el.addEventListener('mouseleave', () => clearTimeout(pressTimer)); } // ===================== 5. 增量渲染媒体DOM ===================== function createMediaItem(mediaObj) { const { type, src } = mediaObj; if (mediaDomMap.has(src)) return mediaDomMap.get(src); const li = document.createElement('li'); li.className = 'gallery-item'; li.dataset.mediaType = type; li.dataset.src = src; if (type === 'image') { const thumb = document.createElement('img'); thumb.className = 'media-thumb'; thumb.src = src; thumb.onerror = () => thumb.style.background = '#eee'; thumb.addEventListener('click', e => { e.stopPropagation(); currentMediaIndex = allMediaList.findIndex(m => m.src === src); openFullImage(src); }); bindLongPressDownload(thumb, src, 'image'); li.appendChild(thumb); } else if (type === 'video') { const thumbImg = document.createElement('img'); thumbImg.className = 'media-thumb'; const videoTag = document.createElement('span'); videoTag.className = 'ios-video-tag'; videoTag.textContent = '视频'; createVideoThumb(src, thumbData => { thumbImg.src = thumbData; }); thumbImg.addEventListener('click', e => { e.stopPropagation(); currentMediaIndex = allMediaList.findIndex(m => m.src === src); openFullVideo(src); }); bindLongPressDownload(thumbImg, src, 'video'); li.append(thumbImg, videoTag); } else if (type === 'iframe-video') { const thumbImg = document.createElement('img'); thumbImg.className = 'media-thumb'; thumbImg.src = 'data:image/svg+xml;utf8,内嵌视频'; const iframeTag = document.createElement('span'); iframeTag.className = 'ios-iframe-tag'; iframeTag.textContent = 'B站/优酷'; thumbImg.addEventListener('click', e => { e.stopPropagation(); currentMediaIndex = allMediaList.findIndex(m => m.src === src); openFullIframe(src); }); li.append(thumbImg, iframeTag); } mediaDomMap.set(src, li); return li; } function renderFilterMedia() { phoneGalleryEl.innerHTML = ''; allMediaList.forEach(media => { if (currentTab === 'all' || (currentTab === 'image' && media.type === 'image') || (currentTab === 'video' && (media.type === 'video' || media.type === 'iframe-video'))) { const dom = createMediaItem(media); phoneGalleryEl.appendChild(dom); } }); } // ===================== 6. 全屏预览 ===================== function openFullImage(src) { fullPreviewWrap.innerHTML = ''; fullPreviewWrap.style.background = '#fff'; const fullImg = document.createElement('img'); fullImg.src = src; fullImg.className = 'full-preview-img'; fullImg.addEventListener('click', closeFullPreview); fullPreviewWrap.appendChild(fullImg); fullPreviewWrap.style.display = 'flex'; } function openFullVideo(src) { fullPreviewWrap.innerHTML = ''; fullPreviewWrap.style.background = '#000'; const fullVideo = document.createElement('video'); fullVideo.src = src; fullVideo.className = 'full-preview-video'; fullVideo.controls = true; fullVideo.muted = mediaConfig.globalMute; fullVideo.autoplay = mediaConfig.autoPlay; fullPreviewWrap.appendChild(fullVideo); fullPreviewWrap.style.display = 'flex'; } function openFullIframe(src) { fullPreviewWrap.innerHTML = ''; fullPreviewWrap.style.background = '#000'; const iframe = document.createElement('iframe'); iframe.src = src; iframe.className = 'full-preview-iframe'; iframe.allow = 'autoplay;fullscreen;encrypted-media'; iframe.frameBorder = '0'; fullPreviewWrap.appendChild(iframe); fullPreviewWrap.style.display = 'flex'; } function closeFullPreview() { const videoEl = fullPreviewWrap.querySelector('video'); if (videoEl) { videoEl.pause(); videoEl.src = ''; } fullPreviewWrap.style.display = 'none'; fullPreviewWrap.innerHTML = ''; } // ===================== 7. 页面媒体采集 ===================== function collectAllMedia() { let hasNew = false; Array.from(document.querySelectorAll('img')).forEach(imgDom => { let src = imgDom.src || imgDom.dataset.src || imgDom.dataset.original; if (!src) return; const blankGif = src.startsWith('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'); if (blankGif || loadedSet.has(src)) return; loadedSet.add(src); allMediaList.push({ type: 'image', src }); hasNew = true; }); Array.from(document.querySelectorAll('video')).forEach(videoDom => { let src = videoDom.src; if (!src && videoDom.querySelector('source')) src = videoDom.querySelector('source').src; if (!src || loadedSet.has(src)) return; loadedSet.add(src); allMediaList.push({ type: 'video', src }); hasNew = true; }); Array.from(document.querySelectorAll('iframe')).forEach(iframe => { const realSrc = parseIframeVideoSrc(iframe.src); if (!realSrc || loadedSet.has(realSrc)) return; loadedSet.add(realSrc); allMediaList.push({ type: 'iframe-video', src: realSrc }); hasNew = true; }); if (hasNew) renderFilterMedia(); } let observerTimer = null; const observer = new MutationObserver(() => { clearTimeout(observerTimer); observerTimer = setTimeout(() => collectAllMedia(), 300); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['src', 'data-src', 'data-original'] }); collectAllMedia(); // ===================== 8. 修复溢出角标样式(核心:固定高度+双层截断) ===================== const style = document.createElement('style'); style.textContent = ` /* iOS 磨砂玻璃整机 */ .ios-blur-card { position: fixed !important; top: 20px !important; right: 20px !important; z-index: 999999 !important; width: 280px !important; height: 560px !important; background: rgba(255, 255, 255, 0.72) !important; backdrop-filter: blur(20px) saturate(180%) !important; -webkit-backdrop-filter: blur(20px) saturate(180%) !important; border: 1px solid rgba(255, 255, 255, 0.85) !important; border-radius: 36px !important; padding: 16px 12px !important; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.09), 0 8px 24px rgba(0, 0, 0, 0.06), inset 0 0 0 0.5px rgba(255,255,255,0.9) !important; box-sizing: border-box !important; display: flex !important; flex-direction: column !important; align-items: center !important; gap: 10px !important; overflow: visible !important; } .ios-notch { width: 62px; height: 13px; background: #111; border-radius: 0 0 11px 11px; } /* 一体化头部 */ .ios-header-wrap { width: 100%; background: rgba(0,0,0,0.05); border-radius: 20px; padding: 8px 10px; display: flex; flex-direction: column; gap: 6px; box-sizing: border-box; } .ios-segment-control { width: 100%; display: flex; background: rgba(0,0,0,0.06); border-radius: 999px; padding: 3px; gap: 2px; } .seg-btn { flex: 1; border: none; padding: 5px 0; font-size: 10px; font-weight: 500; border-radius: 999px; background: transparent; color: #666; cursor: pointer; transition: all 0.2s cubic-bezier(0.2,0,0.2,1); } .seg-btn.active { background: #ffffff; color: #007aff; box-shadow: 0 1px 4px rgba(0,0,0,0.08); } .ios-control-row { width: 100%; display: flex; gap: 10px; justify-content: center; } .ios-small-btn { font-size: 9px; padding: 3px 9px; border-radius: 999px; border: 1px solid rgba(0,122,255,0.25); background: transparent; color: #007aff; cursor: pointer; transition: 0.2s; } .ios-small-btn:hover { background: rgba(0,122,255,0.08); border-color: rgba(0,122,255,0.4); } .ios-screen-wrap { flex: 1; width: 100%; position: relative; border-radius: 20px; overflow: hidden; background: rgba(255,255,255,0.4); border: 1px solid rgba(0,0,0,0.05); } .media-gallery { width: 100%; height: 100%; margin: 0; padding: 9px; list-style: none; overflow-y: auto; overflow-x: hidden; display: flex; flex-wrap: wrap; gap: 5px; box-sizing: border-box; } .media-gallery::-webkit-scrollbar { width: 4px; } .media-gallery::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.12); border-radius: 2px; } .ios-home-button { width: 32px; height: 32px; border: 1.5px solid rgba(0,0,0,0.22); border-radius: 50%; margin-top: 2px; cursor: pointer; transition: 0.2s; background: rgba(255,255,255,0.35); backdrop-filter: blur(4px); } .ios-home-button:hover { border-color: #007aff; background: rgba(0,122,255,0.08); } /* ========== 核心修复:固定高度+双重overflow:hidden,彻底阻止角标溢出 ========== */ .gallery-item { flex: 1 0 42px; margin: 0; position: relative; overflow: hidden !important; border-radius: 7px; height: 42px !important; max-height: 42px !important; } .media-thumb { width: 100%; height: 100%; object-fit: cover; background: #fff; border-radius: 7px; border: 1px solid rgba(0,0,0,0.06); cursor: pointer; transition: transform 0.2s ease; display: block; box-shadow: 0 1px 2px rgba(0,0,0,0.04); overflow: hidden !important; } .media-thumb:hover { transform: scale(1.04); box-shadow: 0 3px 9px rgba(0,0,0,0.09); } /* 角标向上收缩,远离底部边界 */ .ios-video-tag, .ios-iframe-tag { position: absolute; bottom: 4px; right: 4px; color: #fff; font-size: 6px; padding: 1px 3px; border-radius: 3px; backdrop-filter: blur(6px); font-weight: 500; } .ios-video-tag { background: rgba(0,122,255,0.75); } .ios-iframe-tag { background: rgba(52,199,89,0.75); } .full-preview-layer { position: absolute; inset: 0; background: rgba(255,255,255,0.9); display: flex; align-items: center; justify-content: center; padding: 6px; box-sizing: border-box; touch-action: pan-y; } .full-preview-img { width: 100%; height: 100%; object-fit: contain; cursor: grab; border-radius: 10px; } .full-preview-img:active { cursor: grabbing; } .full-preview-video, .full-preview-iframe { width: 100%; height: 100%; object-fit: contain; border: none; border-radius: 10px; } /* 移动端适配 */ @media (max-width: 768px) { .ios-blur-card { width: 144px !important; height: 324px !important; right: 10px !important; bottom: 10px !important; top: auto !important; padding: 12px 8px !important; } .gallery-item { flex: 1 0 30px; height: 30px !important; max-height: 30px !important; } .media-thumb { height: 100%; border-radius: 5px; } .ios-home-button { width: 24px; height: 24px; } .ios-notch { width: 40px; height: 9px; } .seg-btn, .ios-small-btn { font-size: 7px; } .ios-video-tag, .ios-iframe-tag { font-size: 5px; bottom: 2px; right: 2px; } } `; document.head.appendChild(style); })();