// ==UserScript==
// @name New Userscript T1W9-2
// @namespace https://docs.scriptcat.org/
// @version 0.1.0
// @description try to take over the world!
// @author LiuQi
// @match https://*/*
// @grant none
// @noframes
// @license Apache-2.0
// ==/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;
// 触摸变量升级:增加起始Y、起始时间,区分横竖滑动
let touchStartX = 0;
let touchStartY = 0;
let touchStartTime = 0;
let touchMoveX = 0;
let touchMoveY = 0;
// 滑动阈值提高至80px,大幅降低灵敏度,必须长距离滑动才切换
const slideThreshold = 80;
// 垂直容差:垂直移动超过该值则判定为上下拖拽,不切换视频
const verticalTolerance = 30;
let slideThrottle = false;
// 轮询定时器 仅全局声明一次
let observerTimer = null;
let pollTimer = null;
const pollInterval = 2000;
// ===================== 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. 重构滑动判断逻辑(降低灵敏度、区分横竖滑动) =====================
// 触摸开始:记录完整坐标+时间
fullPreviewWrap.addEventListener('touchstart', e => {
const touch = e.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
touchStartTime = Date.now();
touchMoveX = touch.clientX;
touchMoveY = touch.clientY;
});
fullPreviewWrap.addEventListener('touchmove', e => {
const touch = e.touches[0];
touchMoveX = touch.clientX;
touchMoveY = touch.clientY;
});
fullPreviewWrap.addEventListener('touchend', handleSlideSwitch);
fullPreviewWrap.addEventListener('mousedown', e => {
touchStartX = e.clientX;
touchStartY = e.clientY;
touchStartTime = Date.now();
touchMoveX = e.clientX;
touchMoveY = e.clientY;
});
fullPreviewWrap.addEventListener('mousemove', e => { if (e.buttons === 1) {
touchMoveX = e.clientX;
touchMoveY = e.clientY;
}});
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 handleSlideSwitch() {
if (slideThrottle) return;
slideThrottle = true;
const diffX = touchMoveX - touchStartX;
const diffY = touchMoveY - touchStartY;
const absX = Math.abs(diffX);
const absY = Math.abs(diffY);
// 过滤条件1:垂直移动过大,判定为上下拖拽,不切换
if (absY > verticalTolerance) {
slideThrottle = false;
return;
}
// 过滤条件2:水平滑动距离不足阈值,视为误触/点击
if (absX < slideThreshold) {
slideThrottle = false;
return;
}
// 过滤条件3:滑动时间过短(快速轻点拖拽)
const duration = Date.now() - touchStartTime;
if (duration < 100) {
slideThrottle = false;
return;
}
if (diffX < 0) nextMedia();
if (diffX > 0) prevMedia();
setTimeout(() => slideThrottle = false, 250);
}
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 isM3u8Source(src) {
return src.includes('.m3u8') || src.includes('.m3u');
}
function getM3u8Cover() {
return 'data:image/svg+xml;utf8,';
}
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, videoType } = 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';
let tagText = '';
let tagClass = '';
if (videoType === 'downloadable') {
tagText = '可下载视频';
tagClass = 'ios-download-tag';
} else if (videoType === 'm3u8') {
tagText = '加密流媒体';
tagClass = 'ios-m3u8-tag';
}
const videoTag = document.createElement('span');
videoTag.className = tagClass;
videoTag.textContent = tagText;
createVideoThumb(src, thumbData => {
thumbImg.src = thumbData;
});
thumbImg.addEventListener('click', e => {
e.stopPropagation();
currentMediaIndex = allMediaList.findIndex(m => m.src === src);
openFullVideo(src);
});
if (videoType === 'downloadable') 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);
let videoType = isM3u8Source(src) ? 'm3u8' : 'downloadable';
allMediaList.push({ type: 'video', src, videoType });
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();
}
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();
// 2秒循环延时轮询,捕获弹窗/懒加载延迟资源
function startPollCollect() {
pollTimer = setInterval(() => {
collectAllMedia();
}, pollInterval);
}
startPollCollect();
// ===================== 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-download-tag, .ios-iframe-tag, .ios-m3u8-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-download-tag {
background: rgba(0,122,255,0.75);
}
.ios-m3u8-tag {
background: rgba(107,114,128,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-download-tag, .ios-m3u8-tag, .ios-iframe-tag {
font-size: 5px;
bottom: 2px;
right: 2px;
}
}
`;
document.head.appendChild(style);
})();