Greasy Fork is available in English.
在国家中小学智慧教育平台网站中添加电子课本下载按钮,下载电子课本
// ==UserScript==
// @name 国家中小学智慧教育平台电子课本下载
// @description 在国家中小学智慧教育平台网站中添加电子课本下载按钮,下载电子课本
// @namespace https://github.com/BaeKey/smartedu
// @version 0.1.1
// @match https://basic.smartedu.cn/tchMaterial/detail?contentType=assets_document*
// @icon https://basic.smartedu.cn/favicon.ico
// @grant GM_xmlhttpRequest
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 创建按钮
function createDownloadButton() {
// 创建按钮元素
const button = document.createElement('button');
button.textContent = '下载文档';
// 设置按钮样式
button.style.position = 'fixed';
button.style.position = 'fixed';
button.style.top = '13vh';
button.style.right = '0.2vw';
button.style.padding = '1em 1em';
button.style.fontSize = '1rem';
button.style.backgroundColor = '#ff7d24';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '5px';
button.style.cursor = 'pointer';
button.style.zIndex = '1000';
// 按钮点击事件
button.onclick = main;
// 将按钮添加到页面中
document.body.appendChild(button);
}
window.onload = function() {
console.log("Window 加载完成,创建下载按钮...");
createDownloadButton();
};
var main = async function() {
var params = new URLSearchParams(document.location.search);
var id = params.get("contentId");
if (id) {
// 获取下载地址
var jsonUrl = `https://s-file-2.ykt.cbern.com.cn/zxx/ndrv2/resources/tch_material/details/${id}.json`;
try {
let response = await fetch(jsonUrl);
if (response.ok) {
let data = await response.json();
let tiItems = data.ti_items || [];
let downUrl = tiItems[1].ti_storages[0];
// 获取文件名
var fileName = document.querySelector("span.fish-breadcrumb-link").innerText;
if (fileName && downUrl) {
// 获取认证请求头
let authHeader = await authEncrypt(downUrl, 'GET');
// 发起下载请求
downloadWithHeaders(downUrl, fileName, authHeader);
}
}
}
catch (error) {
console.error('JSON 解析出错:', error);
}
}
}
// 下载函数,携带认证头
function downloadWithHeaders(url, fileName, authHeader) {
GM_xmlhttpRequest({
method: "GET",
url: url,
headers: {
"x-nd-auth": authHeader,
},
responseType: "blob",
onload: function(response) {
if (response.status === 200) {
// 创建一个 Blob 对象,并用来生成下载链接
const blob = response.response;
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName + ".pdf";
link.click();
} else {
console.error('下载失败,状态码:', response.status);
}
},
onerror: function(error) {
console.error('请求错误:', error);
}
});
}
// 生成认证的请求头
function authEncrypt(url, methodType) {
// 获取当前时间戳(毫秒)
const currentTimeMs = Date.now();
// 生成一个 700 到 900 之间的随机数
// 将参数 diff 转换为整数
const diff = Math.floor(Math.random() * (900 - 700 + 1)) + 700;
const diffInt = parseInt(diff, 10);
// 生成随机字符串
const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let randomStr = '';
for (let i = 0; i < 8; i++) {
randomStr += characters.charAt(Math.floor(Math.random() * characters.length));
}
// 拼接时间戳、整数部分和随机字符串
const nonce = `${currentTimeMs + diffInt}:${randomStr}`;
// 解析 URL 并构造相对路径
const urlObj = new URL(url);
const relativePath = urlObj.pathname + (urlObj.search || "") + (urlObj.hash || "");
const authority = urlObj.host;
// 构造签名字符串
const signatureString = `${nonce}\n${methodType}\n${relativePath}\n${authority}\n`;
// 获取 accessToken 和 macKey
let { accessToken, macKey } = getAccessTokenAndMacKeyFromLocalStorage();
// 计算 HMAC-SHA256
const macBytes = new TextEncoder().encode(macKey);
const signatureBytes = new TextEncoder().encode(signatureString);
return crypto.subtle.importKey(
"raw",
macBytes,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
).then(key => crypto.subtle.sign("HMAC", key, signatureBytes))
.then(hmacBuffer => {
// 转换为 Base64 编码的字符串
const base64Encoded = btoa(String.fromCharCode(...new Uint8Array(hmacBuffer)));
// 返回认证签名字符串
return `MAC id="${accessToken}",nonce="${nonce}",mac="${base64Encoded}"`;
});
}
// 获取access_token
function getAccessTokenAndMacKeyFromLocalStorage() {
// 遍历 localStorage 寻找符合条件的键
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
// 检查键是否以 "ND_UC_AUTH" 开头并以 "token" 结尾
if (key.startsWith("ND_UC_AUTH") && key.endsWith("token")) {
try {
// 获取键对应的值并解析为 JSON 对象
const valueJson = JSON.parse(localStorage.getItem(key));
// 检查是否存在 "value" 键且对应值也是 JSON
if (valueJson && valueJson.value) {
const innerValue = JSON.parse(valueJson.value);
// 获取 "access_token" 值
const accessToken = innerValue.access_token;
const macKey = innerValue.mac_key;
if (accessToken && macKey) {
return { accessToken, macKey };
} else {
console.warn("access_token 未找到");
return null;
}
} else {
console.warn("未找到 value 字段或 JSON 格式错误");
return null;
}
} catch (error) {
console.error("JSON 解析出错:", error);
return null;
}
}
}
console.warn("未找到符合条件的键");
return null;
}
})();