NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name NGA安科 // @namespace http://tampermonkey.net/ // @version 0.2 // @description 安科用 // @author rrrrrrrrrriyjiiiiiuzhao // @match https://ngabbs.com/read.php?* // @match https://nga.178.com/read.php?* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; function angaToText(angas) { const result = [] angas.forEach(element => { element.options.forEach(opt => { result.push(`${element.floor}:${element.author}:${opt}`) }) }); return `\n${result.join('\n')}\n` } function updateTextarea(content) { // 插入到快速发帖框 const textarea = document.querySelector('#fast_post_c textarea'); if (textarea) { textarea.value = content; textarea.focus(); } } function appendTextarea(content) { // 插入到快速发帖框 const textarea = document.querySelector('#fast_post_c textarea'); if (textarea) { textarea.value += content; textarea.focus(); } } // 楼层=>页 function floor2page(floor) { return Math.floor(floor / 20) + 1; } // 楼层=>起始页 function page2floor(page) { return (page - 1) * 20; } function purifyContent(htmlNode) { // 创建内存中的临时容器 const tempContainer = document.createElement('div'); // 深度克隆时移除事件监听器 const clone = htmlNode.cloneNode(true); clone.querySelectorAll('*').forEach(node => { // 清除所有事件属性 node.removeAttribute('onerror'); node.removeAttribute('onload'); // 移除可能导致脚本执行的元素 if (node.tagName === 'SCRIPT') node.remove(); }); // 将克隆节点附加到内存容器 tempContainer.appendChild(clone); // 执行净化操作(在内存中进行) const quoteBlocks = tempContainer.querySelectorAll('div.quote'); quoteBlocks.forEach(block => block.remove()); // 获取净化后的HTML字符串 const cleanHTML = tempContainer.innerHTML; // 主动销毁临时对象 tempContainer.remove(); clone.remove(); // 创建新的纯净文本节点 const textContainer = document.createElement('div'); textContainer.innerHTML = cleanHTML; // 提取纯文本 const text = textContainer.textContent .replace(/\s+/g, ' ') .trim(); // 再次清理 textContainer.remove(); return text; } function findAngaOptions(text) { // 增强版正则表达式,处理多种情况: const pattern = /安价[::]\s*((?:(?!安价[::])[\s\S])+)/gi; const matches = []; // 先按安价关键词分割文本块 const segments = text.split(/(?=安价[::])/gi); segments.forEach(segment => { if (!segment.toLowerCase().startsWith('安价')) return; // 精确提取内容部分 const match = segment.match(pattern); if (match) { const content = match[0] .replace(/^安价[::]\s*/i, '') // 去除前缀 .replace(/\s+/g, ' ') // 合并空白 .trim(); if (content) { matches.push(content); } } }); return matches; } function scanAllFloorsInPage(root, fromFloor, toFloor) { const results = []; // 核心选择器调整 const floorElements = root.querySelectorAll('tr.postrow'); floorElements.forEach(floor => { // 提取楼层元数据 const floorNumber = Number(floor.querySelector('a[name^="l"]')?.textContent.match(/\d+/)?.[0]); if (floorNumber > toFloor || floorNumber < fromFloor) { return; } const author = floor.querySelector('.userlink.author')?.textContent.trim(); // 定位内容容器 const contentSpan = floor.querySelector('span.postcontent.ubbcode'); if (!contentSpan) return; // 净化内容流程 const cleanText = purifyContent(contentSpan); // 安价解析 const angas = findAngaOptions(cleanText); if (angas.length > 0) { results.push({ floor: floorNumber, author, options: angas, }); } }); return results; } function onQuickAngaBtnClick() { const urlParams = new URLSearchParams(window.location.search); const page = urlParams.get('page'); // 返回字符串 "5" const pageNumber = parseInt(page, 10); var startFloor = page2floor(pageNumber) const angas = scanAllFloorsInPage(startFloor, startFloor + 19); // 调用之前的扫描函数 if (angas.length === 0) { alert('未检测到安价内容'); return; } updateTextarea(`安价数据:\n${angaToText(angas)}\n\n`) } function findUrlForPage(page) { // 获取当前页面的URL对象 const urlObj = new URL(window.location.href); // 获取查询参数操作接口 const params = urlObj.searchParams; // 智能设置page参数(自动处理参数存在与否的情况) params.set('page', page); // 将页码设置为8 // 生成新URL字符串 return urlObj.toString(); } async function loadPageInBackground(url) { return new Promise((resolve, reject) => { const iframe = document.createElement('iframe'); iframe.style.display = 'none'; // 隐藏 iframe document.body.appendChild(iframe); // 监听加载完成事件 iframe.onload = async () => { try { // 确保内容已加载(可根据需要调整延迟) await new Promise(resolve => setTimeout(resolve, 2000)); // 获取 iframe 内的完整 DOM const doc = iframe.contentDocument || iframe.contentWindow.document; const html = doc.documentElement.outerHTML; // 清理 iframe document.body.removeChild(iframe); resolve(html); } catch (error) { reject(error); } }; // 处理加载错误 iframe.onerror = () => { document.body.removeChild(iframe); reject(new Error('Failed to load page')); }; // 开始加载 iframe.src = url; }); } async function fetchAllPage() { var startFloor = Number(startInput.value) var startPage = floor2page(startFloor) var endFloor = Number(endInput.value) var endPage = floor2page(endFloor) const mergedResults = []; for (let page = startPage; page <= endPage; page++) { const url = new URL(findUrlForPage(page), window.location.origin).href; // 发起 GET 请求 const html = await loadPageInBackground(url); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); // 处理当前页结果 var nowStartFloor = page2floor(page) nowStartFloor = (nowStartFloor < startFloor) ? startFloor : nowStartFloor; var nowEndFloor = page2floor(page) + 19 nowEndFloor = (nowEndFloor > endFloor) ? endFloor : nowEndFloor; const currentResults = scanAllFloorsInPage(doc, nowStartFloor, nowEndFloor); mergedResults.push(...currentResults); // 按顺序追加结果 // 插入到快速发帖框 appendTextarea(`页码 ${page} 完成\n`) } return mergedResults; } var startInput, endInput; function onAngaBtnClick() { updateTextarea("开始统计安价\n") fetchAllPage().then(results => { appendTextarea(`安价数据:\n${angaToText(results)}\n\n`) }) } function createAngaButton(textContent, onClick) { const btn = document.createElement('a'); btn.className = 'uitxt1'; // 保持样式统一 btn.style.cssText = 'font-size:1.23em; margin-left:10px;'; btn.textContent = textContent; // 添加点击事件 btn.addEventListener('click', onClick); return btn; } // 获取回复按钮,小的那种 function getQuickReplyButtons() { const allButtons = Array.from(document.getElementsByClassName('uitxt1')); const targetButtons = allButtons.filter(btn => { const span = btn.querySelector('span'); return span && span.textContent.trim() === '发表回复'; }); return targetButtons; } function getReplyButtons() { // 获取所有符合条件的元素,再根据文本内容过滤 const buttons = Array.from(document.getElementsByClassName('uitxt1')); const replyBtns = buttons.filter(btn => btn.textContent.trim() === '发表回复(Ctrl+Enter)' ); return replyBtns } //生成输入框 function createInputsContainer() { const container = document.createElement('div'); container.style.cssText = 'margin:10px 0; text-align:left;'; startInput = document.createElement('input'); startInput.type = 'text'; startInput.placeholder = '开始楼层'; startInput.style.cssText = 'width:48%; margin-right:2%;'; endInput = document.createElement('input'); endInput.type = 'text'; endInput.placeholder = '结束楼层'; endInput.style.cssText = 'width:48%;'; container.append(startInput, endInput); return container; } function injectFloorInputs() { const targetButton = document.querySelector('td.c2 button[title="插入表情"]'); if (!targetButton) return; const existingInputs = document.querySelector('td.c2 input[placeholder="开始楼层"]'); if (existingInputs) return; const container = createInputsContainer(); targetButton.parentNode.insertBefore(container, targetButton); } // 生成安价按钮的类名标识 const ANGA_BUTTON_CLASS = 'anga-json-btn'; // 注入按钮 function injectButtons() { getQuickReplyButtons().forEach(originBtn => { const container = originBtn.closest('td'); if (!container || container.querySelector(`.${ANGA_BUTTON_CLASS}`)) return; const newBtn = createAngaButton("本页安价", onQuickAngaBtnClick); container.insertAdjacentElement('afterend', newBtn); }); getReplyButtons().forEach(originBtn => { const container = originBtn.closest('td'); if (!container || container.querySelector(`.${ANGA_BUTTON_CLASS}`)) return; const newBtn = createAngaButton("生成安价", onAngaBtnClick); container.insertAdjacentElement('afterend', newBtn); }); } const observer = new MutationObserver((mutations) => { const element = document.querySelector('a.uitxt1[style="font-size: 1.23em; margin-left: 10px;"]'); if (element !== null) { return } injectButtons() injectFloorInputs() }); // 配置观察选项: observer.observe(document.body, { childList: true, subtree: true, attributes: false, characterData: false }); injectButtons() injectFloorInputs() })();