可以在国家智慧中小学的教材详情页下载PDF版本的教材
// ==UserScript==
// @name 国家智慧中小学教材下载
// @namespace http://tampermonkey.net/
// @version 0.704
// @description 可以在国家智慧中小学的教材详情页下载PDF版本的教材
// @author hydrachs
// @match https://basic.smartedu.cn/*?*contentId=*
// @match https://www.smartedu.cn/*?*contentId=*
// @match https://teacher.vocational.smartedu.cn/*?*contentId=*
// @match https://core.teacher.vocational.smartedu.cn/*?*contentId=*
// @icon https://i0.hdslb.com/bfs/static/jinkela/long/images/favicon.ico
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @connect s-file-2.ykt.cbern.com.cn
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
const style = document.createElement('style');
style.textContent = `
@keyframes fadeOut {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(10px); }
}
`;
document.head.appendChild(style);
function getContentId() {
const urlParams = new URLSearchParams(window.location.search);
const contentId = urlParams.get("contentId");
if (!contentId || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(contentId)) {
return null;
}
return contentId;
}
function getAccessToken() {
const maxRetries = 3;
const retryDelay = 500;
async function tryGetToken(retryCount) {
const authKeys = Object.keys(localStorage).filter(key =>
key.includes('ND_UC_AUTH') || key.includes('nd_uc_auth')
);
if (authKeys.length === 0) {
if (retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
return tryGetToken(retryCount + 1);
}
console.error('未找到认证相关的localStorage键');
return null;
}
for (const key of authKeys) {
const tokenDataStr = localStorage.getItem(key);
if (!tokenDataStr) continue;
let tokenData;
try {
tokenData = JSON.parse(tokenDataStr);
} catch (e) {
console.error('解析tokenDataStr失败:', e);
continue;
}
if (!tokenData.value) continue;
let parsedValue;
try {
parsedValue = JSON.parse(tokenData.value);
} catch (e) {
parsedValue = tokenData.value; // 若失败,直接使用value
}
if (parsedValue?.access_token) {
console.log('成功获取access_token');
return parsedValue.access_token;
}
}
if (retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, retryDelay));
return tryGetToken(retryCount + 1);
}
console.error('所有认证键解析均失败');
return null;
}
return tryGetToken(0);
}
function createTempAlert(message) {
const alertDiv = document.createElement('div');
alertDiv.style.cssText = `
position: fixed;
top: 60px;
right: 10px;
background: #e74c3c;
color: white;
padding: 10px 20px;
border-radius: 5px;
z-index: 10001;
animation: fadeOut 2s forwards;
`;
alertDiv.textContent = message;
document.body.appendChild(alertDiv);
setTimeout(() => alertDiv.remove(), 2000);
}
async function handleDownload() {
const button = document.getElementById('download-textbook-btn');
if (!button) return;
button.disabled = true;
button.textContent = '处理中...';
const contentId = getContentId();
if (!contentId) {
createTempAlert("当前页面不是有效的教材详情页!");
button.disabled = false;
button.textContent = "下载教材";
return;
}
const accessToken = await getAccessToken();
if (!accessToken) {
createTempAlert('未找到访问令牌,请确保已登录(不可用)并刷新页面');
button.disabled = false;
button.textContent = "下载教材";
return;
}
GM_xmlhttpRequest({
method: "GET",
url: `https://s-file-2.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/${contentId}.json`,
onload: function(response) {
try {
if (response.status !== 200) {
throw new Error(`API请求失败: ${response.status}`);
}
const data = JSON.parse(response.responseText);
const sourceItem = data.ti_items?.find(item => item.ti_file_flag === 'source');
if (!sourceItem || !sourceItem.ti_storages || !Array.isArray(sourceItem.ti_storages)) {
throw new Error('未找到有效的教材存储信息');
}
const fileName = sourceItem.ti_file_name || '教材.pdf';
const parts = sourceItem.ti_storages.flatMap(storage =>
storage.split('`').map(part => part.replace(/\s+/g, ' ').trim()).filter(part => part)
);
let currentUrl = '';
const possibleUrls = [];
for (const part of parts) {
if (part.startsWith('http')) {
if (currentUrl) possibleUrls.push(currentUrl);
currentUrl = part;
} else {
currentUrl += part;
}
}
if (currentUrl) possibleUrls.push(currentUrl);
const pdfUrl = possibleUrls.find(url => url.toLowerCase().endsWith('.pdf'));
if (!pdfUrl) {
throw new Error('未找到有效的PDF下载链接');
}
const downloadUrl = `${pdfUrl}?accessToken=${accessToken}`;
const link = document.createElement('a');
link.href = downloadUrl;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
button.textContent = '下载成功';
setTimeout(() => {
button.textContent = '下载教材';
button.disabled = false;
}, 2000);
} catch (e) {
console.error('下载失败:', e);
createTempAlert(`获取下载链接失败: ${e.message}`);
button.textContent = '下载教材';
button.disabled = false;
}
},
onerror: function(error) {
console.error('网络错误:', error);
createTempAlert(`获取下载链接失败: 网络请求错误 - ${error.message}`);
button.disabled = false;
button.textContent = '下载教材';
}
});
}
// 添加M键快捷键支持
function setupKeyboardShortcut() {
document.addEventListener('keydown', (e) => {
// 检查是否按下了M键,且没有在输入框等元素中
if (e.key.toLowerCase() === 'm' &&
!['INPUT', 'TEXTAREA', 'SELECT'].includes(e.target.tagName) &&
!e.target.isContentEditable) {
e.preventDefault();
handleDownload();
}
});
}
function createDraggableButton() {
const buttonWrapper = document.createElement('div');
const savedPos = GM_getValue('downloadBtnPosition');
const topPos = savedPos?.top || '20px';
const rightPos = savedPos?.right || '20px';
buttonWrapper.style.cssText = `
position: fixed;
top: ${topPos};
right: ${rightPos};
z-index: 10000;
cursor: move;
`;
document.body.appendChild(buttonWrapper);
const downloadButton = document.createElement('button');
downloadButton.id = 'download-textbook-btn';
downloadButton.textContent = '📚下载教材';
downloadButton.style.cssText = `
padding: 10px 15px;
border: none;
background: #26aaebff;
color: white;
font-weight: bold;
cursor: pointer;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
`;
downloadButton.addEventListener('click', handleDownload);
buttonWrapper.appendChild(downloadButton);
let isDragging = false;
let startX, startY;
let initialTop, initialRight;
const EDGE_MARGIN = 40;
buttonWrapper.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialTop = parseInt(buttonWrapper.style.top);
initialRight = parseInt(buttonWrapper.style.right);
buttonWrapper.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const diffY = e.clientY - startY;
const diffX = startX - e.clientX;
const rawTop = initialTop + diffY;
const rawRight = initialRight + diffX;
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const buttonWidth = downloadButton.offsetWidth;
const clampedTop = Math.max(EDGE_MARGIN, Math.min(rawTop, viewportHeight - EDGE_MARGIN));
const clampedRight = Math.max(EDGE_MARGIN, Math.min(rawRight, viewportWidth - buttonWidth - EDGE_MARGIN));
buttonWrapper.style.top = `${clampedTop}px`;
buttonWrapper.style.right = `${clampedRight}px`;
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
buttonWrapper.style.cursor = 'move';
GM_setValue('downloadBtnPosition', {
top: buttonWrapper.style.top,
right: buttonWrapper.style.right
});
}
});
const contentId = getContentId();
downloadButton.disabled = !contentId;
}
createDraggableButton();
setupKeyboardShortcut();
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址