// ==UserScript== // @name [MT论坛]手机版小黑屋 by:青春向上 // @namespace https://github.com/qcxs/mtbbs // @version 2025-12-10 // @description 移动端的小黑屋,在网页侧边栏添加打开按钮 // @author 青春向上 // @match *://bbs.binmt.cc/* // @icon https://bbs.binmt.cc/favicon.ico // @grant none // ==/UserScript== (function () { 'use strict'; function xhw() { // 配置参数 const API_URL = 'forum.php?mod=misc&action=showdarkroom&cid='; const AVATAR_API = 'uc_server/avatar.php?uid='; const SPACE_URL = 'home.php?mod=space&uid='; let currentCid = ''; // 接口分页标识 let totalLoadedPages = 1; // 已加载的总页数 let isLoading = false; let isFiltering = false; let isFailed = false; let allData = []; // 所有页数据 let pageDataMap = new Map(); // 页数据映射:pageNum -> {items, cid} // 创建遮罩层 const mask = document.createElement('div'); mask.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.3); z-index: 9998; `; document.body.append(mask); // 直接构建完整弹窗布局(移除初始加载中) mask.innerHTML = `

小黑屋名单

×
`; // 获取DOM元素 const closeIcon = mask.querySelector('span'); const filterInput = mask.querySelector('input'); const listContainer = mask.querySelector('#xhw-list-container'); const prevBtn = mask.querySelector('#xhw-prev-btn'); const nextBtn = mask.querySelector('#xhw-next-btn'); const closeBtn = mask.querySelector('#xhw-close-btn'); // 关闭事件 closeIcon.addEventListener('click', () => mask.remove()); closeBtn.addEventListener('click', () => mask.remove()); // 统一加载状态处理函数 function updatePageStatus(pageNum, status, cid = '') { // 查找或创建页码标识元素 let pageFlagEl = Array.from(listContainer.querySelectorAll('.xhw-page-flag')).find(el => el.textContent.includes(`第${pageNum}页`) ); if (!pageFlagEl) { pageFlagEl = document.createElement('div'); pageFlagEl.className = 'xhw-page-flag'; pageFlagEl.style.cssText = `text-align:center; color:#666; font-size:14px;`; listContainer.appendChild(pageFlagEl); } // 根据状态更新内容 switch (status) { case 'loading': pageFlagEl.innerHTML = `第${pageNum}页 加载中...`; break; case 'success': pageFlagEl.innerHTML = `第${pageNum}页`; break; case 'finish': pageFlagEl.innerHTML = `第${pageNum}页:已全部加载完毕。`; break; case 'error': pageFlagEl.innerHTML = ` 第${pageNum}页 加载失败 `; isFailed = true; // 绑定重试事件 const retryBtn = pageFlagEl.querySelector('.xhw-retry-btn'); retryBtn?.addEventListener('click', (e) => { const targetPage = e.target.dataset.page; const targetCid = e.target.dataset.cid; isFailed = false; loadPage(targetPage, targetCid); }); break; } return pageFlagEl; } // 核心:加载指定页数据 async function loadPage(pageNum, cid = '') { if (isLoading) return; isLoading = true; // 更新为加载中状态 updatePageStatus(pageNum, 'loading'); try { const response = await fetch(`${API_URL}${cid}&ajaxdata=json`); if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); let text = await response.text(); // 清理文本增强JSON解析兼容性 text = text.replace(/^\uFEFF/, '') .replace(/ |"|&|<|>|'/g, match => ({ ' ': ' ', '"': '"', '&': '&', '<': '<', '>': '>', ''': "'" }[match])) .replace(/\/\*.*?\*\//g, '') .replace(/[\t\n\r]/g, ''); let data; try { data = JSON.parse(text); } catch (e) { const fixedText = text.replace(/([{,]\s*)(\w+)(\s*:)/g, '$1"$2"$3'); data = JSON.parse(fixedText); } // 解析数据 const messageParts = data.message.split('|'); // 0 代表全部加载完 const isFinished = messageParts[0] == '0'; if (isFinished) { updatePageStatus(pageNum, 'finish'); listContainer.removeEventListener('scroll', scrollEvent, { passive: true }); } const nextCid = messageParts[1] || ''; const items = Object.values(data.data).map(item => ({ uid: item.uid, status: item.action, expireTime: item.groupexpiry, nickname: item.username, handlerUid: item.operatorid, handlerNickname: item.operator, handlerRemark: item.reason, dateline: item.dateline.replace(/ /g, ' '), cid: item.cid })).sort((a, b) => b.cid - a.cid); // 存储页数据 pageDataMap.set(pageNum, { items: items, cid: nextCid }); allData = [...allData, ...items]; currentCid = nextCid; totalLoadedPages = Math.max(totalLoadedPages, pageNum); // 更新为加载成功状态 updatePageStatus(pageNum, 'success'); // 渲染当前页数据 const pageContentEl = document.createElement('div'); pageContentEl.style.cssText = `margin-bottom: 16px;`; pageContentEl.innerHTML = items.map(item => getListItemHtml(item)).join(''); // 插入到页码标识之后 const pageFlagEl = Array.from(listContainer.querySelectorAll('.xhw-page-flag')).find(el => el.textContent.includes(`第${pageNum}页`) ); if (pageFlagEl) { pageFlagEl.after(pageContentEl); } } catch (error) { console.error('加载失败:', error); // 更新为加载失败状态 updatePageStatus(pageNum, 'error', cid); } finally { isLoading = false; } } // 渲染列表项HTML function getListItemHtml(item) { return `
${item.nickname} UID: ${item.uid}
${item.dateline}
${item.status} 到期: ${item.expireTime}
${item.handlerNickname}
${item.handlerRemark}
`; } // 获取当前可视区域内的页码 function getCurrentVisiblePage() { const pageFlags = listContainer.querySelectorAll('.xhw-page-flag'); let currentPage = 1; let minDistance = Infinity; pageFlags.forEach(flag => { const rect = flag.getBoundingClientRect(); const containerRect = listContainer.getBoundingClientRect(); // 计算元素中心到容器中心的距离 const flagCenter = rect.top + rect.height / 2; const containerCenter = containerRect.top + containerRect.height / 2; const distance = Math.abs(flagCenter - containerCenter); if (distance < minDistance) { minDistance = distance; // 提取页码 const pageMatch = flag.textContent.match(/第(\d+)页/); if (pageMatch) { currentPage = parseInt(pageMatch[1]); } } }); return currentPage; } // 查找指定方向的最近页码元素 function findNearestPageElement(direction) { const currentPage = getCurrentVisiblePage(); const targetPage = direction === 'prev' ? currentPage - 1 : currentPage + 1; // 查找目标页码元素 const targetElement = Array.from(listContainer.querySelectorAll('.xhw-page-flag')).find(el => el.textContent.includes(`第${targetPage}页`) ); return { element: targetElement, pageNum: targetPage }; } // 上一页事件 prevBtn.addEventListener('click', () => { if (isFiltering || isLoading) return; const { element, pageNum } = findNearestPageElement('prev'); if (element && pageNum >= 1) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); // 下一页事件 nextBtn.addEventListener('click', () => { if (isFiltering || isLoading) return; const { element, pageNum } = findNearestPageElement('next'); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } else { // 找不到下一页元素,滚动到底部触发加载 listContainer.scrollTop = listContainer.scrollHeight; } }); // 过滤功能 filterInput.addEventListener('input', (e) => { const filter = e.target.value.toLowerCase(); isFiltering = !!filter; if (filter) { const filtered = allData.filter(item => item.uid.toString().includes(filter) || item.nickname.toLowerCase().includes(filter) ); listContainer.innerHTML = filtered.length ? filtered.map(item => getListItemHtml(item)).join('') : '
没有匹配记录
'; } else { // 恢复原始分页内容 listContainer.innerHTML = ''; isFailed = false; pageDataMap.forEach((pageInfo, pageNum) => { // 重建页码标识 const pageFlagEl = document.createElement('div'); pageFlagEl.className = 'xhw-page-flag'; pageFlagEl.style.cssText = `text-align:center; color:#666; font-size:14px;`; pageFlagEl.innerHTML = `第${pageNum}页`; // 重建页内容 const pageContentEl = document.createElement('div'); pageContentEl.style.cssText = `margin-bottom: 16px;`; pageContentEl.innerHTML = pageInfo.items.map(item => getListItemHtml(item)).join(''); listContainer.appendChild(pageFlagEl); listContainer.appendChild(pageContentEl); }); } }); // 修复滚动加载监听器(使用passive优化性能) listContainer.addEventListener('scroll', scrollEvent, { passive: true }); function scrollEvent() { if (isLoading || isFiltering || !currentCid || isFailed) return; const { scrollTop, clientHeight, scrollHeight } = listContainer; // 距离底部200px时加载下一页(调整阈值提高触发率) if (scrollTop + clientHeight >= scrollHeight - 200) { const nextPage = totalLoadedPages + 1; loadPage(nextPage, currentCid); } } // 初始加载第1页 loadPage(1); } // 侧边栏添加小黑屋按钮 const ul = document.querySelector('ul.comiis_left_Touch'); if (ul) { const li = document.createElement('li'); li.innerHTML = ` 小黑屋 `; li.addEventListener('click', xhw); ul.appendChild(li); } })();