// ==UserScript==
// @name 福利吧论坛回复导出工具
// @namespace bbs_crewler.ai
// @version 0.2
// @description 导出当前页面的所有论坛回复内容
// @author Chris_C
// @match *://www.wnflb2023.com/*
// @grant GM_setClipboard
// @grant GM_download
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// localStorage包装函数,简化存取操作
const Storage = {
set: function(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get: function(key, defaultValue) {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : defaultValue;
},
remove: function(key) {
localStorage.removeItem(key);
}
};
// 创建导出按钮
const btn = document.createElement('button');
btn.style.position = 'fixed';
btn.style.right = '20px';
btn.style.bottom = '20px';
btn.style.zIndex = 9999;
btn.style.padding = '8px 12px';
btn.style.backgroundColor = '#3498db';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.borderRadius = '4px';
btn.style.cursor = 'pointer';
btn.textContent = '导出回复';
btn.onclick = startExport;
document.body.appendChild(btn);
// 创建进度指示器
const progressIndicator = document.createElement('div');
progressIndicator.style.position = 'fixed';
progressIndicator.style.left = '50%';
progressIndicator.style.top = '50%';
progressIndicator.style.transform = 'translate(-50%, -50%)';
progressIndicator.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
progressIndicator.style.color = 'white';
progressIndicator.style.padding = '20px';
progressIndicator.style.borderRadius = '5px';
progressIndicator.style.zIndex = 10000;
progressIndicator.style.display = 'none';
document.body.appendChild(progressIndicator);
// 检查是否正在爬取过程中
const isExporting = Storage.get('isExporting', false);
const allContent = Storage.get('allContent', []);
const currentPage = Storage.get('currentPage', 1);
const totalPages = Storage.get('totalPages', 0);
const startPageUrl = Storage.get('startPageUrl', '');
// 如果正在爬取过程中,自动继续
if (isExporting) {
setTimeout(() => {
continueExport();
}, 1000);
}
// 开始导出过程
function startExport() {
// 重置存储的数据
Storage.set('allContent', []);
Storage.set('currentPage', 1);
Storage.set('totalPages', getTotalPages());
Storage.set('startPageUrl', window.location.href);
Storage.set('isExporting', true);
// 开始爬取当前页面
continueExport();
}
// 生成1-3秒的随机延迟
function getRandomDelay() {
return Math.floor(Math.random() * 2000) + 1000; // 1000-3000ms之间的随机数
}
// 继续导出过程
async function continueExport() {
try {
// 获取存储的数据
let allContent = Storage.get('allContent', []);
let currentPage = Storage.get('currentPage', 1);
let totalPages = Storage.get('totalPages', 0);
let startPageUrl = Storage.get('startPageUrl', '');
// 禁用按钮
btn.disabled = true;
btn.style.backgroundColor = '#95a5a6';
// 显示进度
showProgress(`正在收集回复 (页面 ${currentPage}/${totalPages})...`);
// 收集当前页面的回复
const replies = extractReplies();
allContent = allContent.concat(replies);
// 更新进度和存储
showProgress(`已收集 ${allContent.length} 条回复 (页面 ${currentPage}/${totalPages})...`);
Storage.set('allContent', allContent);
// 检查是否有下一页和是否已完成所有页面
if (currentPage >= totalPages) {
// 所有页面处理完毕,导出结果
finishExport(allContent, startPageUrl);
return;
}
// 获取下一页链接
const nextPageLink = document.querySelector('.nxt');
if (!nextPageLink) {
// 没有找到下一页链接,提前结束
finishExport(allContent, startPageUrl);
return;
}
// 准备跳转到下一页
currentPage++;
Storage.set('currentPage', currentPage);
// 随机等待1-3秒后再跳转到下一页
const delay = getRandomDelay();
showProgress(`已收集 ${allContent.length} 条回复 (页面 ${currentPage-1}/${totalPages})... ${Math.round(delay/1000)}秒后跳转至下一页`);
setTimeout(() => {
// 导航到下一页
window.location.href = nextPageLink.href;
}, delay);
} catch (error) {
// 错误处理
hideProgress();
alert(`导出失败: ${error.message}`);
resetExportState();
}
}
// 完成导出过程
function finishExport(allContent, startPageUrl) {
try {
// 导出结果
const output = formatContent(allContent);
// 尝试复制到剪贴板
try {
GM_setClipboard(output, 'text/plain');
} catch (e) {
console.error('复制到剪贴板失败', e);
}
// 创建下载
try {
// 使用原生下载方法作为备选
const blob = new Blob([output], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `论坛回复_${Date.now()}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// 尝试使用GM_download作为首选
try {
GM_download({
url: url,
name: `论坛回复_${Date.now()}.txt`,
saveAs: true
});
} catch (e) {
console.log('GM_download不可用,已使用原生下载方法', e);
}
} catch (e) {
console.error('下载失败', e);
alert('下载失败,请尝试手动复制内容并保存');
}
// 重置状态
resetExportState();
// 隐藏进度
hideProgress();
// 如果不在起始页面,返回起始页面
if (window.location.href !== startPageUrl) {
alert(`成功导出 ${allContent.length} 条回复!即将返回起始页面。`);
window.location.href = startPageUrl;
} else {
alert(`成功导出 ${allContent.length} 条回复!`);
}
} catch (error) {
hideProgress();
alert(`导出失败: ${error.message}`);
resetExportState();
} finally {
// 恢复按钮状态
btn.disabled = false;
btn.style.backgroundColor = '#3498db';
}
}
// 重置导出状态
function resetExportState() {
Storage.remove('isExporting');
Storage.remove('allContent');
Storage.remove('currentPage');
Storage.remove('totalPages');
Storage.remove('startPageUrl');
}
function getTotalPages() {
// 查找页码信息
const pageInfo = document.querySelector('.pg');
if(!pageInfo) return 1;
// 查找最后一页链接
const lastPageLink = pageInfo.querySelector('.last');
if(lastPageLink) {
// 从文本中提取数字,例如 "... 32" 提取 32
const match = lastPageLink.textContent.match(/\d+/);
return match ? parseInt(match[0]) : 1;
}
// 如果没有明确的最后页链接,查找所有页码链接
const pageLinks = pageInfo.querySelectorAll('a');
let maxPage = 1;
pageLinks.forEach(link => {
const pageNum = parseInt(link.textContent);
if(!isNaN(pageNum) && pageNum > maxPage) {
maxPage = pageNum;
}
});
return maxPage;
}
function extractReplies() {
// 修改选择器以适配实际页面结构
return Array.from(document.querySelectorAll('#postlist .plhin')).map(item => {
// 排除广告和其他非回复元素
if (!item.querySelector('.authi a') || !item.querySelector('.t_f')) {
return null;
}
return {
author: item.querySelector('.authi a').innerText,
time: item.querySelector('[id^="authorposton"]')?.innerText.replace('发表于 ', '') || '未知时间',
content: item.querySelector('.t_f')?.innerText.replace(/\s+/g, ' ') || '内容为空',
floor: item.querySelector('[id^="postnum"]')?.innerText.trim() || '未知楼层'
};
}).filter(item => item !== null); // 过滤掉空值
}
function formatContent(replies) {
return replies.map((r, index) =>
`【${r.floor}】 ${r.author} 发表于 ${r.time}\n${r.content}\n${'-'.repeat(60)}`
).join('\n\n');
}
function showProgress(message) {
progressIndicator.textContent = message;
progressIndicator.style.display = 'block';
}
function hideProgress() {
progressIndicator.style.display = 'none';
}
})();