- // ==UserScript==
- // @name MTean 苹果壳 - 图片预览瀑布流(延迟加载)
- // @namespace http://tampermonkey.net/
- // @version 2025-01-29 3.0
- // @description MT 增加开关选项和图片预览瀑布流布局,延迟加载修复初始化问题(2025-01-29)
- // @author Yo
- // @match http*://xp.m-team.io/*/*
- // @match http*://xp.m-team.io/*
- // @match http*://kp.m-team.cc/*/*
- // @match http*://kp.m-team.cc/*
- // @match http*://zp.m-team.io/*/*
- // @match http*://zp.m-team.io/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=m-team.io
- // @grant GM_addStyle
- // @connect *
- // @require http://code.jquery.com/jquery-latest.js
- // @grant unsafeWindow
- // @run-at document-end
- // @license MIT
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- // 定义 localStorage 的键名
- const STORAGE_KEY_HOVER = 'isHoverEnabled';
- const STORAGE_KEY_SEARCH_API = 'isSearchApiEnabled';
- const STORAGE_KEY_MASONRY = 'isMasonryEnabled';
-
- // 从 localStorage 中读取用户的选择,如果没有则使用默认值
- let isHoverEnabled = localStorage.getItem(STORAGE_KEY_HOVER) !== 'false'; // 默认值为 true
- let isSearchApiEnabled = localStorage.getItem(STORAGE_KEY_SEARCH_API) !== 'false'; // 默认值为 true
- let isMasonryEnabled = localStorage.getItem(STORAGE_KEY_MASONRY) !== 'false'; // 默认值为 true
-
- // 处理接口数据的方法
- const _XMLHttpRequest = unsafeWindow.XMLHttpRequest;
- function newXHR() {
- const xhr = new _XMLHttpRequest();
-
- // 保存原始的 send 方法
- const originalSend = xhr.send;
-
- // 保存原始的 onreadystatechange
- const originalOnReadyStateChange = xhr.onreadystatechange;
-
- xhr.onreadystatechange = function () {
- if (this.readyState === 4) {
- if (this.status === 200) {
- let response = this.response;
- let url = this.responseURL; // 使用 responseURL 替代 url
- if (url.indexOf('/api/torrent/search') !== -1 && response !== '') {
- try {
- const dataParse = JSON.parse(response);
- if (isSearchApiEnabled && Array.isArray(dataParse.data.data) && dataParse.data.data.length > 0) {
-
- let now = new Date(); // 当前时间
- let previous24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 当前时间往前推24小时
-
- // 分离过去24小时内和非过去24小时的数据,并为每一项添加 sum 值
- let { within24HoursData, otherData } = dataParse.data.data.reduce((acc, item) => {
- let sum = 0;
- if (item.status) {
- sum = parseInt(item.status.leechers, 10) + parseInt(item.status.seeders, 10);
- }
-
- // 判断是否在当前时间和过去24小时之间
- let createdDate = new Date(item.createdDate);
- if (createdDate >= previous24Hours && createdDate <= now) {
- acc.within24HoursData.push({ ...item, sum });
- } else {
- acc.otherData.push({ ...item, sum });
- }
- return acc;
- }, { within24HoursData: [], otherData: [] });
-
- // 过去24小时内的数据按 createdDate 时间升序排序
- within24HoursData.sort((a, b) => new Date(b.createdDate) - new Date(a.createdDate));
-
- // 非过去24小时的数据按 sum 值降序排序
- otherData.sort((a, b) => b.sum - a.sum);
-
- // 合并 24小时内的数据和其他数据
- let tempData = [...within24HoursData, ...otherData];
-
- const modifiedResponse = JSON.stringify({
- ...dataParse,
- data: {
- ...dataParse.data,
- data: tempData,
- }
- });
- Object.defineProperty(this, 'response', {
- get: function () {
- return modifiedResponse;
- }
- });
- Object.defineProperty(this, 'responseText', {
- get: function () {
- return modifiedResponse;
- }
- });
- }
- } catch (error) {
- console.error('Error parsing or modifying response:', error);
- }
- }
- }
- if (originalOnReadyStateChange) {
- originalOnReadyStateChange.apply(this, arguments);
- }
- }
- };
-
- // 重写 send 方法
- xhr.send = function (data) {
- return originalSend.call(this, data);
- };
-
- // 添加辅助方法来获取请求头
- xhr.getRequestHeader = function (name) {
- return this.requestHeaders ? this.requestHeaders[name] : null;
- };
-
- // 重写 setRequestHeader 方法
- const originalSetRequestHeader = xhr.setRequestHeader;
- xhr.requestHeaders = {};
- xhr.setRequestHeader = function (name, value) {
- this.requestHeaders[name] = value;
- return originalSetRequestHeader.apply(this, arguments);
- };
-
- return xhr;
- }
-
- // 替换为新的 XMLHttpRequest
- unsafeWindow.XMLHttpRequest = newXHR;
-
- // 创建控制面板
- function createControlPanel() {
- const panel = document.createElement('div');
- panel.style.position = 'fixed';
- panel.style.top = '10px';
- panel.style.right = '10px';
- panel.style.zIndex = '10000';
- panel.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
- panel.style.padding = '10px';
- panel.style.borderRadius = '5px';
- panel.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.1)';
- panel.style.display = 'flex';
- panel.style.flexDirection = 'column';
- panel.style.gap = '10px';
-
- // 悬停显示图片开关
- const hoverLabel = document.createElement('label');
- hoverLabel.style.display = 'flex';
- hoverLabel.style.alignItems = 'center';
- hoverLabel.style.gap = '5px';
-
- const hoverCheckbox = document.createElement('input');
- hoverCheckbox.type = 'checkbox';
- hoverCheckbox.checked = isHoverEnabled;
- hoverCheckbox.onchange = (e) => {
- isHoverEnabled = e.target.checked;
- localStorage.setItem(STORAGE_KEY_HOVER, isHoverEnabled); // 存储用户选择
- if (!isHoverEnabled) {
- clearDom(); // 如果关闭功能,清除所有显示的图片
- }
- };
-
- hoverLabel.appendChild(hoverCheckbox);
- hoverLabel.appendChild(document.createTextNode('开启悬停显示图片'));
-
- // 处理 /api/torrent/search 开关
- const searchApiLabel = document.createElement('label');
- searchApiLabel.style.display = 'flex';
- searchApiLabel.style.alignItems = 'center';
- searchApiLabel.style.gap = '5px';
-
- const searchApiCheckbox = document.createElement('input');
- searchApiCheckbox.type = 'checkbox';
- searchApiCheckbox.checked = isSearchApiEnabled;
- searchApiCheckbox.onchange = (e) => {
- isSearchApiEnabled = e.target.checked;
- localStorage.setItem(STORAGE_KEY_SEARCH_API, isSearchApiEnabled); // 存储用户选择
- };
-
- searchApiLabel.appendChild(searchApiCheckbox);
- searchApiLabel.appendChild(document.createTextNode('开启搜索数据处理'));
-
- // 瀑布流布局开关
- const masonryLabel = document.createElement('label');
- masonryLabel.style.display = 'flex';
- masonryLabel.style.alignItems = 'center';
- masonryLabel.style.gap = '5px';
-
- const masonryCheckbox = document.createElement('input');
- masonryCheckbox.type = 'checkbox';
- masonryCheckbox.checked = isMasonryEnabled;
- masonryCheckbox.onchange = (e) => {
- isMasonryEnabled = e.target.checked;
- localStorage.setItem(STORAGE_KEY_MASONRY, isMasonryEnabled); // 存储用户选择
- applyMasonryLayout(); // 重新应用布局
- };
-
- masonryLabel.appendChild(masonryCheckbox);
- masonryLabel.appendChild(document.createTextNode('开启瀑布流布局'));
-
- // 将三个开关添加到面板
- panel.appendChild(hoverLabel);
- panel.appendChild(searchApiLabel);
- panel.appendChild(masonryLabel);
- document.body.appendChild(panel);
- }
-
- // 清除显示的图片
- function clearDom() {
- var elements = document.getElementsByClassName('imgdom');
- var elementsArray = Array.from(elements);
- elementsArray.forEach(function (element) {
- element.parentNode.removeChild(element);
- });
- }
-
- // 加载悬停显示图片功能
- function loadScript() {
- const hoverableImages = document.querySelectorAll('.ant-image');
- hoverableImages.forEach((image) => {
- image.addEventListener('mouseover', (event) => {
- if (isHoverEnabled && event && event.target && event.target.previousElementSibling && event.target.previousElementSibling.src && event.target.previousElementSibling.src !== '') {
- clearDom();
- const div = document.createElement('div');
- div.classList.add('imgdom');
- let img = document.createElement('img');
- img.src = event.target.previousElementSibling.src;
- img.alt = '18x';
- img.style.cssText = 'width: 70%;object-position: center;object-fit: contain';
- img.id = 'imgProId';
- div.style.cssText = 'position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);max-width: 100%;';
- div.appendChild(img);
- document.body.appendChild(div);
- }
- });
- });
- document.getElementById('app-content').onscroll = () => clearDom();
- }
-
- // 应用瀑布流布局
- GM_addStyle(`
- /* 瀑布流容器 */
- .masonry-container {
- column-count: 6; /* 默认6列 */
- column-gap: 20px;
- padding: 10px;
- width: calc(100% - 10px); /* 考虑左右padding */
- box-sizing: border-box;
- margin: 10px auto; /* 上下和左右各20px外边距 */
- }
-
- /* 响应式布局 */
- @media (max-width: 2000px) {
- .masonry-container {
- column-count: 6;
- }
- }
- @media (max-width: 1600px) {
- .masonry-container {
- column-count: 5;
- }
- }
- @media (max-width: 1400px) {
- .masonry-container {
- column-count: 4;
- }
- }
- @media (max-width: 1200px) {
- .masonry-container {
- column-count: 4;
- }
- }
- @media (max-width: 900px) {
- .masonry-container {
- column-count: 2;
- }
- }
- @media (max-width: 600px) {
- .masonry-container {
- column-count: 2;
- }
- }
-
- /* 卡片样式 */
- .masonry-item {
- break-inside: avoid;
- background: #fff;
- border-radius: 12px;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- overflow: hidden;
- transition: all 0.3s ease;
- cursor: pointer;
- margin-bottom: 20px; /* 添加底部间距 */
- display: inline-block;
- width: 100%;
- }
-
- /* 图片容器 */
- .masonry-item .img-container {
- width: 100%;
- position: relative;
- overflow: hidden;
- }
-
- .masonry-item .img-container img {
- width: 100%;
- height: auto;
- display: block;
- transition: transform 0.3s ease;
- }
- .masonry-item:hover {
- transform: translateY(-5px);
- box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
- }
-
- .masonry-item:hover .img-container img {
- transform: scale(1.05);
- }
-
- /* 信息容器样式 */
- .masonry-item .info {
- padding: 16px;
- cursor: pointer;
- }
-
- /* 标题样式 */
- .masonry-item .title {
- font-size: 1rem;
- font-weight: 600;
- color: #1a1a1a;
- margin-bottom: 12px;
- line-height: 1.4;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- /* 详情样式 */
- .masonry-item .details {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
- gap: 8px;
- font-size: 0.875rem;
- color: #666;
- }
-
- .masonry-item .details div {
- display: flex;
- align-items: center;
- gap: 4px;
- }
-
- /* 标签样式 */
- .masonry-item .tag {
- display: inline-block;
- padding: 4px 8px;
- background: #f5f5f5;
- border-radius: 4px;
- font-size: 0.75rem;
- color: #666;
- margin-right: 6px;
- margin-bottom: 6px;
- }
-
- /* 弹出层样式 */
- .modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.7);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 1000;
- opacity: 0;
- visibility: hidden;
- transition: all 0.3s ease;
- }
-
- .modal-overlay.active {
- opacity: 1;
- visibility: visible;
- }
-
- .modal-content {
- background: #fff;
- border-radius: 12px;
- max-width: 800px;
- width: 90%;
- max-height: 90vh;
- overflow-y: auto;
- transform: translateY(20px);
- transition: all 0.3s ease;
- }
-
- .modal-overlay.active .modal-content {
- transform: translateY(0);
- }
-
- /* 关闭按钮 */
- .modal-close {
- position: absolute;
- top: 20px;
- right: 20px;
- width: 30px;
- height: 30px;
- background: #fff;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- cursor: pointer;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
- }
-
- /* 预览按钮容器 */
- .masonry-item .preview-container {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(0, 0, 0, 0.5);
- opacity: 0;
- transition: opacity 0.3s ease;
- }
-
- .masonry-item:hover .preview-container {
- opacity: 1;
- }
-
- /* 预览按钮样式 */
- .preview-btn {
- padding: 8px 16px;
- background: #fff;
- border: none;
- border-radius: 20px;
- color: #333;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.3s ease;
- display: flex;
- align-items: center;
- gap: 6px;
- }
-
- .preview-btn:hover {
- background: #f0f0f0;
- transform: scale(1.05);
- }
-
- /* 预览按钮图标 */
- .preview-btn svg {
- width: 16px;
- height: 16px;
- }
-
- /* 修改卡片整体的cursor */
- .masonry-item {
- cursor: default;
- }
- `);
-
- function applyMasonryLayout() {
- const table = document.querySelector('table');
- if (!table) return;
-
- // 移除现有的瀑布流容器
- const existingMasonry = document.querySelector('.masonry-container');
- if (existingMasonry) {
- existingMasonry.remove();
- }
-
- if (isMasonryEnabled) {
- const masonryContainer = document.createElement('div');
- masonryContainer.classList.add('masonry-container');
- table.parentNode.insertBefore(masonryContainer, table);
-
- const rows = Array.from(table.querySelectorAll('tr')).slice(1); // 跳过表头
-
- rows.forEach(row => {
- const imgCell = row.querySelector('td:nth-child(2) img');
- const titleCell = row.querySelector('td:nth-child(3)');
- const dateCell = row.querySelector('td:nth-child(5)');
- const sizeCell = row.querySelector('td:nth-child(6)');
- const uploadsCell = row.querySelector('td:nth-child(7)');
- const downloadsCell = row.querySelector('td:nth-child(8)');
-
- if (imgCell && titleCell && dateCell && sizeCell && downloadsCell && uploadsCell) {
- const item = createMasonryItem({
- img: imgCell,
- title: titleCell.textContent.trim(),
- date: dateCell.textContent.trim(),
- size: sizeCell.textContent.trim(),
- downloads: downloadsCell.textContent.trim(),
- uploads: uploadsCell.textContent.trim(),
- originalRow: row
- });
-
- masonryContainer.appendChild(item);
- }
- });
-
- // 隐藏原始表格
- table.style.display = 'none';
- } else {
- // 恢复原始表格布局
- table.style.display = '';
- }
- }
-
-
- // 获取详情页连接
- function getDetailUrl(tdElement) {
- // 首先尝试找到 a 标签
- const linkElement = tdElement.querySelector('a[href^="/detail/"]');
-
- if (linkElement && linkElement.href) {
- // 使用 URL 对象解析链接
- try {
- const url = new URL(linkElement.href);
- // 确保路径以 /detail/ 开头
- if (url.pathname.startsWith('/detail/')) {
- return url.pathname;
- }
- } catch (e) {
- console.error('URL解析错误:', e);
- }
- }
-
- return null;
- }
-
-
- // 创建瀑布流项
- function createMasonryItem(data) {
- const item = document.createElement('div');
- item.classList.add('masonry-item');
-
- // 图片容器
- const imgContainer = document.createElement('div');
- imgContainer.classList.add('img-container');
-
- // 创建新的 img 元素,而不是直接使用 data.img
- const img = document.createElement('img');
- img.src = data.img.src;
- img.alt = data.img.alt || '';
- imgContainer.appendChild(img);
-
- // 添加预览按钮容器
- const previewContainer = document.createElement('div');
- previewContainer.classList.add('preview-container');
-
- // 创建预览按钮
- const previewBtn = document.createElement('button');
- previewBtn.classList.add('preview-btn');
- previewBtn.innerHTML = `
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
- <circle cx="12" cy="12" r="3"></circle>
- </svg>
- 预览
- `;
-
- // 绑定预览按钮点击事件
- previewBtn.addEventListener('click', (e) => {
- e.stopPropagation(); // 阻止事件冒泡
- showModal(data);
- });
-
- previewContainer.appendChild(previewBtn);
- imgContainer.appendChild(previewContainer);
-
- // 信息容器
- const info = document.createElement('div');
- info.classList.add('info');
-
- // 标题
- const title = document.createElement('div');
- title.classList.add('title');
- title.textContent = data.title;
-
- // 详情
- const details = document.createElement('div');
- details.classList.add('details');
- details.innerHTML = `
- <div>📅 ${data.date}</div>
- <div>💾 ${data.size}</div>
- <div>⬇️ ${data.downloads}</div>
- <div>⬆️ ${data.uploads}</div>
- `;
-
- // 组装
- info.appendChild(title);
- info.appendChild(details);
- item.appendChild(imgContainer);
- item.appendChild(info);
-
- // 让整个卡片可以点击链接到原始页面(如果需要的话)
- if (data.originalRow) {
- const href = getDetailUrl(data.originalRow.querySelector('td:nth-child(3)'));
- if (href) {
- item.addEventListener('click', () => {
- window.open(href, '_blank');
- });
- }
- }
-
- return item;
- }
-
- // 创建并显示模态框
- async function showModal(data) {
- // 创建遮罩层和基础结构
- const overlay = document.createElement('div');
- overlay.classList.add('modal-overlay');
-
- const content = document.createElement('div');
- content.classList.add('modal-content');
-
- // 先显示基础内容
- content.innerHTML = `
- <div style="padding: 24px;">
- <div style="text-align: center; margin-bottom: 20px;">
- <img src="${data.img.src}" style="max-width: 100%; max-height: 400px; object-fit: contain;">
- </div>
- <h2 style="margin-bottom: 16px; color: #1a1a1a;">${data.title}</h2>
- <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 20px;">
- <div>📅 发布日期:${data.date}</div>
- <div>💾 文件大小:${data.size}</div>
- <div>⬇️ 下载量:${data.downloads}</div>
- <div>⬆️ 做种量:${data.uploads}</div>
- </div>
- </div>
- `;
-
- // 添加关闭按钮
- const closeButton = document.createElement('div');
- closeButton.classList.add('modal-close');
- closeButton.innerHTML = '✕';
- closeButton.onclick = () => {
- overlay.classList.remove('active');
- setTimeout(() => overlay.remove(), 300);
- };
-
- overlay.appendChild(content);
- overlay.appendChild(closeButton);
- document.body.appendChild(overlay);
-
- // 显示模态框
- requestAnimationFrame(() => {
- overlay.classList.add('active');
- });
-
- // 添加关闭事件
- overlay.addEventListener('click', (e) => {
- if (e.target === overlay) {
- overlay.classList.remove('active');
- setTimeout(() => overlay.remove(), 300);
- }
- });
-
- }
-
-
- // 监听分页按钮点击事件
- function setupPaginationListener() {
- const pagination = document.querySelector('.pagination');
- if (pagination) {
- pagination.addEventListener('click', (e) => {
- if (e.target.tagName === 'A' || e.target.parentElement.tagName === 'A') {
- // 分页按钮点击后,等待页面内容加载完成,然后重新应用瀑布流布局
- setTimeout(() => {
- applyMasonryLayout();
- }, 1000); // 延迟 1 秒以确保内容加载完成
- }
- });
- }
- }
-
-
- // 初始化
- function init() {
- if (location.pathname.indexOf('/browse') !== -1) {
- // 延迟 2 秒执行,确保页面内容加载完成
- setTimeout(() => {
- const table = document.querySelector('table');
- if (table) {
- loadScript();
- applyMasonryLayout();
- setupPaginationListener();
- }
- }, 3000); // 延迟 3 秒
- }
- }
-
- // 监听页面变化
- let oldPushState = history.pushState;
- history.pushState = function () {
- init();
- return oldPushState.apply(history, arguments);
- };
-
- window.addEventListener('popstate', init);
-
- // 初始化控制面板和功能
- createControlPanel();
- init();
- })();