// ==UserScript==
// @name 四川精英人才教育培训中心_四川专继-课程助手
// @namespace http://tampermonkey.net/zzzzzzys_ 四川精英人才教育培训中心_四川专继-课程助手
// @version 1.0.0
// @copyright zzzzzzys.All Rights Reserved.
// @description 四川精英人才教育培训中心_四川专继-课程助手,脚本免费功能有限,可自动静音播放视频,防暂停。更全自动方法,请查看文档介绍!
// @author zzzzzzys
// @match https://www.sczjpx.com/*
// @require https://fastly.jsdelivr.net/npm/crypto-js@4.2.0/crypto-js.min.js
// @resource https://cdn.staticfile.org/limonte-sweetalert2/11.7.1/sweetalert2.min.css
// @require https://fastly.jsdelivr.net/npm/sweetalert2@11.12.2/dist/sweetalert2.all.min.js
// @require https://scriptcat.org/lib/637/1.4.5/ajaxHooker.js#sha256=EGhGTDeet8zLCPnx8+72H15QYRfpTX4MbhyJ4lJZmyg=
// @connect fc-mp-8ba0e2a3-d9c9-45a0-a902-d3bde09f5afd.next.bspapp.com
// @connect mp-8ba0e2a3-d9c9-45a0-a902-d3bde09f5afd.cdn.bspapp.com
// @connect zys-api.zzzzzzys.com
// @connect 1.zzzzzzys.com
// @grant unsafeWindow
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @grant GM_info
// @grant GM_addStyle
// @run-at document-start
// @antifeature ads 有弹窗广告,介绍完全自动化软件
// @antifeature payment 脚本基础功能使用免费,但需要使用完全自动化软件时,可能收费
// ==/UserScript==
// 变更记录(2026-09-09 定制版 v1.3.0):
// 1) 强化防暂停(需求1):pause 事件恢复 + 2s 看门狗 + 窗口聚焦/页面可见恢复
// 2) 自动连播(需求2):当前课程内前 N 个视频按顺序自动学习(播完→刷新→自动跳下一节)
// 3) 学满提醒(需求3):课程前 N 个视频全部学完后弹窗,内容待定 → 改 Utils.REMIND_TEXT
// 4) 范围限制(需求4):只自动学每门课的前 N(=Utils.MAX_COURSES)个视频,
// 第 N+1 个及之后打开页面仅提示、不自动播放
// 修改点集中在 Course.run / goNextCatalog / autoPlay 与 Utils 配置区。
class Runner {
constructor() {
this.runner = null
this.initAjaxHooker()
this.waitForDOMLoaded()
}
initAjaxHooker() {
ajaxHooker.hook(request => {
if (request.url.includes('vedioValidQuestions/getQuestions')) {
request.response = res => {
window.QuestionInfo = JSON.parse(res.responseText).data
console.log("QuestionInfo:", window.QuestionInfo)
};
} else if (request.url.includes('p/play/config')) {
request.response = res => {
const json = JSON.parse(res.responseText)
console.log("play/config:");
console.log(json);
window.playConfig = json.data
};
} else if (request.url.includes('learning/learnVerify/checkCode')) {
request.abort = true
request.response = res => {
res.responseText = '{"code":0,"msg":null,"data":{"data":"请勿频繁请求","status":9999}}'
}
} else if (request.url.includes('learning/learnVerify')) {
request.abort = true
}
})
}
waitForDOMLoaded() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.run());
} else {
// DOM已经就绪,直接执行
this.run();
}
}
run() {
this.runner = new Course("channel-hunau")
}
}
class Course {
constructor(channel = "channel-my") {
this.panel = new AuthWindow()
this.channel = new BroadcastChannel(channel)
this.VIP = false
this.running = false
this.init()
}
init() {
this.panel.setOnVerifyCallback(async (data) => {
this.url = await Utils.validateCode(data)
if (this.url) {
this.panel.setTip(Utils.vipText)
this.VIP = true
return true
}
})
this.panel.setOnBegin(() => {
if (!this.running) {
this.running = true
console.log("运行时:", this.VIP)
this.run().then(r => {
this.running = false
})
}
})
this.panel.setOnVIP(async () => {
if (!this.url) {
await this.panel.handleVerify()
}
await this.runVIP()
})
this.loadVIPStatus()
try {
Swal.fire({
title: "提示",
text: Utils.swFireText,
icon: 'info',
timer: 5000,
confirmButtonText: '确定',
timerProgressBar: true,
willClose: () => {
this.panel.startAutomation()
}
});
} catch (e) {
console.error(e)
this.panel.startAutomation()
}
}
loadVIPStatus() {
if (Utils.loadStatus()) {
this.panel.setTip(Utils.vipText)
this.VIP = true
} else {
this.panel.setTip(Utils.baseText)
this.VIP = false
}
console.log("VIP:", this.VIP)
}
async runVIP() {
try {
Utils.showUpgradeAlert()
} catch (error) {
console.error(error)
Swal.fire({
title: "高级功能执行失败!",
text: "若一直失败,请联系进行售后处理!",
icon: 'error',
confirmButtonText: '确定',
allowOutsideClick: false,
willClose: () => {
console.log(' 用户确认错误,脚本已停止');
}
});
}
}
/**
* 自动静音播放:设置静音 + 防暂停(需求1:解除暂停播放限制)
* 三层防护:
* 1) 事件驱动:站点失焦/切后台触发 pause 后自动恢复
* 2) 看门狗:每 2s 巡检,遇到未触发 pause 事件的暂停也能恢复
* 3) 焦点/可见性恢复:窗口重新聚焦、页面重新可见时兜底续播
*/
async autoPlay() {
const video = document.querySelector('video')
if (!video) return
const setMuted = () => {
video.volume = 0
video.muted = true
}
setMuted()
// 清理旧监听与未执行的恢复定时器,避免重复绑定
if (this._preventPauseHandler) {
video.removeEventListener('pause', this._preventPauseHandler)
clearTimeout(this._resumeTimer)
clearInterval(this._watchdogTimer)
window.removeEventListener('focus', this._focusHandler)
document.removeEventListener('visibilitychange', this._visibilityHandler)
}
const tryPlay = () => {
if (video.ended) return
setMuted()
const p = video.play()
if (p && p.catch) p.catch(e => console.error("恢复播放失败:", e))
}
// 1) 防暂停:站点在窗口失焦/切后台时可能强制 pause,收到 pause 后 500ms 自动恢复
this._preventPauseHandler = () => {
if (video.ended) return
clearTimeout(this._resumeTimer)
this._resumeTimer = setTimeout(() => {
console.log("检测到视频暂停,自动恢复播放...")
tryPlay()
}, 500)
}
video.addEventListener('pause', this._preventPauseHandler)
// 2) 看门狗:2s 巡检兜底
this._watchdogTimer = setInterval(() => {
if (video.paused && !video.ended) {
console.log("看门狗检测到暂停,自动恢复播放...")
tryPlay()
}
}, 2000)
// 3) 窗口重新聚焦 / 页面重新可见时兜底续播
this._focusHandler = () => {
if (video.paused && !video.ended) {
console.log("窗口重新聚焦,继续播放...")
tryPlay()
}
}
window.addEventListener('focus', this._focusHandler)
this._visibilityHandler = () => {
if (!document.hidden && video.paused && !video.ended) {
console.log("页面重新可见,继续播放...")
tryPlay()
}
}
document.addEventListener('visibilitychange', this._visibilityHandler)
// 播放结束后:本站会自动标记"已学完"并刷新页面;
// 若 1.5s 后页面仍未刷新,则手动刷新触发跳转流程
video.addEventListener('ended', () => {
console.log("视频播放结束,等待站点标记完成后刷新...")
setTimeout(() => {
console.log("页面未自动刷新,手动刷新进入下一节")
location.reload()
}, 1500)
}, { once: true })
try {
await video.play()
} catch (e) {
console.error("初始自动播放失败:", e)
}
}
/**
* 判断当前章节的学习状态
* 本站播完一节后会标记"已学完"并自动刷新页面,因此:
* 脚本启动时若发现当前章节已是"已学完",即视为"刚播完",触发跳转流程
* @returns {'completed'|'playing'|'unknown'}
*/
getCurrentSectionStatus() {
const path = location.pathname
const link = Array.from(document.querySelectorAll('.chapter-item a[href*="/study/"]'))
.find(a => (a.getAttribute('href') || '').indexOf(path) !== -1)
if (!link) return 'unknown'
const item = link.closest('.chapter-item')
if (!item) return 'unknown'
if (item.classList.contains('completed')) return 'completed'
const span = item.querySelector('span')
if (span && span.textContent.includes('已学完')) return 'completed'
return 'playing'
}
/**
* 从学习页侧边目录中提取所有章节链接(按 课程-小节 顺序,去重)
* 本站目录链接格式: /home/User/study/course_id/{课程ID}/id/{小节ID}.html
*/
getCatalogLinks() {
const seen = new Set()
const list = []
document.querySelectorAll('a[href*="/study/"]').forEach(a => {
const href = a.getAttribute('href') || ''
const m = href.match(/\/home\/User\/study\/course_id\/(\d+)\/id\/(\d+)\.html/)
if (!m) return
const key = `${m[1]}-${m[2]}`
if (seen.has(key)) return
seen.add(key)
// 读取侧边栏该章节的完成状态(div.chapter-item.completed / 文字"已学完")
const item = a.closest('.chapter-item')
const span = item ? item.querySelector('span') : null
const completed = item ? (item.classList.contains('completed') || !!(span && span.textContent.includes('已学完'))) : false
list.push({
courseId: m[1],
sectionId: m[2],
url: href,
title: Utils.escapeHtml(a.textContent.trim().replace(/\s+/g, ' ')),
completed: completed,
})
})
return list
}
/**
* 解析学习页 URL,返回 { courseId, sectionId };非小节播放页返回 null
*/
parseStudyUrl(url) {
const m = String(url || location.href).match(/\/home\/User\/study\/course_id\/(\d+)\/id\/(\d+)\.html/i)
return m ? { courseId: m[1], sectionId: m[2] } : null
}
/**
* 取"当前课程"目录:过滤出与 courseId 相同的全部小节(保持页面目录顺序),
* allowed = 其中前 Utils.MAX_COURSES 个小节(自动学习范围)。
* @returns {{items: Array, allowed: Array}}
*/
getCurrentCourseCatalog(courseId) {
const items = this.getCatalogLinks()
if (!courseId) return { items: [], allowed: [] }
const courseItems = items.filter(i => i.courseId === courseId)
const allowed = courseItems.slice(0, Utils.MAX_COURSES)
return { items: courseItems, allowed }
}
/**
* 需求4:超出范围提示。当前小节是某课程第 N+1 个及以后的视频时,只提示、不自动播放。
*/
showOutOfRangeTip(section, order) {
Swal.fire({
title: "已超出自动学习范围",
html: `当前小节是该课程第 ${order} 个视频:
${section.title}
` +
`本脚本每门课程只自动学习前 ${Utils.MAX_COURSES} 个视频,第 ${Utils.MAX_COURSES + 1} 个及之后不自动播放。
` +
`如需学习请手动播放,或打开该课程目录中的前 ${Utils.MAX_COURSES} 个视频。`,
icon: 'warning',
confirmButtonText: '知道了',
allowOutsideClick: false,
})
}
/**
* 需求3:前 N 个视频全部学完后的提醒弹窗(内容待定 → 修改 Utils.REMIND_TEXT 即可)
*/
showStudyReminder(courseId, learnedCount) {
console.log(`课程[${courseId}] 前 ${learnedCount} 个视频已全部自动学习完成,弹出提醒`)
const title = `本课程前 ${learnedCount} 个视频已学习完成`
Swal.fire({
title: title,
html: Utils.REMIND_TEXT,
icon: 'success',
confirmButtonText: '确定',
allowOutsideClick: false,
})
}
/**
* 自动连播(当前课程内):从当前课程的前 Utils.MAX_COURSES 个视频中,
* 找到第一个"未学完"的并跳转;若前 N 个已全部学完 → 弹提醒(需求3),不再连播。
*/
goNextCatalog(courseId) {
const { allowed } = this.getCurrentCourseCatalog(courseId)
if (!allowed.length) {
console.log("未找到当前课程目录")
return
}
// 若范围内前 N 个视频已全部学完:弹提醒并停止(需求3,不再跳转范围外小节)
const pending = allowed.find(s => !s.completed)
if (!pending) {
this.showStudyReminder(courseId, allowed.length)
return
}
console.log("跳转下一节:", pending.url)
Swal.fire({
title: "视频已播放完毕",
html: `即将跳转到本课程下一个视频:
${pending.title}`,
icon: 'success',
timer: 5000,
timerProgressBar: true,
confirmButtonText: '立即跳转',
showCancelButton: true,
cancelButtonText: '取消',
}).then((result) => {
// isConfirmed=点了确认,isDismissed+timer=倒计时结束,两种情况都跳转
if (result.isConfirmed || result.dismiss === Swal.DismissReason.timer) {
window.location.href = pending.url
}
// dismiss === 'cancel' 时用户主动取消,不跳转
})
}
async run() {
try {
// 解析当前学习页 URL:/home/User/study/course_id/{课程ID}/id/{小节ID}.html
const study = this.parseStudyUrl()
if (!study) {
// 非课程小节页(如首页/列表页)不处理,避免误判
console.log("当前页面不是课程小节学习页,脚本不启动自动连播")
return
}
const { courseId, sectionId } = study
// 获取当前课程的目录(含自动学习范围=前 MAX_COURSES 个小节)
const { items, allowed } = this.getCurrentCourseCatalog(courseId)
if (!items.length) {
console.log("未找到当前课程目录")
return
}
// 当前小节在该课程中的序号(从 1 开始)
const order = items.findIndex(i => i.sectionId === sectionId) + 1
// 需求4:本课程第 MAX_COURSES+1 个及之后的小节,一律不自动播放,仅提示
if (order > Utils.MAX_COURSES) {
const cur = items[order - 1]
console.log(`当前小节是课程第 ${order} 个视频,超出自动学习范围(前 ${Utils.MAX_COURSES} 个)`)
this.showOutOfRangeTip(cur, order)
return
}
// 本站播完一节后会标记"已学完"并刷新页面:
// 刷新后脚本重新运行,若当前节已是"已学完"→ 刚播完 → 跳转下一节 / 弹完成框
if (this.getCurrentSectionStatus() === 'completed') {
this.goNextCatalog(courseId)
return
}
// 等待站点播放器创建完成(篡改猴注入时机早于播放器渲染,需轮询等待)
await Utils.getStudyNode('video', 'node', 20000)
// 未学完 → 自动静音播放(播完后由站点刷新页面,脚本重新运行接管跳转)
await this.autoPlay()
} catch (e) {
console.error(e)
Swal.fire({
title: "失败!",
text: `视频基础播放失败!`,
icon: 'error',
confirmButtonColor: "#FF4DAFFF",
confirmButtonText: "确定",
timer: 5000,
timerProgressBar: true
})
}
}
sendMsg(msg) {
// 复用构造时创建的 BroadcastChannel 实例(修复:原代码误将实例当字符串传入构造器)
this.channel.postMessage(msg)
}
finish() {
if (!this.VIP) {
Swal.fire({
title: "请升级高级版!",
text: `脚本已停止!基础版只能连播几个视频!`,
icon: 'info',
confirmButtonColor: "#FF4DAFFF",
confirmButtonText: "确定",
timer: 0
})
return
}
this.sendMsg('finish')
Swal.fire({
title: "学习完成!",
text: `学习完成,5s后页面自动关闭!`,
icon: 'success',
confirmButtonColor: "#FF4DAFFF",
confirmButtonText: "确定",
timer: 5000,
willClose: () => {
history.back()
setTimeout(() => {
location.reload()
}, 1000)
}
})
}
}
class Utils {
constructor() {
}
// =========================================================
// 【必改配置】每个网站脚本只需修改这一块
// =========================================================
// 【自动学习范围】每门课程只自动学习【前 N 个小节(视频)】,
// 即:第 1→2→…→N 个视频按顺序自动连播;学满前 N 个视频后自动停止并弹出提醒;
// 第 N+1 个及之后的小节若被手动打开,脚本只会提示“不在自动学习范围”,不自动播放。
static MAX_COURSES = 3
// 【第 N 个视频学完后的提醒弹窗内容】(需求3:内容待定,只改这一句即可)
static REMIND_TEXT = '提醒内容待定:请在此处填写你想提醒的话(可修改 Utils.REMIND_TEXT)。例如:今天先学到这里,剩余小节请合理安排时间继续学习。'
// 网站唯一ID,用于跳转到对应文档页面
static webId = '0a1734236025f5cacc17b263'
// 脚本启动时弹窗提示文字(简短说明脚本怎么用)
static swFireText = "请在视频播放页面使用脚本,脚本检测到视频会自动开始,脚本功能有限,也可能页面有所更新,导致脚本不能正常使用,建议下载软件使用!全自动学习所有未完成视频"
// 控制面板顶部提示文字(基础版状态)
static baseText = '建议下载软件使用!全自动学习所有未完成视频'
// 控制面板顶部提示文字(VIP/已验证状态)
static vipText = '全自动建议下载客户端使用! 四川精英人才教育培训中心_四川专继-客户端'
// 【当前脚本】功能列表,显示在功能说明弹窗左侧
static scriptFeatures = [
"辅助当前页面视频播放",
"防暂停",
"自动静音播放",
"仅限单页面使用",
]
// 【客户端软件】功能列表,显示在功能说明弹窗右侧
static softwareFeatures = [
"输入账号密码即可全自动",
"全自动完成所有未完成课程",
"支持批量多账号同时学习",
]
// =========================================================
// 【下载/链接配置】一般不需要改
// =========================================================
// 阿里云盘下载链接
static aliLink = "https://www.alipan.com/s/wViqbLvgSF8"
// 直链下载地址(备用)
static directLink = 'https://zzzzzzys.lanzouu.com/b00zxor42h'
// 授权码购买链接
static link = [
"https://68n.cn/IJ8QB",
]
// 备用网址列表(官网打不开时使用,总有一个能用)
static web_list = [
{ name: "备用地址0", url: "https://www.zzzzzzys.com/" },
{ name: "备用地址1", url: "https://zzzzzzys.lovestoblog.com/" },
{ name: "备用地址2", url: "https://zzzzzzys.us.kg/" },
{ name: "备用地址3", url: "https://zzysdocs.dpdns.org/" },
{ name: "备用地址4", url: "https://zzzzzzys.dpdns.org/" },
{ name: "备用地址5", url: "https://zzzzzzys.kesug.com/" },
{ name: "备用地址6", url: "https://zzysdocs.great-site.net/" },
]
// 官网文档链接(会自动拼接 webId)
static docLink = `${Utils.web_list[0].url}?webId=` + Utils.webId
static loadStatus() {
// 当前版本固定为基础版
return false
}
static async validateCode(data) {
// 基础版不执行远程校验,直接展示升级引导(原实现 return 之后的代码为不可达死代码,已移除)
Utils.showUpgradeAlert()
return undefined
}
/**
* HTML 转义,防止课程标题等外部文本注入弹窗 HTML(显示效果不变)
*/
static escapeHtml(str) {
return String(str).replace(/[&<>"']/g, c => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[c]))
}
static getStudyNode(selector, type = 'node', timeout = 10000) {
return new Promise((resolve, reject) => {
if (!['node', 'nodeList'].includes(type)) {
console.error('Invalid type parameter. Expected "node" or "nodeList"');
reject('Invalid type parameter. Expected "node" or "nodeList"');
return
}
const cleanup = (timeoutId, intervalId) => {
clearTimeout(timeoutId);
clearInterval(intervalId);
};
const handleSuccess = (result, timeoutId, intervalId) => {
console.log(`${selector} ready!`);
cleanup(timeoutId, intervalId);
resolve(result);
};
const handleFailure = (timeoutId, intervalId) => {
cleanup(timeoutId, intervalId);
resolve(null);
};
const checkNode = () => {
try {
if (type === 'node') {
return document.querySelector(selector)
}
const nodes = document.querySelectorAll(selector);
return nodes.length > 0 ? nodes : null;
} catch (error) {
console.error('节点检查错误:', error);
reject('节点检查错误:', error)
}
};
const intervalId = setInterval(() => {
const result = checkNode();
if (result) {
handleSuccess(result, timeoutId, intervalId);
} else {
console.log(`等待节点: ${selector}...`);
}
}, 1000);
const timeoutId = setTimeout(() => {
console.error(`节点获取超时: ${selector}`);
handleFailure(timeoutId, intervalId);
}, timeout);
});
}
static showUpgradeAlert() {
return Swal.fire({
title: '全自动学习功能说明',
html: `
⚠️ 网页脚本有局限性
浏览器脚本运行在沙盒中,无法跨页面、无法自动登录、无法批量管理账号。
若需要 输入账号密码后全自动完成所有视频,推荐使用本地客户端。
📄 当前脚本