// ==UserScript==
// @name 2025国家中小学智慧教育平台寒假研修,可秒刷视频!
// @namespace http://tampermonkey.net/zzzzzzys_国家中小学
// @version 1.0.1
// @description 适用2025国家中小学智慧教育平台寒假研修,可快速通过视频学习。即秒刷!欢迎加入QQ交流群,及时获取最新消息!请勿随意二次发布代码,尊重原创!
// @author zzzzzzys
// @match https://basic.smartedu.cn/teacherTraining/courseDetail?*
// @match https://basic.smartedu.cn/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=smartedu.cn
// @require https://fastly.jsdelivr.net/npm/[email protected]/crypto-js.min.js
// @resource https://cdn.staticfile.org/limonte-sweetalert2/11.7.1/sweetalert2.min.css
// @require https://fastly.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js
// @connect basic.smartedu.cn
// @connect x-study-record-api.ykt.eduyun.cn
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @license GPL-3.0-or-later
// ==/UserScript==
/*****************************
* 盗版可耻
* 请尊重原创劳动成果!
* 作者:zzzzzzys
* https://greasyfork.org/zh-CN/users/1176747-zzzzzzys
* 搬运可耻
****************************/
(function () {
'use strict';
let qqUrl="https://qm.qq.com/q/rDCbvTiV9K"
let qqNum="570337037"
let requestObj = {
fullsData: {
url: "https://s-file-2.ykt.cbern.com.cn/teach/s_course/v2/activity_sets/3efdb592-138e-4854-8964-5e10f6011f33/fulls.json",
method: "GET",
},
resourceLearningPositions: {
url: "https://x-study-record-api.ykt.eduyun.cn/v1/resource_learning_positions/",
method: "PUT"
},
}
//样式
let style = `.button-3 {
position: fixed;
appearance: none;
background-color: #e52b13;
border: 1px solid rgba(27, 31, 35, .15);
border-radius: 6px;
box-shadow: rgba(27, 31, 35, .1) 0 1px 0;
box-sizing: border-box;
color: #ffffff;
cursor: pointer;
display: inline-block;
font-family: -apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
font-size: 14px;
font-weight: 600;
line-height: 20px;
padding: 6px 16px;
left: 20px;
top: 300px;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
}
.button-3:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none;
outline: none;
}
.button-3:hover {
background-color: #2c974b;
}
.button-3:focus {
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none;
}
.button-3:disabled {
background-color: #94d3a2;
border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8);
cursor: default;
}
.button-3:active {
background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
}`
const createFloatingButton = () => {
// 如果按钮已存在则先移除旧实例
const existingBtn = document.getElementById('zs-helper-btn');
if (existingBtn) existingBtn.remove();
// 直接创建按钮元素(去掉外层div嵌套)
const btn = document.createElement('div');
btn.id = 'zs-helper-btn'; // 确保唯一ID直接设置在元素上
btn.style.cssText = `
position: fixed;
left: 0px;
top: 50%;
transform: translateY(-50%);
background: #FF4DAF;
color: white;
padding: 12px 24px;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 4px 12px rgba(255,77,175,0.3);
z-index: 2147483647; /* 使用最大z-index值 */
transition: 0.3s;
font-family: 'Microsoft Yahei', sans-serif;
white-space: nowrap;
display: flex;
align-items: center;
gap: 8px;
`;
// 添加内部HTML内容
btn.innerHTML = `
<svg style="width:18px;height:18px;fill:white;" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"/>
</svg>
<span>使用指南</span>
`;
// 使用更可靠的事件监听方式
const handleHover = () => {
btn.style.transform = 'translateY(-50%) scale(1.05)';
btn.style.boxShadow = '0 6px 16px rgba(255,77,175,0.4)';
};
const handleLeave = () => {
btn.style.transform = 'translateY(-50%) scale(1)';
btn.style.boxShadow = '0 4px 12px rgba(255,77,175,0.3)';
};
btn.addEventListener('mouseenter', handleHover);
btn.addEventListener('mouseleave', handleLeave);
btn.addEventListener('click', showGuideDialog);
document.body.appendChild(btn);
return btn;
};
// 显示操作指南弹窗
const showGuideDialog = () => {
if (Swal) {
Swal.fire({
title: '<span style="color: #FF4DAF; font-size:26px; display: flex; align-items: center; gap:8px;">📚 智能刷课指南 <div style="font-size:12px; color:#95a5a6; margin-left:auto;">v1.0.0</div></span>',
html: `
<div style="text-align: left; max-width: 720px; line-height: 1.8;">
<!-- 操作步骤 -->
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<div style="color: red; font-weight:500; margin-bottom:10px;">
播放页面未正常生效请刷新页面!
</div>
<div style="color: #2c3e50; font-weight:500; margin-bottom:10px;">
🚀 极速操作流程<br>
</div>
<div style="display: grid; grid-template-columns: 32px 1fr; gap: 10px; align-items: center;">
<div style="background: #FF4DAF; color: white; width:24px; height:24px; border-radius:50%; text-align:center; line-height:24px;">1</div>
<div>进入2025研修课程播放页面</div>
<div style="background: #FF4DAF; color: white; width:24px; height:24px; border-radius:50%; text-align:center; line-height:24px;">2</div>
<div>等待视频加载完成(<span style="color:#e74c3c">未自动播放时</span>)</div>
<div style="background: #FF4DAF; color: white; width:24px; height:24px; border-radius:50%; text-align:center; line-height:24px;">3</div>
<div>点击左侧<span style="color:#FF4DAF; font-weight:bold">「即刻开刷」</span>按钮</div>
</div>
</div>
<!-- 注意事项 -->
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-bottom:20px;">
<div style="border-left: 3px solid #FF4DAF; padding-left:12px;">
<div style="color: #e74c3c; font-weight:500; margin-bottom:8px;">⚠️ 重要提醒</div>
<ul style="margin:0; padding-left:18px; color:#7f8c8d; font-size:14px;">
<li>视频最后剩下5秒需要看完</li>
<li>请勿主动点击播放</li>
<li>建议刷完全部视频再刷新,观看最后的几秒</li>
</ul>
</div>
<div style="border-left: 3px solid #27ae60; padding-left:12px;">
<div style="color: #27ae60; font-weight:500; margin-bottom:8px;">💡 高效技巧</div>
<ul style="margin:0; padding-left:18px; color:#7f8c8d; font-size:14px;">
<li>先刷一个视频</li>
<li>点击另外一个视频</li>
<li>再点击回刚刷的视频,播放完最后5s</li>
</ul>
</div>
</div>
<!-- 社群入口 -->
<div style="background: linear-gradient(135deg, #FF4DAF 0%, #FF6B6B 100%); padding:15px; border-radius:8px; color:white;">
<div style="display: flex; align-items: center; gap:15px;">
<img src="https://qzonestyle.gtimg.cn/qzone/qzact/act/external/tiqq/logo.png"
style="height:36px; border-radius:6px;">
<div>
<div style="font-size:16px; font-weight:bold; margin-bottom:4px;">教师交流群</div>
<div style="font-size:12px; opacity:0.9;">获取实时支持 | 最新功能优先体验</div>
</div>
</div>
<a href="${qqUrl}"
target="_blank"
style="display: block; margin-top:12px; padding:10px;
background: rgba(255,255,255,0.2); border-radius:6px;
text-align:center; text-decoration:none; color:white !important;
transition:0.3s; font-weight:500;">
🎯 点击加入QQ群:${qqNum}
</a>
</div>
</div>
`,
confirmButtonText: "已了解,开始减负之旅 →",
confirmButtonColor: "#FF4DAF",
showCancelButton: true,
cancelButtonText: "不在显示此窗口",
cancelButtonColor: "#95a5a6",
width: 760,
customClass: {
popup: 'animated pulse',
title: 'swal-title-custom'
},
footer: '<div style="color:#bdc3c7; font-size:12px;">请合理使用本工具</div>'
}).then((result) => {
// console.log(result);
// console.log(Swal.DismissReason.cancel);
if (result.dismiss === Swal.DismissReason.cancel) {
// 跳转到课程列表页或其他操作
localStorage.setItem('noMoreDialog',"ture")
}
});
}
}
// 初始化逻辑
// 初始化逻辑优化
const init = () => {
// 创建悬浮按钮
const floatBtn = createFloatingButton();
// 添加防DOM清理监听(优化版)
const observer = new MutationObserver(mutations => {
if (!document.body.contains(floatBtn)) {
createFloatingButton();
}
});
observer.observe(document.body, { childList: true });
// 添加CSS保护
const style = document.createElement('style');
style.textContent = `
#zs-helper-btn {
pointer-events: auto !important;
opacity: 1 !important;
visibility: visible !important;
}
#zs-helper-btn:hover {
transform: translateY(-50%) scale(1.05) !important;
}
`;
document.head.appendChild(style);
};
window.onload = function () {
init()
if (!location.href.includes("courseDetail") && !localStorage.getItem("noMoreDialog")) {
showGuideDialog()
return
}
if(!location.href.includes("courseDetail") ) {
return;
}
let myStyle = document.createElement('style')
myStyle.innerHTML = style;
document.head.appendChild(myStyle);
/*let intercept=GM_GetValue*/
let div = document.createElement('div');
div.innerHTML = `<div style="left: 0;top: 300px;" id="my1" class="button-3" >即刻开刷</div>
<div style="left: 0;top: 340px;" id="my2" class="button-3" >2222</div>`
document.body.appendChild(div);
document.getElementById('my1').addEventListener("click", async () => {
try {
await setProgress(requestObj.resourceLearningPositions.url + getResourceId() + '/' + getDynamicToken().token["user_id"], getVideoTime())
if (Swal) {
Swal.fire({
title: "刷课成功!",
html: `
<div style="text-align: left;">
<p>此视频只剩下最后5s,请刷新后再观看!建议先刷完目录下视频再刷新!</p>
<hr style="margin: 10px 0;">
<p>💬 <strong>教师交流QQ群</strong></p>
<p>欢迎加入交流群获取更多支持:<br>
<a href="${qqUrl}"
target="_blank"
style="color: #FF4DAFFF; text-decoration: underline;">
点击加入QQ群:${qqNum}
</a>
</p>
</div>
`,
icon: 'success',
confirmButtonColor: "#FF4DAFFF",
// cancelButtonText: "取消,等会刷新",
// 作者:zzzzzzys
// https://greasyfork.org/zh-CN/users/1176747-zzzzzzys
// 搬运可耻
confirmButtonText: "确定",
}).then((result) => {
if (result.isConfirmed) {
}
});
}
} catch (e) {
console.error(e)
if (Swal) {
Swal.fire({
title: "失败!",
text: e,
icon: 'error',
// showCancelButton: true,
confirmButtonColor: "#FF4DAFFF",
// cancelButtonText: "取消,等会刷新",
confirmButtonText: "点击去反馈",
}).then((result) => {
if (result.isConfirmed) {
window.open("https://greasyfork.org/zh-CN/scripts/525037/feedback")
}
});
}
}
})
document.getElementById('my2').addEventListener('click', function() {
Swal.fire({
title: '<span style="font-size:24px; color: #FF4DAF;">欢迎加入交流群</span>',
html: `
<div style="text-align: left; max-width: 580px; line-height: 1.7; font-size: 14px;">
<!-- 社群入口 -->
<div style="background: #f8f9fa; padding: 15px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin:0 0 12px 0; color: #2c3e50;">
<img src="https://qzonestyle.gtimg.cn/qzone/qzact/act/external/tiqq/logo.png"
style="height:20px; vertical-align:middle;">
QQ群
</h3>
<div style="color: #e74c3c; font-weight:500;">
群号:<span style="font-family: monospace;">${qqNum}</span>
</div>
<a href="${qqUrl}"
target="_blank"
style="display: inline-block; margin-top:10px; padding:8px 20px;
background: #FF4DAF; color: white !important; border-radius:20px;
text-decoration: none; transition:0.3s;">
🚀 一键加入群聊
</a>
</div>
<!-- 核心价值 -->
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px;">
<!-- 左列 -->
<div style="padding-right:15px; border-right:1px dashed #eee;">
<div style="color: #27ae60; margin-bottom:15px;">
<h4 style="margin:0 0 8px 0; font-size:15px;">📚 减负工具</h4>
<!-- <ul style="margin:0; padding-left:18px;">-->
<!-- <li>自动化备课工具套件</li>-->
<!-- <li>智能学情分析报告</li>-->
<!-- <li>教学资源智能检索</li>-->
<!-- </ul>-->
</div>
<div style="color: #2980b9; margin-top:15px;">
<h4 style="margin:0 0 8px 0; font-size:15px;">🛡️ 使用规范</h4>
<ul style="margin:0; padding-left:18px;">
<li>仅限中小学课程使用</li>
<li>禁止商业倒卖行为</li>
<li>请勿批量自动化操作大量刷课</li>
</ul>
</div>
</div>
<!-- 右列 -->
<div style="padding-left:15px;">
<div style="color: #e67e22;">
<h4 style="margin:0 0 8px 0; font-size:15px;">⚖️ 版权声明</h4>
<ul style="margin:0; padding-left:18px;">
<li>本工具完全免费</li>
<li>源码禁止二次传播</li>
<!-- <li>保留原创法律权利</li>-->
</ul>
</div>
<div style="color: #9b59b6; margin-top:15px;">
<h4 style="margin:0 0 8px 0; font-size:15px;">💌 联系我们</h4>
<ul style="margin:0; padding-left:18px;">
<!-- <li>反馈建议:[email protected]</li>-->
<li>紧急问题:请私聊群管理员</li>
</ul>
</div>
</div>
</div>
</div>
`,
icon: 'info',
confirmButtonColor: "#FF4DAF",
confirmButtonText: "2222",
showCloseButton: true,
width: 680,
showDenyButton: true,
denyButtonText: '<img src="https://img.icons8.com/fluency/24/star--v1.png" style="height:18px; vertical-align:middle;"> 前往好评', // 带图标的按钮
denyButtonColor: '#FFC107',
focusDeny: false,
showCancelButton: false,
// 新增按钮回调
preDeny: () => {
window.open("https://greasyfork.org/zh-CN/scripts/525037/feedback", "_blank");
return false; // 阻止弹窗关闭
},
customClass: {
denyButton: 'swal-custom-deny',
popup: 'swal-custom-popup',
title: 'swal-custom-title'
},
footer: '<div style="color:#95a5a6; font-size:12px;">请合理使用。</div>'
});
});
}
function getVideoTime() {
return Math.round(document.querySelector('video').duration)
}
function getResourceId() {
// 获取目标元素
const divElement = document.querySelector('div.vjs-poster');
if (divElement) {
const bgImage = divElement.style.backgroundImage;
const uuidPattern = /assets\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/;
const match = bgImage.match(uuidPattern);
if (match) {
const resId = match[1];
console.log(resId);
return resId
}
}
throw Error("can not get ResourceId!")
}
function getDynamicToken() {
try {
const pattern = /^ND_UC_AUTH-([0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12})&ncet-xedu&token$/;
for (let key of Object.keys(localStorage)) {
if (pattern.test(key)) {
return {
key: key,
appId: key.match(pattern)[1],
token: JSON.parse(JSON.parse(localStorage.getItem(key)).value)
};
}
}
throw Error("Invalid token! can not get loginInfo!");
} catch (err) {
throw Error("At:getDynamicToken>>" + err);
}
}
// const tokenData = getDynamicToken();
// if (tokenData) {
// console.log("完整键名:", tokenData.key);
// console.log("用户UUID:", tokenData.uuid);
// console.log("Token值:", tokenData.token);
// }
// 作者:zzzzzzys
// https://greasyfork.org/zh-CN/users/1176747-zzzzzzys
// 搬运可耻
const getMACAuthorizationHeaders = function (url, method) {
let n = getDynamicToken().token
return He(url, method, {
accessToken: n.access_token,
macKey: n.mac_key,
diff: n.diff
});
}
function Ze(e) {
for (var t = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), n = "", r = 0; r < e; r++)
n += t[Math.ceil(35 * Math.random())];
return n
}
function Fe(e) {
return (new Date).getTime() + parseInt(e, 10) + ":" + Ze(8)
}
function ze(e, t, n, r) {
let o = {
relative: new URL(e).pathname,
authority: new URL(e).hostname
}
let i = t + "\n" + n.toUpperCase() + "\n" + o.relative + "\n" + o.authority + "\n";
return CryptoJS.HmacSHA256(i, r).toString(CryptoJS.enc.Base64)
}
function He(e) {
// 作者:zzzzzzys
// https://greasyfork.org/zh-CN/users/1176747-zzzzzzys
// 搬运可耻
let t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : "GET"
, n = arguments.length > 2 ? arguments[2] : void 0
, r = n.accessToken
, o = n.macKey
, i = n.diff
, s = Fe(i)
, a = ze(e, s, t, o);
return 'MAC id="'.concat(r, '",nonce="').concat(s, '",mac="').concat(a, '"')
}
const setProgress = function (url, duration) {
const info = getDynamicToken()
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
'url': url,
method: 'PUT',
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"authorization": getMACAuthorizationHeaders(url, 'PUT'),
"cache-control": "no-cache",
"pragma": "no-cache",
"content-type": "application/json",
"sdp-app-id": info.appId,
"sec-ch-ua": "\"Not A(Brand\";v=\"8\", \"Chromium\";v=\"132\", \"Microsoft Edge\";v=\"132\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"host": "x-study-record-api.ykt.eduyun.cn",
"origin": "https://basic.smartedu.cn",
"referer": "https://basic.smartedu.cn/",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
},
data: JSON.stringify({position: duration - 3}),
// fetch:true,
onload: function (res) {
console.log('请求成功')
console.log(res)
if (res.status === 200) {
console.log("刷课成功!")
resolve(res)
}
},
onerror: function (err) {
reject('请求错误!' + err.toString())
}
})
})
}
})()