// ==UserScript==
// @name 中少快乐阅读平台中少报刊资源下载器
// @namespace http://tampermonkey.net/
// @version 0.2.6
// @description 在幼儿画报期刊列表页为每一期添加“下载”按钮,自动下载 XML 中的高分辨率图像资源(href2)并打包为 ZIP 文件
// @author 野原新之布
// @license GPL-3.0-only
// @match http://202.96.31.36:8888/reading/onemagazine/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @note 2025.05.09-v0.2.6 修复幼儿画报课堂无法下载的bug
// @note 2025.05.09-v0.2.5 监听页面内容变化,确保动态加载的内容也能添加下载按钮
// @note 2025.05.09-v0.2.4 修复嘟嘟熊画报不能正常下载的bug
// @note 2025.05.09-v0.2.3 完善下载按钮的样式
// @note 2025.05.09-v0.2.2 修复被Chrome阻止下载的bug
// @note 2025.05.09-v0.2.1 修复无法下载报纸资源的bug
// @note 2025.05.09-v0.2 完成在期刊展示列表中添加下载按钮进行下载
// @note 2025.05.08-v0.1 完成阅读刊物时自动下载资源
// @downloadURL https://update.greasyfork.icu/scripts/535366/%E4%B8%AD%E5%B0%91%E5%BF%AB%E4%B9%90%E9%98%85%E8%AF%BB%E5%B9%B3%E5%8F%B0%E4%B8%AD%E5%B0%91%E6%8A%A5%E5%88%8A%E8%B5%84%E6%BA%90%E4%B8%8B%E8%BD%BD%E5%99%A8.user.js
// @updateURL https://update.greasyfork.icu/scripts/535366/%E4%B8%AD%E5%B0%91%E5%BF%AB%E4%B9%90%E9%98%85%E8%AF%BB%E5%B9%B3%E5%8F%B0%E4%B8%AD%E5%B0%91%E6%8A%A5%E5%88%8A%E8%B5%84%E6%BA%90%E4%B8%8B%E8%BD%BD%E5%99%A8.meta.js
// ==/UserScript==
(function() {
'use strict';
// 引入 JSZip 库
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js';
document.head.appendChild(script);
// 主函数:添加下载按钮
function addDownloadButtons() {
// 提取URL前缀
const origin = window.location.origin;
// 查找所有的期刊项(排除已包含下载按钮的项)
const items = document.querySelectorAll('li.col-md-3.col-sm-3.col-xs-6:not(.has-download-btn)');
items.forEach(item => {
// 标记已处理的项
item.classList.add('has-download-btn');
// 提取期号信息
const imgElement = item.querySelector('img');
const hrefElement = item.querySelector('a');
if (imgElement && hrefElement) {
// 从图片的 src 获取期号
const imgSrc = imgElement.src;
// 通过 '/fliphtml5/password/' 将路径分割
const imgSrcPath = imgSrc.split('/fliphtml5/password/')[1];
// 分割剩下的路径
const imgSrcPathParts = imgSrcPath.split('/');
if (imgSrcPathParts.length >= 7) {
// 路径的第三部分,如main、other
const channel = imgSrcPathParts[0];
// 提取期刊或报纸类别,如qikan、baozhi
const categoryPrefix = imgSrcPathParts[1];
// 提取具体分类,如wmakx、youerhb
const subCategory = imgSrcPathParts[2];
// 获取年份
const year = imgSrcPathParts[3];
// 获取月份
const month = imgSrcPathParts[4];
// 获取编号部分
const number = imgSrcPathParts[5];
// 获取 xmlFileName,不包括 "_cover_.jpg"
const xmlFileName = imgSrcPathParts[8].split('_cover_')[0] + '.xml';
// 获取详情页的 URL
const publicationUrl = origin + hrefElement.getAttribute('href');
// 获取 publicationTitle
fetch(publicationUrl)
.then(response => response.text())
.then(pageContent => {
// 从页面中提取
标签内容
const titleMatch = pageContent.match(/(.*?)<\/title>/);
const publicationTitle = titleMatch ? titleMatch[1] : '未找到标题';
const xmlUrl = `${origin}/fliphtml5/password/${channel}/${categoryPrefix}/${subCategory}/${year}/${month}/${number}/web/html5/tablet/${xmlFileName}`;
// 创建下载按钮
const downloadButton = document.createElement('button');
downloadButton.textContent = '下载';
downloadButton.classList.add('btn', 'btn-primary');
downloadButton.style.marginTop = '10px';
downloadButton.style.position = 'relative';
downloadButton.style.padding = '10px 20px';
downloadButton.style.width = '100%';
downloadButton.style.border = '2px solid #007bff';
downloadButton.style.backgroundColor = '#007bff';
downloadButton.style.color = 'white';
// 创建进度条的内层 div,初始时不显示进度条
const progressBarContainer = document.createElement('div');
progressBarContainer.style.position = 'absolute';
progressBarContainer.style.top = '0';
progressBarContainer.style.left = '0';
progressBarContainer.style.width = '0%';
progressBarContainer.style.height = '100%';
progressBarContainer.style.backgroundColor = '#28a745';
progressBarContainer.style.borderRadius = '5px';
progressBarContainer.style.transition = 'width 0.3s';
progressBarContainer.style.display = 'none';
// 将进度条嵌入到按钮内部
downloadButton.appendChild(progressBarContainer);
// 为按钮添加点击事件
downloadButton.addEventListener('click', () => {
// 显示进度条并立即开始进度更新
progressBarContainer.style.display = 'block';
progressBarContainer.style.width = '0%';
// 使用 fetch 直接加载 XML 文件
fetch(xmlUrl)
.then(response => {
if (!response.ok) throw new Error('XML 加载失败');
// 获取文件的文本内容
return response.text();
})
.then(xmlContent => {
// 解析 XML 内容
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlContent, 'application/xml');
// 获取 manifest 下的所有 item
const itemNodes = Array.from(xmlDoc.getElementsByTagName('item'));
if (itemNodes.length === 0) {
alert('XML 中没有找到 - 元素');
return;
}
const zip = new JSZip();
let completed = 0;
const baseUrl = xmlUrl.replace(/[^/]+\.xml$/, '');
// 下载每个 item 中的资源并打包到 ZIP
itemNodes.forEach((item, index) => {
const href2 = item.getAttribute('href2');
const href = item.getAttribute('href');
const filePath = href2 || href;
if (!filePath) return;
const fileUrl = baseUrl + filePath;
const fileName = filePath.split('/').pop();
fetch(fileUrl)
.then(res => res.arrayBuffer())
.then(data => {
zip.file(fileName, data);
completed++;
const percent = Math.round((completed / itemNodes.length) * 100);
// 更新进度条宽度
progressBarContainer.style.width = `${percent}%`;
// 所有文件下载完成后生成 ZIP 文件并触发下载
if (completed === itemNodes.length) {
zip.generateAsync({ type: 'blob' }).then(blob => {
const zipUrl = URL.createObjectURL(blob);
GM_download({
url: zipUrl,
name: `${year}${month}-${publicationTitle}.zip`.replace(/[\\/:*?"<>|]/g, '_'),
onload: () => URL.revokeObjectURL(zipUrl),
onerror: err => console.error('下载失败:', err)
});
});
}
})
.catch(err => console.error(`下载失败: ${fileUrl}`, err));
});
})
.catch(err => {
alert('加载 XML 文件失败');
console.error(err);
});
});
// 将下载按钮添加到期刊项中
item.appendChild(downloadButton);
})
.catch(err => console.error('获取刊物标题失败:', err));
}
}
});
}
// 初始运行
addDownloadButtons();
// 监听列表区域变化
const listPanel = document.getElementById('listPanel');
if (listPanel) {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
addDownloadButtons();
}
});
});
observer.observe(listPanel, {
childList: true,
subtree: true
});
}
// 监听年份切换按钮点击
document.querySelectorAll('.year-grid a.onereadlistlink').forEach(link => {
link.addEventListener('click', function() {
// 添加一个小延迟确保新内容加载完成
setTimeout(addDownloadButtons, 500);
});
});
})();