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.3
// @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 isMobileView() {
return !!document.querySelector('body.bodyVsmall');
}
// --- 通用功能 ---
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());
const cleanHTML = tempContainer.innerHTML;
tempContainer.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 showCustomPrompt(content) {
// Prevent multiple prompts
if (document.getElementById('angaCustomPrompt')) return;
// Overlay
const overlay = document.createElement('div');
overlay.id = 'angaCustomPrompt';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
`;
// Modal Box
const modal = document.createElement('div');
modal.style.cssText = `
background-color: #fff8e7;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
width: 90%;
max-width: 500px;
display: flex;
flex-direction: column;
gap: 15px;
`;
// Title
const title = document.createElement('h3');
title.textContent = '安价结果 (长按复制)';
title.style.cssText = `
margin: 0;
font-size: 18px;
font-weight: normal;
color: #591804;
text-align: center;
`;
// Textarea for content
const textarea = document.createElement('textarea');
textarea.readOnly = true;
textarea.value = content;
textarea.style.cssText = `
width: 100%;
height: 200px;
padding: 10px;
border: 1px solid #bd7b68;
border-radius: 4px;
font-family: inherit; /* Inherit font from body */
font-size: 16px; /* Match page font */
font-weight: normal;
line-height: 1.5;
white-space: pre-wrap; /* Ensure line breaks are respected */
word-wrap: break-word; /* Ensure long lines wrap */
resize: none;
box-sizing: border-box;
`;
// Close Button
const closeButton = document.createElement('button');
closeButton.textContent = '关闭';
closeButton.style.cssText = `
padding: 10px 20px;
border: none;
background-color: #bd7b68;
color: #fff8e7;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
align-self: center;
`;
// Event Listeners
closeButton.onclick = () => document.body.removeChild(overlay);
overlay.onclick = (e) => {
if (e.target === overlay) {
document.body.removeChild(overlay);
}
};
// Assemble
modal.appendChild(title);
modal.appendChild(textarea);
modal.appendChild(closeButton);
overlay.appendChild(modal);
document.body.appendChild(overlay);
// Select text for easy copying
textarea.select();
}
// --- 桌面端专用函数 ---
function scanAllFloorsInPage_desktop(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 scanAllFloorsInPage_mobile(root, fromFloor, toFloor) {
const results = [];
const floorElements = root.querySelectorAll('tr.postrow');
floorElements.forEach(floor => {
const floorNumberText = floor.querySelector('.postinfot.postoptswb')?.textContent;
const floorNumber = Number(floorNumberText?.match(/\d+/)?.[0]);
if (!floorNumber || 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 scanAllFloorsInPage(root, fromFloor, toFloor) {
if (isMobileView()) {
return scanAllFloorsInPage_mobile(root, fromFloor, toFloor);
} else {
return scanAllFloorsInPage_desktop(root, fromFloor, toFloor);
}
}
function onQuickAngaBtnClick() {
const urlParams = new URLSearchParams(window.location.search);
const page = urlParams.get('page') || '1';
const pageNumber = parseInt(page, 10);
var startFloor = page2floor(pageNumber)
const angas = scanAllFloorsInPage(document, startFloor, startFloor + 19);
if (angas.length === 0) {
alert('未检测到安价内容');
return;
}
const resultText = `安价数据:${angaToText(angas)}`;
if (isMobileView()) {
showCustomPrompt(resultText);
} else {
updateTextarea(resultText);
}
}
function findUrlForPage(page) {
const urlObj = new URL(window.location.href);
const params = urlObj.searchParams;
params.set('page', page);
return urlObj.toString();
}
async function loadPageInBackground(url) {
return new Promise((resolve, reject) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.onload = async () => {
try {
await new Promise(resolve => setTimeout(resolve, 2000));
const doc = iframe.contentDocument || iframe.contentWindow.document;
const html = doc.documentElement.outerHTML;
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;
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);
if (!isMobileView()) {
appendTextarea(`页码 ${page} 完成\n`)
}
}
return mergedResults;
}
var startInput, endInput;
function onAngaBtnClick() {
if (!isMobileView()) {
updateTextarea("开始统计安价\n")
}
fetchAllPage().then(results => {
const resultText = `安价数据:${angaToText(results)}`;
if (isMobileView()) {
showCustomPrompt(resultText);
} else {
appendTextarea(resultText);
}
})
}
function createAngaButton(textContent, onClick) {
const td = document.createElement('td');
const btn = document.createElement('a');
btn.className = 'uitxt1 cell rep txtbtnx nobr';
btn.style.cssText = 'font-size:1.23em;';
btn.textContent = textContent;
btn.addEventListener('click', onClick);
td.appendChild(btn);
return td;
}
function getReplyButtons_desktop() {
const buttons = Array.from(document.querySelectorAll('a.uitxt1'));
return buttons.filter(btn => btn.textContent.trim() === '发表回复(Ctrl+Enter)');
}
function getQuickReplyButtons_desktop() {
const allButtons = Array.from(document.querySelectorAll('a.uitxt1'));
return allButtons.filter(btn => {
const span = btn.querySelector('span');
return span && span.textContent.trim() === '发表回复';
});
}
function getReplyButtons_mobile() {
return Array.from(document.querySelectorAll('#postbbtm a, #postbtop a')).filter(a => a.textContent.trim() === '回复');
}
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_desktop() {
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);
}
function injectFloorInputs_mobile() {
const quickReplyTextarea = document.querySelector('#fast_post_c textarea');
if (!quickReplyTextarea) return;
const parentContainer = quickReplyTextarea.closest('div');
if (!parentContainer) return;
const existingInputs = parentContainer.querySelector('input[placeholder="开始楼层"]');
if (existingInputs) return;
const container = createInputsContainer();
parentContainer.insertBefore(container, quickReplyTextarea);
}
const ANGA_BUTTON_CLASS = 'anga-json-btn';
function injectButtons_desktop() {
getQuickReplyButtons_desktop().forEach(originBtn => {
const container = originBtn.closest('tr');
if (!container || container.querySelector(`.${ANGA_BUTTON_CLASS}`)) return;
const newBtn = createAngaButton("本页安价", onQuickAngaBtnClick);
newBtn.classList.add(ANGA_BUTTON_CLASS);
originBtn.closest('td').insertAdjacentElement('afterend', newBtn);
});
getReplyButtons_desktop().forEach(originBtn => {
const container = originBtn.closest('tr');
if (!container || container.querySelector(`.${ANGA_BUTTON_CLASS}`)) return;
const newBtn = createAngaButton("生成安价", onAngaBtnClick);
newBtn.classList.add(ANGA_BUTTON_CLASS);
originBtn.closest('td').insertAdjacentElement('afterend', newBtn);
});
}
function injectButtons_mobile() {
getReplyButtons_mobile().forEach(originBtn => {
const container = originBtn.closest('tr');
if (!container || container.querySelector(`.${ANGA_BUTTON_CLASS}`)) return;
const quickAngaBtn = createAngaButton("本页安价", onQuickAngaBtnClick);
quickAngaBtn.classList.add(ANGA_BUTTON_CLASS);
originBtn.closest('td').insertAdjacentElement('beforebegin', quickAngaBtn);
});
}
function initialize() {
if (isMobileView()) {
injectButtons_mobile();
injectFloorInputs_mobile();
} else {
injectButtons_desktop();
injectFloorInputs_desktop();
}
}
const observer = new MutationObserver(() => {
if (document.querySelector(`.${ANGA_BUTTON_CLASS}`)) {
return;
}
initialize();
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
initialize();
})();