喵课助手 | 网课小工具
// ==UserScript==
// @name 喵课助手 | 网课小工具
// @namespace http://nb.zizizi.top/
// @version 1.1.3
// @description 为各大网课平台提供全自动学习辅助功能,包括视频加速、自动播放、笔记工具等,让您轻松应对各类网络课程
// @author 喵课团队
// @match *://*.edu.cn/*
// @match *://*.chaoxing.com/*
// @match *://*.zhihuishu.com/*
// @match *://*.icve.com.cn/*
// @match *://*.cnmooc.org/*
// @match *://*.xuetangx.com/*
// @match *://*.icourse163.org/*
// @match *://*.yuketang.cn/*
// @match *://*.mooc.cn/*
// @match *://study.163.com/*
// @match *://www.bilibili.com/video/*
// @match *://v.qq.com/*
// @icon http://nb.zizizi.top/miaoke.ico
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 工具类
class MiaoKeHelper {
constructor() {
// 单例模式:检查是否已有实例
if (window.miaokeHelperInstance) {
console.log('喵课助手已经实例化,返回现有实例');
return window.miaokeHelperInstance;
}
// 检查全局是否有面板
if (document.getElementById('miaoke-panel')) {
console.log('检测到页面已有喵课助手面板,移除旧面板');
// 尝试移除可能存在的旧的事件监听器
try {
const oldPanel = document.getElementById('miaoke-panel');
oldPanel.parentNode.removeChild(oldPanel);
} catch (e) {
console.error('移除旧面板失败', e);
}
}
// 存储实例到全局
window.miaokeHelperInstance = this;
this.version = '1.0';
this.siteName = this.detectSite();
this.playAttempts = {}; // 记录播放尝试次数
this.lastPlayTime = 0; // 最后一次尝试播放的时间
this.isUserPaused = false; // 用户是否主动暂停
// 显示欢迎提示,增强推广效果
this.showFirstTimeWelcome = !GM_getValue('miaokeHelper_welcomed', false);
this.init();
// 首次使用显示欢迎提示
if (this.showFirstTimeWelcome) {
setTimeout(() => {
this.showWelcomeNotification();
GM_setValue('miaokeHelper_welcomed', true);
}, 1500);
}
// 检查更新版本后显示新功能提示
const lastVersion = GM_getValue('miaokeHelper_version', '');
if (lastVersion !== this.version) {
setTimeout(() => {
this.showUpdateNotification(lastVersion);
GM_setValue('miaokeHelper_version', this.version);
}, 2000);
}
// 设置周期性提醒
this.setupPeriodicReminder();
// 添加存储到localStorage,记录已初始化
try {
localStorage.setItem('miaokeHelper_initialized', 'true');
localStorage.setItem('miaokeHelper_timestamp', Date.now().toString());
} catch (e) {
console.log('无法写入localStorage', e);
}
}
// 检测当前网站
detectSite() {
const host = window.location.hostname;
if (host.includes('chaoxing.com')) return '超星学习通';
if (host.includes('zhihuishu.com')) return '智慧树';
if (host.includes('icve.com.cn')) return '智慧职教';
if (host.includes('xuetangx.com')) return '学堂在线';
if (host.includes('icourse163.org')) return '中国大学MOOC';
if (host.includes('bilibili.com')) return 'B站视频';
if (host.includes('v.qq.com')) return '腾讯视频';
return '教育平台';
}
// 初始化
init() {
this.addStyles();
this.createHelperButton();
this.createPanel();
this.setupEvents();
this.listenToHistoryChanges();
console.log(`喵课助手已启动 - ${this.siteName}`);
}
// 添加样式
addStyles() {
const style = document.createElement('style');
style.textContent = `
/* 主容器样式 */
#miaoke-helper-btn {
position: fixed;
z-index: 9999;
right: 20px;
top: 100px;
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
text-align: center;
line-height: 60px;
font-size: 28px;
cursor: pointer;
box-shadow: 0 4px 20px rgba(106, 90, 249, 0.4);
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
user-select: none;
border: 2px solid rgba(255, 255, 255, 0.3);
animation: pulseMiaoke 2s infinite;
}
@keyframes pulseMiaoke {
0% { transform: scale(1); box-shadow: 0 4px 20px rgba(106, 90, 249, 0.4); }
50% { transform: scale(1.1); box-shadow: 0 4px 25px rgba(106, 90, 249, 0.7); }
100% { transform: scale(1); box-shadow: 0 4px 20px rgba(106, 90, 249, 0.4); }
}
#miaoke-helper-btn:hover {
transform: scale(1.1) rotate(5deg);
box-shadow: 0 6px 25px rgba(106, 90, 249, 0.6);
animation: none;
}
/* 通知样式 */
.miaoke-notification {
position: fixed;
bottom: 30px;
right: 30px;
width: 320px;
background: white;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
z-index: 10000;
overflow: hidden;
animation: slideInRight 0.5s forwards;
border: 1px solid rgba(106, 90, 249, 0.2);
font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
}
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOutRight {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
.notification-header {
padding: 15px 20px;
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
font-weight: bold;
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-content {
padding: 18px 20px;
line-height: 1.5;
color: #333;
}
.notification-footer {
padding: 12px 20px;
display: flex;
justify-content: flex-end;
background: #f7f7f7;
border-top: 1px solid #eee;
}
.notification-close {
cursor: pointer;
padding: 5px 15px;
background: #6a5af9;
color: white;
border-radius: 5px;
font-size: 14px;
transition: all 0.2s;
}
.notification-close:hover {
background: #d66efd;
}
.feature-highlight {
display: flex;
align-items: center;
margin: 10px 0;
padding: 8px 12px;
background: rgba(106, 90, 249, 0.1);
border-radius: 6px;
}
.feature-icon {
font-size: 18px;
margin-right: 10px;
}
.share-button {
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
margin-top: 10px;
width: 100%;
transition: all 0.2s;
}
.share-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(106, 90, 249, 0.3);
}
/* 主面板样式 */
#miaoke-helper-panel {
position: fixed;
z-index: 9998;
right: 20px;
top: 100px;
width: 340px;
background: rgba(255, 255, 255, 0.95);
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
font-family: 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
display: none;
overflow: hidden;
backdrop-filter: blur(10px);
border: 1px solid rgba(106, 90, 249, 0.2);
transform-origin: top right;
}
#miaoke-helper-panel.active {
display: block;
animation: panelFadeIn 0.4s forwards;
}
@keyframes panelFadeIn {
from {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* 面板头部 */
.helper-header {
padding: 18px 20px;
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.helper-title {
margin: 0;
font-size: 18px;
font-weight: 600;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 8px;
}
.helper-title:before {
content: '🐱';
display: inline-block;
font-size: 22px;
}
.helper-close {
cursor: pointer;
font-size: 22px;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
background: rgba(255, 255, 255, 0.2);
}
.helper-close:hover {
background: rgba(255, 255, 255, 0.3);
transform: rotate(90deg);
}
/* 功能区 */
.helper-content {
padding: 20px;
max-height: 450px;
overflow-y: auto;
scrollbar-width: thin;
}
.helper-content::-webkit-scrollbar {
width: 6px;
}
.helper-content::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 3px;
}
.helper-content::-webkit-scrollbar-thumb {
background: rgba(106, 90, 249, 0.3);
border-radius: 3px;
}
.helper-section {
margin-bottom: 24px;
padding-bottom: 8px;
position: relative;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 14px;
color: #333;
border-bottom: 2px solid #eee;
padding-bottom: 8px;
position: relative;
}
.section-title:after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 50px;
height: 2px;
background: linear-gradient(90deg, #6a5af9, #d66efd);
}
.feature-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 10px;
}
.feature-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 16px;
background: #f5f5f5;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
color: #444;
transition: all 0.3s;
border: 1px solid #e0e0e0;
min-width: 90px;
}
.feature-btn:before {
font-size: 16px;
}
#auto-play:before { content: '▶️'; }
#reading-mode:before { content: '📖'; }
#take-notes:before { content: '📝'; }
#speed-control:before { content: '⏱️'; }
#auto-next:before { content: '⏭️'; }
.feature-btn:hover {
background: #EEEAFF;
border-color: #d1caff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(106, 90, 249, 0.1);
}
.feature-btn.active {
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
border-color: transparent;
box-shadow: 0 4px 15px rgba(106, 90, 249, 0.3);
}
/* 设置区域 */
.helper-setting {
margin-bottom: 14px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background: #f8f8f8;
border-radius: 10px;
transition: all 0.3s;
border: 1px solid #eee;
}
.helper-setting:hover {
background: #f0f0f0;
border-color: #ddd;
}
.setting-label {
font-size: 14px;
color: #444;
font-weight: 500;
}
.setting-input {
width: 70px;
text-align: center;
border: 1px solid #ddd;
border-radius: 6px;
padding: 6px 8px;
font-size: 14px;
transition: all 0.3s;
background: white;
}
.setting-input:focus {
outline: none;
border-color: #6a5af9;
box-shadow: 0 0 0 3px rgba(106, 90, 249, 0.1);
}
/* 底部 */
.helper-footer {
padding: 12px 15px;
background: #f5f5f5;
text-align: center;
font-size: 12px;
color: #666;
border-top: 1px solid #eee;
}
.helper-footer a {
color: #6a5af9;
text-decoration: none;
font-weight: 500;
transition: all 0.2s;
}
.helper-footer a:hover {
color: #d66efd;
text-decoration: underline;
}
/* 笔记面板 */
#note-panel {
position: fixed;
right: 20px;
bottom: 20px;
width: 340px;
height: 300px;
background: white;
border-radius: 16px;
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
z-index: 9997;
display: none;
overflow: hidden;
border: 1px solid rgba(106, 90, 249, 0.2);
transition: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
}
#note-panel.active {
display: block;
animation: notePanelFadeIn 0.4s forwards;
}
@keyframes notePanelFadeIn {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.note-header {
padding: 15px;
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
font-weight: 600;
}
.note-content {
padding: 15px;
height: calc(100% - 110px);
}
.note-textarea {
width: 100%;
height: 100%;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 10px;
resize: none;
font-size: 14px;
line-height: 1.5;
transition: all 0.3s;
}
.note-textarea:focus {
outline: none;
border-color: #6a5af9;
box-shadow: 0 0 0 3px rgba(106, 90, 249, 0.1);
}
.note-footer {
padding: 10px 15px;
display: flex;
justify-content: flex-end;
background: #f5f5f5;
}
.note-save {
padding: 8px 16px;
background: linear-gradient(135deg, #6a5af9, #d66efd);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
box-shadow: 0 4px 10px rgba(106, 90, 249, 0.2);
}
.note-save:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(106, 90, 249, 0.3);
}
/* 阅读模式 */
.reading-mode-active {
background-color: #f8f9fa !important;
color: #333 !important;
font-size: 18px !important;
line-height: 1.7 !important;
letter-spacing: 0.3px !important;
}
.reading-mode-active p, .reading-mode-active div {
max-width: 900px !important;
margin: 0 auto !important;
padding: 15px 30px !important;
}
/* 拖动功能 */
.draggable {
cursor: move;
}
/* 状态提示 */
.status-tip {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 8px;
z-index: 10000;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.status-tip.show {
opacity: 1;
}
/* 按钮动效 */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.feature-btn.active:before {
animation: pulse 2s infinite;
}
/* 进度条 */
.progress-container {
width: 100%;
height: 6px;
background: #f0f0f0;
border-radius: 3px;
overflow: hidden;
margin-top: 5px;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #6a5af9, #d66efd);
width: 0;
transition: width 0.3s;
}
`;
document.head.appendChild(style);
}
// 创建助手按钮
createHelperButton() {
const btn = document.createElement('div');
btn.id = 'miaoke-helper-btn';
btn.innerHTML = '🐱';
btn.title = '点击打开喵课助手 - 学习更轻松';
document.body.appendChild(btn);
// 添加点击事件
btn.addEventListener('click', () => {
this.togglePanel();
});
// 创建提示标签
const tooltip = document.createElement('div');
tooltip.id = 'miaoke-tooltip';
tooltip.textContent = '点击打开喵课助手';
tooltip.style.position = 'fixed';
tooltip.style.right = '90px';
tooltip.style.top = '120px';
tooltip.style.background = 'white';
tooltip.style.color = '#333';
tooltip.style.padding = '8px 12px';
tooltip.style.borderRadius = '6px';
tooltip.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
tooltip.style.fontSize = '14px';
tooltip.style.zIndex = '9998';
tooltip.style.opacity = '0';
tooltip.style.transition = 'opacity 0.3s, right 0.3s';
tooltip.style.pointerEvents = 'none';
document.body.appendChild(tooltip);
// 显示提示标签
btn.addEventListener('mouseenter', () => {
tooltip.style.opacity = '1';
tooltip.style.right = '95px';
});
// 隐藏提示标签
btn.addEventListener('mouseleave', () => {
tooltip.style.opacity = '0';
tooltip.style.right = '90px';
});
// 首次加载2秒后短暂显示提示
setTimeout(() => {
tooltip.style.opacity = '1';
tooltip.style.right = '95px';
setTimeout(() => {
tooltip.style.opacity = '0';
tooltip.style.right = '90px';
}, 3000);
}, 2000);
}
// 创建主面板
createPanel() {
// 主面板
const panel = document.createElement('div');
panel.id = 'miaoke-helper-panel';
panel.innerHTML = `
<div class="helper-header draggable">
<h3 class="helper-title">喵课助手 - ${this.siteName}</h3>
<span class="helper-close">×</span>
</div>
<div class="helper-content">
<div class="helper-section">
<div class="section-title">学习辅助功能</div>
<div class="feature-container">
<div class="feature-btn" id="auto-play">自动播放</div>
<div class="feature-btn" id="reading-mode">阅读模式</div>
<div class="feature-btn" id="take-notes">笔记工具</div>
<div class="feature-btn" id="speed-control">速度调节</div>
<div class="feature-btn" id="auto-next">自动下一章</div>
</div>
</div>
<div class="helper-section" id="speed-settings" style="display:none;">
<div class="section-title">速度设置</div>
<div class="helper-setting">
<span class="setting-label">播放速度:</span>
<input type="number" class="setting-input" id="play-speed" min="0.5" max="16" step="0.5" value="1.5">
</div>
<div class="feature-container">
<div class="feature-btn" id="apply-speed">应用速度</div>
</div>
<div class="progress-container">
<div class="progress-bar" id="speed-progress"></div>
</div>
</div>
<div class="helper-section">
<div class="section-title">视频状态</div>
<div class="helper-setting">
<span class="setting-label">当前状态:</span>
<span id="video-status">未检测到视频</span>
</div>
<div class="helper-setting">
<span class="setting-label">自动播放:</span>
<span id="autoplay-status">未启用</span>
</div>
<div class="progress-container">
<div class="progress-bar" id="video-progress"></div>
</div>
</div>
<div class="helper-section">
<div class="section-title">喵课资源推荐</div>
<div class="helper-setting" style="margin-bottom:10px;">
<span class="setting-label" style="font-weight:600;color:#6a5af9;">邀请码: 0000</span>
<span style="padding:4px 10px;background:linear-gradient(135deg, #6a5af9, #d66efd);color:white;border-radius:6px;font-size:12px;font-weight:500;">必填</span>
</div>
<div class="helper-setting">
<span class="setting-label">网课自动化解决方案:</span>
<a href="http://nb.zizizi.top/js" target="_blank" style="color:#6a5af9; text-decoration:none; font-weight:500;">访问</a>
</div>
<div class="helper-setting">
<span class="setting-label">更多学习工具:</span>
<a href="http://nb.zizizi.top/index" target="_blank" style="color:#6a5af9; text-decoration:none; font-weight:500;">查看</a>
</div>
</div>
</div>
<div class="helper-footer">
由 <a href="http://nb.zizizi.top" target="_blank">喵课在线学习平台</a> 提供支持 | 邀请码: 0000
</div>
`;
document.body.appendChild(panel);
// 笔记面板
const notePanel = document.createElement('div');
notePanel.id = 'note-panel';
notePanel.innerHTML = `
<div class="note-header draggable">
<span>学习笔记</span>
<span class="helper-close">×</span>
</div>
<div class="note-content">
<textarea class="note-textarea" placeholder="在这里记录你的学习笔记..."></textarea>
</div>
<div class="note-footer">
<button class="note-save">保存笔记</button>
</div>
`;
document.body.appendChild(notePanel);
// 状态提示
const statusTip = document.createElement('div');
statusTip.className = 'status-tip';
statusTip.id = 'status-tip';
document.body.appendChild(statusTip);
}
// 监听历史记录变化,用于单页应用
listenToHistoryChanges() {
// 监听 history.pushState 和 replaceState
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function() {
originalPushState.apply(this, arguments);
// 触发自定义事件
window.dispatchEvent(new Event('locationchange'));
};
history.replaceState = function() {
originalReplaceState.apply(this, arguments);
// 触发自定义事件
window.dispatchEvent(new Event('locationchange'));
};
// 监听 popstate
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'));
});
// 在URL变化时检查视频
window.addEventListener('locationchange', () => {
console.log('检测到页面导航,重新检查视频');
// 更新站点名称
this.siteName = this.detectSite();
// 短暂延迟后检查视频
setTimeout(() => {
this.checkForVideo();
}, 1000);
// 确保只有一个面板
this.ensureOnlyOnePanel();
});
}
// 确保页面上只有一个面板
ensureOnlyOnePanel() {
const panels = document.querySelectorAll('#miaoke-panel');
if (panels.length > 1) {
console.log(`发现${panels.length}个面板,移除多余的`);
// 保留第一个面板,移除其他面板
for (let i = 1; i < panels.length; i++) {
panels[i].parentNode.removeChild(panels[i]);
}
}
}
// 检查视频并自动启用功能
checkForVideo() {
const videos = document.querySelectorAll('video');
if (videos.length > 0) {
console.log(`检测到${videos.length}个视频元素`);
// 自动开启视频功能
if (document.getElementById('auto-play').classList.contains('active')) {
this.autoPlayVideos();
}
}
}
// 显示状态提示
showStatusTip(message, duration = 2000) {
const tip = document.getElementById('status-tip');
tip.textContent = message;
tip.classList.add('show');
setTimeout(() => {
tip.classList.remove('show');
}, duration);
}
// 显示欢迎提示
showWelcomeNotification() {
const notification = document.createElement('div');
notification.className = 'miaoke-notification';
notification.innerHTML = `
<div class="notification-header">
<span>🌟 喵课助手欢迎您 🌟</span>
<span class="notification-close">×</span>
</div>
<div class="notification-content">
<h3 style="color:#6a5af9;margin-top:0;text-align:center;">网课学习神器已准备就绪!</h3>
<p style="text-align:center;margin-bottom:15px;">全自动学习辅助工具,让您轻松应对各类网络课程</p>
<div class="feature-highlight">
<span class="feature-icon">📚</span>
<span><b>自动播放视频</b> - 无需手动点击下一节课程</span>
</div>
<div class="feature-highlight">
<span class="feature-icon">⏱️</span>
<span><b>视频加速</b> - 自由调节播放速度,节省学习时间</span>
</div>
<div class="feature-highlight">
<span class="feature-icon">📝</span>
<span><b>笔记工具</b> - 轻松记录学习要点,提高效率</span>
</div>
<button class="share-button" id="miaoke-welcome-share">分享给同学</button>
</div>
<div class="notification-footer">
<a href="http://nb.zizizi.top" target="_blank" style="color:#6a5af9;text-decoration:none;margin-right:auto;">了解更多</a>
<div class="notification-close" style="padding:5px 15px;background:#6a5af9;color:white;border-radius:5px;cursor:pointer;">我知道了</div>
</div>
`;
document.body.appendChild(notification);
// 关闭按钮事件
const closeButtons = notification.querySelectorAll('.notification-close');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
notification.style.animation = 'slideOutRight 0.5s forwards';
setTimeout(() => {
notification.remove();
}, 500);
});
});
// 分享按钮事件
const shareButton = notification.querySelector('#miaoke-welcome-share');
if (shareButton) {
shareButton.addEventListener('click', () => {
const shareText = '推荐一个超好用的网课神器「喵课助手」,可以视频加速、自动播放、一键笔记,一站式解决网课难题!安装地址:http://nb.zizizi.top';
const tempInput = document.createElement('textarea');
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
tempInput.value = shareText;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
this.showTip('分享文本已复制到剪贴板,快去发给同学吧!', 3000);
} catch (err) {
this.showTip('复制失败,请手动复制分享文本', 3000);
}
document.body.removeChild(tempInput);
});
}
// 自动关闭计时器 - 延长时间确保用户看到
setTimeout(() => {
if (document.body.contains(notification)) {
notification.style.animation = 'slideOutRight 0.5s forwards';
setTimeout(() => {
if (document.body.contains(notification)) {
notification.remove();
}
}, 500);
}
}, 20000);
}
// 分享喵课助手
shareHelper() {
const url = 'http://nb.zizizi.top';
const title = '喵课助手 - 网课自动化学习工具';
const text = '🐱 喵课助手:自动播放、视频加速、笔记工具等功能,帮助您更好地学习!';
// 分享到微信
const wechatShare = document.createElement('div');
wechatShare.innerHTML = `
<img src="https://nb.zizizi.top/images/wechat-share.png" width="200" height="200" style="margin: 20px auto; display: block;">
<p style="text-align: center; font-size: 16px;">打开微信,扫描二维码分享给朋友</p>
`;
document.body.appendChild(wechatShare);
// 分享到QQ
const qqShare = document.createElement('div');
qqShare.innerHTML = `
<img src="https://nb.zizizi.top/images/qq-share.png" width="200" height="200" style="margin: 20px auto; display: block;">
<p style="text-align: center; font-size: 16px;">打开QQ,扫描二维码分享给朋友</p>
`;
document.body.appendChild(qqShare);
// 分享到微博
const weiboShare = document.createElement('div');
weiboShare.innerHTML = `
<img src="https://nb.zizizi.top/images/weibo-share.png" width="200" height="200" style="margin: 20px auto; display: block;">
<p style="text-align: center; font-size: 16px;">打开微博,扫描二维码分享给朋友</p>
`;
document.body.appendChild(weiboShare);
// 分享到其他平台
const otherShare = document.createElement('div');
otherShare.innerHTML = `
<p style="text-align: center; font-size: 16px;">复制链接,分享给朋友</p>
<input type="text" value="${url}" style="width: 100%; padding: 10px; font-size: 16px; border: 1px solid #ddd; border-radius: 6px;">
`;
document.body.appendChild(otherShare);
}
// 显示更新提示
showUpdateNotification(lastVersion) {
const notification = document.createElement('div');
notification.className = 'miaoke-notification';
notification.innerHTML = `
<div class="notification-header">
<span>🚀 喵课助手全新升级 🚀</span>
<span class="notification-close">×</span>
</div>
<div class="notification-content">
<h3 style="color:#6a5af9;margin-top:0;text-align:center;">您的喵课助手已升级至 v${this.version}</h3>
<p style="text-align:center;margin-bottom:15px;">我们带来了更多强大功能,让您的学习更高效!</p>
<div class="feature-highlight">
<span class="feature-icon">⚡</span>
<span><b>性能优化</b> - 运行更快更稳定,支持更多课程平台</span>
</div>
<div class="feature-highlight">
<span class="feature-icon">🔍</span>
<span><b>智能识别</b> - 自动适配不同平台的视频控件</span>
</div>
<div class="feature-highlight">
<span class="feature-icon">🛠️</span>
<span><b>界面升级</b> - 全新UI设计,操作更简便</span>
</div>
<button class="share-button" id="miaoke-update-share">推荐给同学</button>
</div>
<div class="notification-footer">
<a href="http://nb.zizizi.top" target="_blank" style="color:#6a5af9;text-decoration:none;margin-right:auto;">查看更新详情</a>
<div class="notification-close" style="padding:5px 15px;background:#6a5af9;color:white;border-radius:5px;cursor:pointer;">知道了</div>
</div>
`;
document.body.appendChild(notification);
// 关闭按钮事件
const closeButtons = notification.querySelectorAll('.notification-close');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
notification.style.animation = 'slideOutRight 0.5s forwards';
setTimeout(() => {
notification.remove();
}, 500);
});
});
// 分享按钮事件
const shareButton = notification.querySelector('#miaoke-update-share');
if (shareButton) {
shareButton.addEventListener('click', () => {
const shareText = '喵课助手更新啦!现在支持更多平台,运行更稳定,UI更美观。这是我用过最好的网课学习工具,强烈推荐! http://nb.zizizi.top';
const tempInput = document.createElement('textarea');
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
tempInput.value = shareText;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
this.showTip('更新分享文本已复制,快去告诉同学吧!', 3000);
} catch (err) {
this.showTip('复制失败,请手动复制分享文本', 3000);
}
document.body.removeChild(tempInput);
});
}
// 自动关闭计时器
setTimeout(() => {
if (document.body.contains(notification)) {
notification.style.animation = 'slideOutRight 0.5s forwards';
setTimeout(() => {
if (document.body.contains(notification)) {
notification.remove();
}
}, 500);
}
}, 20000);
}
// 初始化功能
initFeatures() {
// 获取保存的笔记
const savedNote = GM_getValue('miaoke_helper_note_' + window.location.href, '');
if (savedNote) {
document.querySelector('.note-textarea').value = savedNote;
}
// 获取保存的设置
const savedSpeed = GM_getValue('miaoke_helper_speed', 1.5);
document.getElementById('play-speed').value = savedSpeed;
// 获取自动播放设置
const autoPlayEnabled = GM_getValue('miaoke_helper_autoplay', true);
if (autoPlayEnabled) {
document.getElementById('auto-play').classList.add('active');
this.enableAutoPlay();
}
}
// 绑定事件
setupEvents() {
const self = this;
// 主按钮点击
document.getElementById('miaoke-helper-btn').addEventListener('click', function() {
const panel = document.getElementById('miaoke-helper-panel');
panel.classList.toggle('active');
});
// 关闭按钮
document.querySelectorAll('.helper-close').forEach(function(el) {
el.addEventListener('click', function() {
this.closest('#miaoke-helper-panel, #note-panel').classList.remove('active');
});
});
// 自动播放
document.getElementById('auto-play').addEventListener('click', function() {
this.classList.toggle('active');
if (this.classList.contains('active')) {
self.enableAutoPlay();
GM_setValue('miaoke_helper_autoplay', true);
self.showStatusTip('已开启自动播放功能', 1500);
} else {
self.disableAutoPlay();
GM_setValue('miaoke_helper_autoplay', false);
self.showStatusTip('已关闭自动播放功能', 1500);
}
});
// 阅读模式
document.getElementById('reading-mode').addEventListener('click', function() {
this.classList.toggle('active');
document.body.classList.toggle('reading-mode-active');
});
// 笔记工具
document.getElementById('take-notes').addEventListener('click', function() {
document.getElementById('note-panel').classList.toggle('active');
});
// 保存笔记
document.querySelector('.note-save').addEventListener('click', function() {
const noteContent = document.querySelector('.note-textarea').value;
GM_setValue('miaoke_helper_note_' + window.location.href, noteContent);
self.showStatusTip('笔记已保存!', 1500);
});
// 速度调节
document.getElementById('speed-control').addEventListener('click', function() {
this.classList.toggle('active');
const speedSettings = document.getElementById('speed-settings');
speedSettings.style.display = speedSettings.style.display === 'none' ? 'block' : 'none';
});
// 应用速度
document.getElementById('apply-speed').addEventListener('click', function() {
const speedValue = parseFloat(document.getElementById('play-speed').value);
GM_setValue('miaoke_helper_speed', speedValue);
self.applyVideoSpeed(speedValue);
self.showStatusTip(`已将视频速度设为 ${speedValue}x`, 1500);
});
// 自动下一章
document.getElementById('auto-next').addEventListener('click', function() {
this.classList.toggle('active');
if (this.classList.contains('active')) {
self.enableAutoNext();
self.showStatusTip('已开启自动下一章功能', 1500);
} else {
self.disableAutoNext();
self.showStatusTip('已关闭自动下一章功能', 1500);
}
});
// 拖动功能
this.enableDrag(document.querySelectorAll('.draggable'));
}
// 启用自动播放
enableAutoPlay() {
// 立即尝试播放当前视频
this.autoPlayVideos();
// 更新自动播放状态
document.getElementById('autoplay-status').textContent = '已启用';
document.getElementById('autoplay-status').style.color = '#6a5af9';
// 监听 DOM 变化以捕捉新加载的视频
if (!this.mutationObserver) {
this.mutationObserver = new MutationObserver((mutations) => {
// 使用防抖,避免频繁调用
if (Date.now() - this.lastPlayTime > 1000) {
this.autoPlayVideos();
this.lastPlayTime = Date.now();
}
});
this.mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
}
// 定时检查是否有需要播放的视频,降低检查频率
if (!this.autoPlayInterval) {
this.autoPlayInterval = setInterval(() => {
this.updateVideoStatus();
// 只在需要时才处理自动播放和弹窗
if (this.shouldTryAutoplay()) {
this.autoPlayVideos();
this.handlePopupDialogs();
}
}, 2000); // 增加间隔到2秒,减少干扰
}
}
// 判断是否应该尝试自动播放
shouldTryAutoplay() {
// 获取所有视频元素
const videos = document.querySelectorAll('video');
if (videos.length === 0) return false;
// 如果用户主动暂停,则不应该自动播放
if (this.isUserPaused) return false;
// 检查是否有任何视频需要播放
let needsPlayback = false;
videos.forEach(video => {
// 只有当视频暂停且不是已结束状态时才需要播放
if (video.paused && !video.ended) {
// 检查是否正在缓冲
if (video.readyState < 3) {
// 正在缓冲,等待加载更多数据
return;
}
// 检查尝试次数,避免无限尝试
const videoId = video.src || video.currentSrc || video.id || 'unknown';
if (!this.playAttempts[videoId]) {
this.playAttempts[videoId] = 0;
}
// 如果尝试次数过多,间隔至少30秒再试
if (this.playAttempts[videoId] > 5) {
const lastAttempt = video.getAttribute('last-attempt-time') || 0;
if (Date.now() - lastAttempt < 30000) {
return;
}
// 重置尝试次数
this.playAttempts[videoId] = 0;
}
needsPlayback = true;
}
});
return needsPlayback;
}
// 实际处理自动播放的函数
autoPlayVideos() {
// 获取所有视频元素
const videos = document.querySelectorAll('video');
if (videos.length > 0) {
// 更新视频状态
document.getElementById('video-status').textContent = '已检测到视频';
document.getElementById('video-status').style.color = '#6a5af9';
// 播放每个视频
videos.forEach(video => {
// 为视频添加事件监听器(如果尚未添加)
if (!video.hasAttribute('miaoke-processed')) {
// 添加播放错误处理
video.addEventListener('error', () => {
console.log('视频播放出错');
this.showStatusTip('视频播放出错,尝试恢复...', 2000);
setTimeout(() => this.clickPlayButton(), 500);
});
// 添加进度更新
video.addEventListener('timeupdate', () => {
this.updateVideoProgress(video);
});
// 添加视频结束处理
video.addEventListener('ended', () => {
if (document.getElementById('auto-next').classList.contains('active')) {
setTimeout(() => this.findAndClickNextButton(), 1000);
}
});
// 添加用户交互检测
video.addEventListener('pause', () => {
// 检测如果是用户手动暂停的
if (document.activeElement === video || Date.now() - this.lastPlayTime > 1000) {
this.isUserPaused = true;
console.log('检测到用户手动暂停');
// 五秒后重置用户暂停状态,允许系统再次尝试自动播放
setTimeout(() => {
this.isUserPaused = false;
}, 5000);
}
});
// 添加播放事件处理
video.addEventListener('play', () => {
// 重置播放尝试次数
const videoId = video.src || video.currentSrc || video.id || 'unknown';
this.playAttempts[videoId] = 0;
// 更新播放状态
this.isUserPaused = false;
});
video.setAttribute('miaoke-processed', 'true');
}
// 如果视频暂停且不是用户主动暂停
if (video.paused && !video.ended && !this.isUserPaused) {
// 获取视频ID
const videoId = video.src || video.currentSrc || video.id || 'unknown';
// 记录尝试时间
video.setAttribute('last-attempt-time', Date.now());
this.lastPlayTime = Date.now();
// 增加尝试次数
if (!this.playAttempts[videoId]) {
this.playAttempts[videoId] = 0;
}
this.playAttempts[videoId]++;
// 检查视频是否已缓冲足够数据
if (video.readyState >= 3) {
// 先尝试点击播放按钮
const buttonClicked = this.clickPlayButton();
// 如果没有点击成功或者是第一次尝试,直接调用play()
if (!buttonClicked || this.playAttempts[videoId] <= 1) {
// 尝试直接播放视频元素
const playPromise = video.play();
// 处理可能的错误
if (playPromise !== undefined) {
playPromise.catch(error => {
console.log('自动播放失败,尝试备用方法:', error.message);
// 备用方法:模拟用户交互后再尝试播放
if (this.playAttempts[videoId] < 3) {
setTimeout(() => {
// 短暂聚焦视频元素
video.focus();
// 模拟点击视频
video.click();
// 再次尝试播放
video.play().catch(e => {
console.log('第二次播放尝试也失败:', e.message);
// 最后的方法:尝试特定站点的解决方案
if (this.playAttempts[videoId] < 5) {
this.trySiteSpecificAutoplay();
}
});
}, 500);
}
});
}
}
} else {
console.log('视频未完全加载,等待缓冲...');
}
}
// 应用当前设置的速度,但避免频繁设置
const currentTime = Date.now();
if (!video.lastSpeedApplied || currentTime - video.lastSpeedApplied > 5000) {
const currentSpeed = parseFloat(document.getElementById('play-speed').value);
if (video.playbackRate !== currentSpeed) {
video.playbackRate = currentSpeed;
video.lastSpeedApplied = currentTime;
}
}
});
} else {
// 没有视频的情况
document.getElementById('video-status').textContent = '未检测到视频';
document.getElementById('video-status').style.color = '#888';
// 重置进度条
const progressBar = document.getElementById('video-progress');
progressBar.style.width = '0%';
}
}
// 尝试特定网站的自动播放解决方案
trySiteSpecificAutoplay() {
let success = false;
switch(this.siteName) {
case '超星学习通':
// 超星特定方法
success = this.chaoxingSiteSpecific();
break;
case '智慧树':
// 智慧树特定方法
success = this.zhihuishuSiteSpecific();
break;
case 'B站':
// B站特定方法
success = this.bilibiliSiteSpecific();
break;
default:
// 通用方法,模拟空格键按下
try {
document.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', keyCode: 32, which: 32 }));
document.dispatchEvent(new KeyboardEvent('keyup', { key: ' ', keyCode: 32, which: 32 }));
console.log('已尝试模拟空格键播放');
success = true;
} catch (e) {
console.error('模拟空格键失败:', e);
}
break;
}
return success;
}
// 超星学习通特定解决方案
chaoxingSiteSpecific() {
try {
// 尝试查找并移除覆盖层
const overlays = document.querySelectorAll('.vjs-overlay, .ans-videoquiz');
overlays.forEach(overlay => overlay.remove());
// 尝试直接调用播放器的play方法
const playerObj = document.querySelector('#video_html5_api') ||
document.querySelector('.anv-video video');
if (playerObj && typeof playerObj.play === 'function') {
playerObj.play();
console.log('超星学习通: 直接调用播放器方法');
return true;
}
// 尝试点击特定的播放按钮
const specificButton = document.querySelector('.vjs-play-control') ||
document.querySelector('.vjs-big-play-button');
if (specificButton) {
specificButton.click();
console.log('超星学习通: 点击特定播放按钮');
return true;
}
return false;
} catch (e) {
console.error('超星学习通特定方法失败:', e);
return false;
}
}
// 智慧树特定解决方案
zhihuishuSiteSpecific() {
try {
// 尝试使用智慧树的视频对象
const videoContainer = document.querySelector('#mediaplayer');
if (videoContainer) {
const playButton = videoContainer.querySelector('[class*="play-button"]') ||
videoContainer.querySelector('.controlsBar__play');
if (playButton) {
playButton.click();
console.log('智慧树: 点击播放按钮');
return true;
}
}
// 尝试移除对话框
const dialog = document.querySelector('.dialog-content');
if (dialog) {
const okButton = dialog.querySelector('.ant-btn-primary');
if (okButton) {
okButton.click();
console.log('智慧树: 关闭对话框');
return true;
}
}
return false;
} catch (e) {
console.error('智慧树特定方法失败:', e);
return false;
}
}
// B站特定解决方案
bilibiliSiteSpecific() {
try {
// 尝试点击B站特定的播放按钮
const bButton = document.querySelector('.bpx-player-ctrl-play') ||
document.querySelector('.bilibili-player-video-btn-start');
if (bButton) {
bButton.click();
console.log('B站: 点击播放按钮');
return true;
}
// 尝试使用B站的视频对象
const bVideo = document.querySelector('video.bilibili-player-video');
if (bVideo && typeof bVideo.play === 'function') {
bVideo.play();
console.log('B站: 直接调用视频对象播放');
return true;
}
return false;
} catch (e) {
console.error('B站特定方法失败:', e);
return false;
}
}
// 查找并点击播放按钮
clickPlayButton() {
// 基于网站类型查找播放按钮
let playButton = null;
let clickSuccess = false;
switch(this.siteName) {
case '超星学习通':
// 超星的播放按钮
playButton = document.querySelector('.vjs-play-control') ||
document.querySelector('.vjs-big-play-button') ||
document.querySelector('.playButton');
break;
case '智慧树':
// 智慧树的播放按钮
playButton = document.querySelector('.controlsBar__play') ||
document.querySelector('.controlsBar__bigPlay') ||
document.querySelector('[class*="play-button"]');
break;
case 'B站':
// B站的播放按钮
playButton = document.querySelector('.bpx-player-ctrl-play') ||
document.querySelector('.bilibili-player-video-btn-start');
break;
default:
// 通用播放按钮选择器
playButton = document.querySelector('button[title="播放"]') ||
document.querySelector('.vjs-play-control') ||
document.querySelector('[class*="play-button"]') ||
document.querySelector('[class*="play_button"]') ||
document.querySelector('[aria-label*="播放"]') ||
document.querySelector('[aria-label*="Play"]');
break;
}
// 如果找到了播放按钮且当前是暂停状态,则点击
if (playButton &&
(playButton.classList.contains('vjs-paused') ||
!playButton.classList.contains('vjs-playing'))) {
try {
// 首先尝试模拟鼠标事件
['mouseover', 'mousedown', 'mouseup', 'click'].forEach(eventType => {
const event = new MouseEvent(eventType, {
bubbles: true,
cancelable: true,
view: window
});
playButton.dispatchEvent(event);
});
// 日志和状态记录
console.log('成功点击播放按钮');
clickSuccess = true;
} catch (e) {
console.error('点击播放按钮失败:', e);
}
}
// 检查iframe内的视频播放按钮
if (!clickSuccess) {
const iframes = document.querySelectorAll('iframe');
iframes.forEach(iframe => {
try {
if (iframe.contentDocument) {
const iframePlayButton = iframe.contentDocument.querySelector('[class*="play-button"]') ||
iframe.contentDocument.querySelector('.vjs-play-control') ||
iframe.contentDocument.querySelector('[aria-label*="播放"]');
if (iframePlayButton) {
try {
['mouseover', 'mousedown', 'mouseup', 'click'].forEach(eventType => {
const event = new MouseEvent(eventType, {
bubbles: true,
cancelable: true,
view: iframe.contentWindow
});
iframePlayButton.dispatchEvent(event);
});
console.log('成功点击iframe内的播放按钮');
clickSuccess = true;
} catch (e) {
console.error('点击iframe内播放按钮失败:', e);
}
}
}
} catch (e) {
// 跨域iframe访问错误,正常情况
console.log('无法访问iframe内容 (可能是跨域限制)');
}
});
}
return clickSuccess;
}
// 处理弹窗和对话框
handlePopupDialogs() {
// 处理常见的弹窗
const dialogSelectors = [
'.video-answer', // 超星的视频浏览会有的弹题
'.question-wrapper', // 智慧树的题目
'.dialog-test', // 一般测试弹窗
'.popbox', // 常见弹窗
'[class*="dialog"]', // 包含 dialog 的类
'[class*="popup"]', // 包含 popup 的类
'[class*="alert"]', // 包含 alert 的类
'[class*="modal"]', // 包含 modal 的类
// 特定平台的选择器
'.ans-videoquiz', // 超星弹题
'.ans-videoquiz-opt', // 超星选项
'.topic-item' // 智慧树题目
];
// 尝试处理匹配到的弹窗
let dialogHandled = false;
for (let selector of dialogSelectors) {
const dialogs = document.querySelectorAll(selector);
for (let dialog of dialogs) {
if (dialog && (dialog.style.display !== 'none' && dialog.offsetParent !== null)) {
// 尝试自动回答问题(对超星和智慧树常见的选择题)
if (this.tryToAnswerQuestion(dialog)) {
dialogHandled = true;
continue;
}
// 尝试找到并点击关闭按钮
const closeButtons = dialog.querySelectorAll('button, .close, .btn-close, [class*="close"], [class*="cancel"], a[href="#"]');
for (let button of closeButtons) {
if (button.innerText.includes('关闭') ||
button.innerText.includes('取消') ||
button.innerText.includes('继续') ||
button.innerText.includes('确定') ||
button.className.includes('close')) {
button.click();
console.log('自动关闭了弹窗');
dialogHandled = true;
break;
}
}
if (dialogHandled) break;
}
}
if (dialogHandled) break;
}
// 特殊处理:超星学习通的弹题
if (!dialogHandled && this.siteName === '超星学习通') {
// 超星特定的弹题处理
const topicMenus = document.querySelectorAll('.topic-menu, .ans-videoquiz');
if (topicMenus.length > 0) {
// 尝试寻找继续按钮
const continueButtons = document.querySelectorAll('div[onclick*="continue"], a:contains("继续学习"), .ans-videoquiz-submit');
if (continueButtons.length > 0) {
continueButtons[0].click();
dialogHandled = true;
}
// 如果没找到继续按钮,尝试自动选择答案
if (!dialogHandled) {
const options = document.querySelectorAll('.ans-videoquiz-opt');
if (options.length > 0) {
// 默认选第一个
options[0].click();
// 寻找提交按钮
setTimeout(() => {
const submitBtn = document.querySelector('.ans-videoquiz-submit');
if (submitBtn) submitBtn.click();
}, 500);
}
}
}
}
return dialogHandled;
}
// 尝试自动回答问题
tryToAnswerQuestion(dialog) {
// 检查是否有选项
const options = dialog.querySelectorAll('input[type="radio"], input[type="checkbox"], .ans-videoquiz-opt');
if (options.length === 0) return false;
// 智慧树和超星平台的问题处理
if (this.siteName === '智慧树' || this.siteName === '超星学习通') {
// 随机选择一个选项(或第一个)
const option = options[0];
option.click();
// 查找提交按钮
setTimeout(() => {
const submitBtns = dialog.querySelectorAll('button[type="submit"], .submit-btn, .ans-videoquiz-submit, [class*="submit"]');
if (submitBtns.length > 0) {
submitBtns[0].click();
return true;
}
}, 500);
}
return false;
}
// 禁用自动播放
disableAutoPlay() {
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
if (this.autoPlayInterval) {
clearInterval(this.autoPlayInterval);
this.autoPlayInterval = null;
}
// 更新自动播放状态
document.getElementById('autoplay-status').textContent = '已禁用';
document.getElementById('autoplay-status').style.color = '#888';
}
// 启用自动下一章
enableAutoNext() {
if (this.autoNextInterval) {
clearInterval(this.autoNextInterval);
}
this.autoNextInterval = setInterval(() => {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (video.ended) {
this.findAndClickNextButton();
}
});
}, 2000);
}
// 禁用自动下一章
disableAutoNext() {
if (this.autoNextInterval) {
clearInterval(this.autoNextInterval);
this.autoNextInterval = null;
}
}
// 寻找并点击下一章按钮
findAndClickNextButton() {
// 针对不同平台查找下一章按钮
let nextBtn = null;
// 超星学习通
if (this.siteName === '超星学习通') {
nextBtn = document.querySelector('.ans-job-icon[title="下一章"]') ||
document.querySelector('.nextChapter');
}
// 智慧树
else if (this.siteName === '智慧树') {
nextBtn = document.querySelector('.next-page-btn') ||
document.querySelector('.next-btn');
}
// 智慧职教
else if (this.siteName === '智慧职教') {
nextBtn = document.querySelector('.next_lesson') ||
document.querySelector('.next-lesson');
}
// 其他平台的通用选择器
else {
const possibleSelectors = [
'.next', '.next-btn', '.next-lesson', '.nextChapter',
'[title="下一章"]', '[title="下一节"]', '[title="下一讲"]',
'a:contains("下一章")', 'a:contains("下一节")'
];
for (let selector of possibleSelectors) {
nextBtn = document.querySelector(selector);
if (nextBtn) break;
}
}
if (nextBtn) {
nextBtn.click();
console.log('已自动跳转至下一章');
}
}
// 设置视频速度
applyVideoSpeed(speed) {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
video.playbackRate = speed;
});
// 持续应用速度(防止视频网站重置)
if (this.speedInterval) {
clearInterval(this.speedInterval);
}
this.speedInterval = setInterval(() => {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
if (video.playbackRate !== speed) {
video.playbackRate = speed;
}
});
// 更新速度进度条
const speedProgressBar = document.getElementById('speed-progress');
if (speedProgressBar) {
const speedPercent = Math.min(100, (speed / 16) * 100);
speedProgressBar.style.width = `${speedPercent}%`;
}
// 在面板上显示当前速度
const statusText = document.getElementById('video-status');
if (statusText && statusText.textContent.includes('播放中')) {
statusText.textContent = `播放中 (${speed}x)`;
}
}, 1000);
// 显示状态提示
this.showStatusTip(`视频速度已设置为 ${speed}x`, 2000);
}
// 启用拖动功能
enableDrag(elements) {
elements.forEach(el => {
el.addEventListener('mousedown', (e) => {
const target = el.closest('#miaoke-helper-panel, #note-panel, #miaoke-helper-btn');
if (!target) return;
// 初始位置
const initialX = e.clientX;
const initialY = e.clientY;
const startLeft = target.offsetLeft;
const startTop = target.offsetTop;
// 移动处理函数
const moveHandler = (e) => {
const dx = e.clientX - initialX;
const dy = e.clientY - initialY;
target.style.left = startLeft + dx + 'px';
target.style.top = startTop + dy + 'px';
};
// 释放处理函数
const upHandler = () => {
document.removeEventListener('mousemove', moveHandler);
document.removeEventListener('mouseup', upHandler);
};
document.addEventListener('mousemove', moveHandler);
document.addEventListener('mouseup', upHandler);
});
});
}
// 清理资源,用于页面卸载或重新初始化时
cleanup() {
// 移除面板
const panel = document.getElementById('miaoke-panel');
if (panel) {
panel.parentNode.removeChild(panel);
}
// 移除状态提示
const tips = document.querySelectorAll('.miaoke-status-tip');
tips.forEach(tip => {
tip.parentNode.removeChild(tip);
});
// 清理定时器
if (this.autoPlayInterval) {
clearInterval(this.autoPlayInterval);
this.autoPlayInterval = null;
}
if (this.speedInterval) {
clearInterval(this.speedInterval);
this.speedInterval = null;
}
// 断开突变观察器
if (this.mutationObserver) {
this.mutationObserver.disconnect();
this.mutationObserver = null;
}
// 清除实例引用
window.miaokeHelperInstance = null;
}
// 更新视频状态
updateVideoStatus() {
const videos = document.querySelectorAll('video');
if (videos.length > 0) {
let anyPlaying = false;
videos.forEach(video => {
if (!video.paused && !video.ended) {
anyPlaying = true;
// 更新当前播放中的视频进度
this.updateVideoProgress(video);
}
});
const statusText = document.getElementById('video-status');
if (anyPlaying) {
const currentSpeed = parseFloat(document.getElementById('play-speed').value);
statusText.textContent = `播放中 (${currentSpeed}x)`;
statusText.style.color = '#4CAF50';
} else if (videos[0].ended) {
statusText.textContent = '已播放完毕';
statusText.style.color = '#FF9800';
} else {
statusText.textContent = '已暂停';
statusText.style.color = '#F44336';
}
}
}
// 更新视频进度条
updateVideoProgress(video) {
if (!video) return;
// 计算百分比
const percent = (video.currentTime / video.duration) * 100;
const progressBar = document.getElementById('video-progress');
if (!isNaN(percent) && isFinite(percent)) {
progressBar.style.width = `${percent}%`;
// 更新速度进度条
const speedProgressBar = document.getElementById('speed-progress');
if (speedProgressBar) {
const speedPercent = Math.min(100, (parseFloat(document.getElementById('play-speed').value) / 16) * 100);
speedProgressBar.style.width = `${speedPercent}%`;
}
}
}
// 设置周期性提醒
setupPeriodicReminder() {
const reminderInterval = 60 * 60 * 1000; // 1小时
setInterval(() => {
this.showReminderNotification();
}, reminderInterval);
}
// 显示周期性提醒
showReminderNotification() {
const notification = document.createElement('div');
notification.className = 'miaoke-notification';
notification.innerHTML = `
<div class="notification-header">
<span>📣 喵课助手提醒 📣</span>
<span class="notification-close">×</span>
</div>
<div class="notification-content">
<p style="text-align:center;margin-bottom:15px;">您已经学习了一段时间了,记得休息一下,保护眼睛和身体哦!</p>
<button class="share-button" id="miaoke-reminder-share">分享给同学</button>
</div>
<div class="notification-footer">
<a href="http://nb.zizizi.top" target="_blank" style="color:#6a5af9;text-decoration:none;margin-right:auto;">了解更多</a>
<div class="notification-close" style="padding:5px 15px;background:#6a5af9;color:white;border-radius:5px;cursor:pointer;">我知道了</div>
</div>
`;
document.body.appendChild(notification);
// 关闭按钮事件
const closeButtons = notification.querySelectorAll('.notification-close');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
notification.style.animation = 'slideOutRight 0.5s forwards';
setTimeout(() => {
notification.remove();
}, 500);
});
});
// 分享按钮事件
const shareButton = notification.querySelector('#miaoke-reminder-share');
if (shareButton) {
shareButton.addEventListener('click', () => {
const shareText = '喵课助手提醒您:学习是一件辛苦的事情,记得休息一下,保护眼睛和身体哦!';
const tempInput = document.createElement('textarea');
tempInput.style.position = 'absolute';
tempInput.style.left = '-9999px';
tempInput.value = shareText;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
this.showTip('分享文本已复制到剪贴板,快去发给同学吧!', 3000);
} catch (err) {
this.showTip('复制失败,请手动复制分享文本', 3000);
}
document.body.removeChild(tempInput);
});
}
// 自动关闭计时器
setTimeout(() => {
if (document.body.contains(notification)) {
notification.style.animation = 'slideOutRight 0.5s forwards';
setTimeout(() => {
if (document.body.contains(notification)) {
notification.remove();
}
}, 500);
}
}, 20000);
}
}
// 在DOMContentLoaded时检查,优先于load事件
document.addEventListener('DOMContentLoaded', () => {
initializeHelper();
});
// 页面完全加载后再次检查(以防DOMContentLoaded中的检查有问题)
window.addEventListener('load', () => {
initializeHelper();
});
// 页面卸载时清理资源
window.addEventListener('beforeunload', () => {
if (window.miaokeHelperInstance) {
window.miaokeHelperInstance.cleanup();
}
});
// 帮助器初始化函数
function initializeHelper() {
// 防止短时间内多次初始化
if (window.miaokeHelperInitializing) {
return;
}
window.miaokeHelperInitializing = true;
setTimeout(() => {
window.miaokeHelperInitializing = false;
}, 2000);
// 首先检查全局实例
if (window.miaokeHelperInstance) {
console.log('喵课助手全局实例已存在,跳过初始化');
return;
}
// 检查是否在iframe中,只在顶层窗口创建
if (window !== window.top) {
console.log('在iframe中,跳过创建喵课助手');
return;
}
// 检查localStorage中是否有记录
try {
const initialized = localStorage.getItem('miaokeHelper_initialized');
const timestamp = localStorage.getItem('miaokeHelper_timestamp');
// 如果已初始化且时间在5秒内,则跳过
if (initialized === 'true' && timestamp && (Date.now() - parseInt(timestamp)) < 5000) {
console.log('喵课助手最近已初始化,跳过');
return;
}
} catch (e) {
console.log('无法读取localStorage');
}
// 检查DOM中是否已有面板
const existingPanel = document.getElementById('miaoke-panel');
if (existingPanel) {
console.log('页面已存在喵课助手面板');
return;
}
// 所有检查通过,创建新实例
console.log('创建新的喵课助手实例');
window.miaokeHelper = new MiaoKeHelper();
}
// 立即检查是否应该初始化
if (document.readyState === 'complete' || document.readyState === 'interactive') {
initializeHelper();
}
})();