// ==UserScript==
// @name 华为人才在线课程助手 (Huawei Talent Helper) - Visual
// @namespace http://tampermonkey.net/
// @version 0.8
// @description 【可视化回归版】恢复 GUI 界面,支持倍速调节与日志下载,采用 Shadow DOM 隔离检测。
// @author Antigravity
// @match *://e.huawei.com/cn/talent/*
// @match *://talent.shixizhi.huawei.com/*
// @grant none
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const CONFIG = {
autoNext: true,
playbackSpeed: 1.0,
minDelay: 5000,
maxDelay: 15000
};
const logger = {
buffer: [],
log: function(msg, type = "INFO") {
const time = new Date().toLocaleTimeString();
const logMsg = `[${time}] [${type}] ${msg}`;
this.buffer.push(logMsg);
if (type === "SUCCESS") console.log(`%c${logMsg}`, "color: #28a745; font-weight: bold;");
else if (type === "WARN") console.warn(logMsg);
else console.log(logMsg);
if (this.buffer.length > 500) this.buffer.shift();
updateUIRunStatus(logMsg);
},
download: function() {
const blob = new Blob([this.buffer.join("\n")], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `huawei_logs_${Date.now()}.txt`;
a.click();
URL.revokeObjectURL(url);
}
};
// UI 相关逻辑 (采用 Shadow DOM 提高安全性)
let uiRoot = null;
function createUI() {
if (document.getElementById('huawei-helper-host')) return;
const host = document.createElement('div');
host.id = 'huawei-helper-host';
host.style = "position: fixed; top: 50px; right: 20px; z-index: 2147483647;";
document.body.appendChild(host);
uiRoot = host.attachShadow({ mode: 'closed' }); // 使用 closed 模式进一步隐藏
const panel = document.createElement('div');
panel.style = `
background: #fff;
border: 2px solid #ee0000;
padding: 12px;
border-radius: 10px;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
font-family: system-ui, -apple-system, sans-serif;
font-size: 13px;
width: 200px;
color: #333;
`;
panel.innerHTML = `
华为助手 v0.8
[_]
`;
uiRoot.appendChild(panel);
// 事件绑定
uiRoot.getElementById('auto-next-toggle').addEventListener('change', (e) => {
CONFIG.autoNext = e.target.checked;
logger.log("自动连播已" + (CONFIG.autoNext ? "开启" : "关闭"));
});
uiRoot.getElementById('speed-input').addEventListener('input', (e) => {
CONFIG.playbackSpeed = parseFloat(e.target.value) || 1.0;
logger.log(`期望倍速已设为: ${CONFIG.playbackSpeed}x`);
});
uiRoot.getElementById('dl-log-btn').addEventListener('click', () => logger.download());
uiRoot.getElementById('minimize-btn').addEventListener('click', function() {
const ctrl = uiRoot.getElementById('controls');
if (ctrl.style.display === 'none') {
ctrl.style.display = 'block';
this.innerText = '[_]';
} else {
ctrl.style.display = 'none';
this.innerText = '[+]';
}
});
}
function updateUIRunStatus(msg) {
if (!uiRoot) return;
const el = uiRoot.getElementById('ui-current-status');
if (el) el.innerText = msg.replace(/\[.*?\] /g, "");
}
// 快捷键
window.addEventListener('keydown', (e) => {
if (e.altKey && e.shiftKey && e.code === 'KeyL') {
e.preventDefault();
logger.download();
}
});
function runLoop() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (video.playbackRate !== CONFIG.playbackSpeed) {
video.playbackRate = CONFIG.playbackSpeed;
}
if (video.paused && !video.ended && video.readyState >= 2) {
video.play().catch(() => {});
}
if (video.ended && CONFIG.autoNext && !video.dataset.v8Jumped) {
video.dataset.v8Jumped = "true";
const delay = Math.floor(Math.random() * (CONFIG.maxDelay - CONFIG.minDelay) + CONFIG.minDelay);
logger.log(`视频完毕,${Math.round(delay/1000)}s 后跳转...`, "SUCCESS");
setTimeout(doJump, delay);
}
});
setTimeout(runLoop, 3000);
}
function doJump() {
const sel = ['.course-next-item', '.kltCourse-btn-next', '.vjs-next-button', '.course-side-catalog-item.active + li'];
let ok = false;
for (let s of sel) {
let b = document.querySelector(s);
if (b && b.offsetParent) { b.click(); ok = true; break; }
}
if (!ok) {
for (let el of document.querySelectorAll('span, button, div')) {
if (el.innerText.includes('下一讲') && el.offsetParent) { el.click(); ok = true; break; }
}
}
if (ok) logger.log("跳转成功", "SUCCESS");
}
// 初始化
setInterval(createUI, 3000);
setTimeout(runLoop, 5000);
})();