// ==UserScript==
// @name 提取章节内容1.0
// @namespace http://tampermonkey.net/
// @version 2024-12-25
// @description 提取章节标题和内容,展示状态框,提供操作按钮
// @author cheniyy
// @match *://*.bqgui.cc/*
// @match *://*.bq01.cc/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=bqgui.cc
// @grant none
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/521775/%E6%8F%90%E5%8F%96%E7%AB%A0%E8%8A%82%E5%86%85%E5%AE%B910.user.js
// @updateURL https://update.greasyfork.icu/scripts/521775/%E6%8F%90%E5%8F%96%E7%AB%A0%E8%8A%82%E5%86%85%E5%AE%B910.meta.js
// ==/UserScript==
(function() {
'use strict';
// 提取页面的章节标题和内容
const chapterTitleElement = document.querySelector('h1.wap_none');
const chapterContentElement = document.querySelector('div#chaptercontent');
const chapterTitle = chapterTitleElement ? chapterTitleElement.textContent.trim() : null;
const chapterContent = chapterContentElement ? chapterContentElement.innerHTML.trim() : null;
let currentUrl = window.location.href; // 获取当前页面的URL
// 处理题目提取,去掉所有 HTML 标签
function getTextFromHTML1(html) {
const doc = new DOMParser().parseFromString(html, 'text/html');
return doc.body.textContent || "";
}
function getTextFromHTML2(html) {
const doc = new DOMParser().parseFromString(html, 'text/html');
let text = doc.body.innerHTML
.replace(/
/gi, '\n') // 替换
标签为换行符
.replace(/<\/p>/gi, '\n\n') // 将
标签转换为换行符,并保留段落间距
.replace(/<\/div>/gi, '\n\n') // 将 标签转换为换行符,并保留段落间距
.replace(/ /gi, ' ') // 替换 为普通空格
.replace(/<[^>]+>/g, ''); // 去除所有HTML标签
// 去掉首尾空白
text = text.trim();
// 处理文本缩进(去掉多余的空格或空行)
text = text.replace(/^[\s]+/gm, ''); // 去掉每行前面的空白字符(防止多重缩进)
// 添加首行缩进(例如,四个空格)
text = text.replace(/^(.+)/gm, ' $1'); // 给每行加上四个空格,模拟首行缩进
return text;
}
// 如果没有章节内容或标题,设置错误提示
let statusMessage = `章节标题:${chapterTitle || '未找到标题'}`;
if (!chapterTitle || !chapterContent) {
statusMessage = `未找到章节内容,请检查页面结构!`;
}
// 合并标题和内容
const pureTextContent = chapterTitle && chapterContent ? getTextFromHTML1(chapterTitle) + "\n\n" + getTextFromHTML2(chapterContent) : '';
// 创建一个浮动框,用于显示脚本状态
const statusBox = document.createElement('div');
statusBox.style.position = 'fixed';
statusBox.style.top = '20px';
statusBox.style.left = '20px';
statusBox.style.width = '300px';
statusBox.style.padding = '15px';
statusBox.style.backgroundColor = '#fff';
statusBox.style.border = '1px solid #ccc';
statusBox.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
statusBox.style.zIndex = '9999';
statusBox.style.borderRadius = '8px';
statusBox.style.fontFamily = 'Arial, sans-serif';
// 使 statusBox 可拖动
let isDragging = false;
let offsetX, offsetY;
statusBox.style.position = 'absolute';
statusBox.style.cursor = 'move';
statusBox.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - statusBox.offsetLeft;
offsetY = e.clientY - statusBox.offsetTop;
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
statusBox.style.left = `${e.clientX - offsetX}px`;
statusBox.style.top = `${e.clientY - offsetY}px`;
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
// 显示脚本状态文字
const statusText = document.createElement('div');
statusText.textContent = statusMessage;
// 添加按钮
const copyButton = document.createElement('button');
copyButton.textContent = '复制';
copyButton.style.marginTop = '10px';
copyButton.style.padding = '8px 12px';
copyButton.style.backgroundColor = '#4CAF50';
copyButton.style.color = '#fff';
copyButton.style.border = 'none';
copyButton.style.borderRadius = '5px';
copyButton.style.cursor = 'pointer';
copyButton.onclick = async () => {
try {
await navigator.clipboard.writeText(pureTextContent);
alert('章节内容已复制!');
} catch (err) {
alert('复制失败,请检查剪贴板权限');
}
};
const viewButton = document.createElement('button');
viewButton.textContent = '查看复制内容';
viewButton.style.marginTop = '10px';
viewButton.style.padding = '8px 12px';
viewButton.style.backgroundColor = '#2196F3';
viewButton.style.color = '#fff';
viewButton.style.border = 'none';
viewButton.style.borderRadius = '5px';
viewButton.style.cursor = 'pointer';
viewButton.onclick = () => {
const newWindow = window.open('', '_blank', 'width=800,height=600');
const contentHTML = `
章节内容
`;
newWindow.document.write(contentHTML);
newWindow.document.close();
};
const saveButton = document.createElement('button');
saveButton.textContent = '保存为文件';
saveButton.style.marginTop = '10px';
saveButton.style.padding = '8px 12px';
saveButton.style.backgroundColor = '#FF5722';
saveButton.style.color = '#fff';
saveButton.style.border = 'none';
saveButton.style.borderRadius = '5px';
saveButton.style.cursor = 'pointer';
saveButton.onclick = () => {
const blob = new Blob([pureTextContent], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `${chapterTitle || '章节'} - 内容.txt`;
link.click();
};
const settingsButton = document.createElement('button');
settingsButton.textContent = '设置';
settingsButton.style.marginTop = '10px';
settingsButton.style.padding = '8px 12px';
settingsButton.style.backgroundColor = '#FFC107';
settingsButton.style.color = '#fff';
settingsButton.style.border = 'none';
settingsButton.style.borderRadius = '5px';
settingsButton.style.cursor = 'pointer';
settingsButton.onclick = () => {
const shouldDisplayTitle = confirm("是否显示章节标题?");
if (shouldDisplayTitle) {
statusText.textContent = `章节标题:${chapterTitle}`;
} else {
statusText.textContent = "章节内容";
}
};
// 将状态框元素添加到页面
statusBox.appendChild(statusText);
statusBox.appendChild(copyButton);
statusBox.appendChild(viewButton);
statusBox.appendChild(saveButton);
statusBox.appendChild(settingsButton);
document.body.appendChild(statusBox);
// 将提取的内容存储到本地存储(例如使用 localStorage)
if (chapterTitle && chapterContent) {
localStorage.setItem('chapterTitle', chapterTitle);
localStorage.setItem('chapterContent', chapterContent);
} else {
localStorage.removeItem('chapterTitle');
localStorage.removeItem('chapterContent');
}
// 自动加载下一章节(优化滚动判断)
let autoLoadEnabled = true;
window.addEventListener('scroll', () => {
if (autoLoadEnabled && (window.innerHeight + window.scrollY >= document.body.offsetHeight - 100)) {
autoLoadEnabled = false;
const parts = currentUrl.split('/');
const lastPart = parts.pop();
const chapNumber = parseInt(lastPart, 10);
if (!isNaN(chapNumber)) {
let nextUrl = parts.join('/') + '/' + (chapNumber + 1);
if (currentUrl.includes('.html')) {
nextUrl += '.html';
}
window.location.href = nextUrl;
}
}
});
})();