Greasy Fork

Greasy Fork is available in English.

【高教在线刷课助手】|| 自动执行,移除防止暂停,自动跳转下一节

高教在线课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         【高教在线刷课助手】|| 自动执行,移除防止暂停,自动跳转下一节
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  高教在线课程自动挂机,当前脚本支持课程视频播放完成,自动跳转下一小节,章节测试自动跳过,后台播放防止视频暂停。
// @author       Sweek
// @match        *://*.cqooc.com/*
// @license      GPLv3
// @icon         https://www.sweek.top/api/preview/avatar.jpg
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://code.jquery.com/jquery-2.1.4.min.js
// @grant        unsafeWindow
// @grant        GM_getResourceText
// ==/UserScript==






/* 窗口初始化 */
/* 窗口初始化 */
/* 窗口初始化 */

function initPopup() {
  // 创建 CSS 样式
  const popCSs = `
    #my-window {
      position: fixed;
      top: 10px;
      left: 20px;
      width: 400px;
      height: 600px;
      background: rgb(255, 255, 255);
      color: white;
      font-size: 14px;
      border-radius: 5px;
      z-index: 9999;
      display: flex;
      flex-direction: column;
      user-select: none;
      font-family: 'fangsong';
      border: 1px solid rgb(71, 158, 130);
      overflow: hidden;
    }
    #window-header {
      height: 50px;
      padding: 0 10px;
      background: rgb(71, 158, 130);
      cursor: grab;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-radius: 5px 5px 0 0;
      .window-header-title {
        font-size: 16px;
        font-weight: bold;
        font-family: 'fangsong';
      }
    }
    #restore-btn {
      cursor: pointer;
      font-size: 16px;
      background: none;
      border: none;
      color: #444;
      height:20px;
      width:30px;
      text-align: center;
      line-height: 20px;
      background: #fff;
      border-radius: 5px;
      padding: 0 5px;
    }
    #restore-btn:hover {
      background: rgb(71, 158, 130);
      border: 1px solid #fff;
      color: #fff;
    }
    .tab {
      display: inline-block;
      padding: 5px 10px;
      cursor: pointer;
      background: #888;
      text-align: center;
      font-weight: bold;
    }
    .tab.active {
      background: #444;
    }
    #window-content {
      flex: 1;
      padding: 8px;
      overflow: auto;
    }
    #my-window.minimized {
      height: 50px;
      overflow: hidden;
    }
    #tab-container {
      display: block;
      width: 100%;
      background: #ddd;
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      padding: 6px 8px;
    }
    #window-content {
      display: block;
      background: #fff;
    }
    #my-window.minimized #window-content, #my-window.minimized #tab-container {
      display: none;
    }
    #taskCountContent {
      background: #fff;
      border: 1px solid #ccc;
      border-radius: 5px;
      height: 100%;
      overflow-y: auto;
      color: #333;
      line-height: 20px;
      padding: 5px;
    }
    #taskLogsContent {
      background: #222;
      border: 1px solid #ccc;
      border-radius: 2px;
      height: 100%;
      overflow-y: auto;
      color: #fff;
      line-height: 20px;
      padding: 5px;
    }
  `;

  // 添加 CSS 样式
  const style = document.createElement("style");
  style.innerHTML = popCSs;
  document.head.appendChild(style);

  // 创建窗口元素
  const popHtml = `
    <div id="window-header">
      <span class="window-header-title">Sweek高教在线刷课助手[0.0.1]</span>
      <div id="restore-btn">⏷</div>
    </div>
    <div id="tab-container">
      <span class="tab active" data-tab="taskCount">页面任务</span>
      <span class="tab" data-tab="taskLogs">执行日志</span>
    </div>
    <div id="window-content">
      <div id="taskCountContent">当前任务数: <span id="taskCount">0</span></div>
      <div id="taskLogsContent" style="display: none;"></div>
    </div>
  `;

  const myWindow = document.createElement("div");
  myWindow.id = "my-window";
  myWindow.innerHTML = popHtml;
  document.body.appendChild(myWindow);

  // 绑定最小化按钮事件
  document.getElementById("restore-btn").addEventListener("click", () => {
    myWindow.classList.toggle("minimized");
  });

  // 处理拖动窗口
  let isDragging = false;
  let offsetX = 0;
  let offsetY = 0;

  document.getElementById("window-header").addEventListener("mousedown", (e) => {
    isDragging = true;
    offsetX = e.clientX - myWindow.offsetLeft;
    offsetY = e.clientY - myWindow.offsetTop;
  });

  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      let x = e.clientX - offsetX;
      let y = e.clientY - offsetY;
  
      // 限制窗口不能拖出屏幕
      let maxX = window.innerWidth - myWindow.offsetWidth;
      let maxY = window.innerHeight - myWindow.offsetHeight;
  
      x = Math.max(0, Math.min(x, maxX));
      y = Math.max(0, Math.min(y, maxY));
  
      myWindow.style.left = `${x}px`;
      myWindow.style.top = `${y}px`;
    }
  });
  

  document.addEventListener("mouseup", () => {
    isDragging = false;
  });

  // 绑定 Tab 切换事件
  document.querySelectorAll(".tab").forEach(tab => {
    tab.addEventListener("click", () => {
      document.querySelectorAll(".tab").forEach(el => el.classList.remove("active"));
      tab.classList.add("active");
      
      document.getElementById("taskCountContent").style.display = "none";
      document.getElementById("taskLogsContent").style.display = "none";
      
      if (tab.dataset.tab === "taskCount") {
        document.getElementById("taskCountContent").style.display = "block";
      } else {
        document.getElementById("taskLogsContent").style.display = "block";
      }
    });
  });
}



function destroyPopup() {
  const myWindow = document.getElementById("my-window");
  if (myWindow) {
    myWindow.remove(); // 移除窗口
  }

  const styleTags = document.querySelectorAll("style");
  styleTags.forEach(style => {
    if (style.innerHTML.includes("#my-window")) {
      style.remove(); // 移除添加的 CSS 样式
    }
  });

  document.removeEventListener("mousemove", moveHandler);
  document.removeEventListener("mouseup", upHandler);

  // console.log("窗口已销毁");
}










/* 课程处理方法 */
/* 课程处理方法 */
/* 课程处理方法 */

let taskQueue = []; // 任务队列
const testDealEvent = new Event("testRedeal", { bubbles: false, cancelable: false });



// 处理视频
function handleVideo(Dom) {
  return new Promise((resolve) => {
    const playButton = Dom.querySelector(".dplayer-mobile-play"); // 获取播放按钮
    const video = Dom.querySelector("video"); // 获取视频元素

    if (!video) {
      addLog("未找到视频元素");
      return resolve();
    }

    let playAttempted = false; // 是否尝试过播放

    // 监听视频播放时的进度
    video.addEventListener("timeupdate", () => {
      const currentTime = video.currentTime; // 当前播放时间
      const duration = video.duration; // 视频总时长
      const playbackRate = video.playbackRate; // 播放倍速

      // 生成显示进度的内容
      const val1 = currentTime; // 当前播放时间
      const val2 = duration; // 视频总时长
      setProgress(val1, val2, playbackRate, 'video'); // 设置显示播放进度
    });

    function preventPause() {
      video.onpause = () => {
        // addLog("视频暂停,1秒后重新播放");
        setTimeout(() => {
          if (video.paused) {
            video.play().catch((error) => {
              addLog(`重新播放失败: ${error}`);
            });
          }
        }, 500); // 0.5秒后重新播放
      };
    }

    function checkPlayback() {
      setTimeout(() => {
        if (!video.paused) {
          addLog("视频已成功播放");
          return;
        }

        addLog("播放按钮点击无效,尝试直接调用 video.play()");
        video.muted = true; // 静音播放,防止浏览器限制
        video.play().catch((error) => {
          addLog(`直接播放失败: ${error}`);
        });
      }, 500); // 延迟检查,确保点击后有时间触发播放
    }

    if (playButton) {
      addLog("找到播放按钮,尝试点击播放");
      playButton.click();
      playAttempted = true;
      checkPlayback(); // 延迟检查播放情况
    }

    if (!playAttempted) {
      addLog("未找到播放按钮,尝试直接调用 video.play()");
      video.muted = true;
      video.play().catch((error) => {
        addLog(`直接播放失败: ${error}`);
      });
    }

    // 阻止视频暂停
    preventPause();

    // 监听视频播放完成事件
    video.onended = () => {
      addLog("视频播放完成");
      // 清理事件监听和状态
      video.onended = null;  // 移除事件监听
      video.onpause = null;  // 移除暂停监听
      resolve();  // 完成当前视频任务
    };
  });
}










// 处理PDF
function handlePDF(Dom) {
  return new Promise((resolve) => {
    const interval = setInterval(() => {
      const headerBox = document.querySelector(".header-box");
      if (headerBox) {
        const text = headerBox.innerText;

        if (!text.includes("完成倒计时")) {
          clearInterval(interval); // 停止轮询
          resolve(); // 任务完成,调用 resolve
        }
      }
    }, 1000); // 每秒检查一次
  });
}





// 获取课程内容类型
function getCourseContentType(Dom) {

  // 获取DOM元素的HTML内容
  const htmlContent = Dom.innerHTML || Dom.outerHTML;

  // 判断Dom中是否包含<video>标签
  if (htmlContent.includes('video')) {
    return 'video';
  }

  // 判断Dom中是否包含<iframe>标签
  if (htmlContent.includes('iframe')) {
    return 'PDF';
  }

  // 如果都不包含,返回null或者其他默认值
  return null;
}






// 处理课程接口
function handleCourseContent(Dom) {
  return new Promise((resolve) => {
      const courseContentType = getCourseContentType(Dom)
      switch (courseContentType) {
        case 'video':
          addLog("开始处理视频任务...");
          handleVideo(Dom).then(() => {
            addLog("视频任务处理完成");
            resolve();
          });
          break;
        case 'PDF':
          addLog("开始处理PDF/PPT内容...");
          handlePDF(Dom).then(() => {
            addLog("PDF/PPT任务处理完成");
            resolve();
          });
          break;
        default:
          addLog("未知课程类型,跳过该任务");
          resolve();
      }
  });
}



// 监听任务完成状态的函数
function waitForContentToLoad() {
  return new Promise((resolve) => {
      let contentContainer = document.querySelector(".video-box");
      if (!contentContainer) {
        addLog("未找到课程内容模块,直接继续下一个任务");
        resolve();
        return
      }

      let observer = new MutationObserver((mutations, obs) => {
          if (contentContainer.innerText.trim() !== "") {
              obs.disconnect(); // 停止监听

              // 调用 handleCourseContent,并等待其完成
              handleCourseContent(contentContainer).then(() => {
                  resolve();
              });
          }
      });

      observer.observe(contentContainer, { childList: true, subtree: true });

      // 设置超时防止卡死
      // setTimeout(() => {
      //     addLog("课程内容加载超时,继续下一个任务");
      //     observer.disconnect();
      //     resolve();
      // }, 5000);
  });
}


// 任务执行器
async function processNextTask() {
    if (taskQueue.length === 0) {
        addLog("所有任务已执行完毕");
        return;
    }

    let element = taskQueue.shift(); // 取出任务
    // console.log(`点击元素:`, element);

    element.click(); // 触发点击
    await waitForContentToLoad(); // 等待课程内容加载处理完成

    // 触发下一个任务
    setTimeout(() => {
        document.dispatchEvent(testDealEvent);
    }, 2000);
}

// 监听事件,触发下一个任务
document.addEventListener("testRedeal", processNextTask);


// 初始化任务执行
function startTaskFlow() {
  let allTasks = Array.from(document.querySelectorAll(".third-level-box"))
    .filter(el => !/作业|测验/.test(el.innerText)); // 筛选掉包含"作业"或"测验"的任务

  let activeIndex = allTasks.findIndex(el => el.classList.contains("active"));
  addLog(`本课程共 ${allTasks.length} 个有效任务`); // 仅计算有效任务

  if (activeIndex === -1) {
      console.log("未找到 active 状态的元素,从头开始执行任务流");
      taskQueue = allTasks;
  } else {
      console.log(`找到 active 元素,从索引 ${activeIndex} 开始执行任务流`);
      taskQueue = [...allTasks.slice(activeIndex)];
  }

  addLog(`共 ${taskQueue.length} 个任务即将执行`);

  if (taskQueue.length > 0) {
      document.dispatchEvent(testDealEvent);
  }
}











/* 工具方法 */
/* 工具方法 */
/* 工具方法 */

// 格式化时间函数
function formatTime(seconds) {
  const minutes = Math.floor(seconds / 60);
  const remainingSeconds = Math.floor(seconds % 60);
  return `${minutes}:${remainingSeconds < 10 ? "0" + remainingSeconds : remainingSeconds}`;
}

// 添加日志方法
function addLog(message) {
  let logEl = document.getElementById("taskLogsContent");
  let logItem = document.createElement("div");
  logItem.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
  logEl.appendChild(logItem);
};


// 设置播放进度
function setProgress(val1, val2, val3, type) {        
  let newContent = "";
  switch (type) {
      case "video":
        newContent += `<p>播放进度:${(val1/val2 * 100).toFixed(2)}%</p><hr>`;
        newContent += `<p>播放倍速:${val3}</p><hr>`;
        newContent += `<p>视频长度:${formatTime(val1)}/${formatTime(val2)}</p>`;
        break;
      case "PDF":
        newContent += `<p>滚动进度:${(val1/val2 * 100).toFixed(2)}%</p><hr>`;
        newContent += `<p>滚动倍速:${val3}</p><hr>`;
        newContent += `<p>PDF高度:${formatTime(val1)}/${formatTime(val2)}</p>`;
        break;
      default:
        break;
  }
  const taskCountContent = document.getElementById("taskCountContent"); // 获取显示进度的元素
  // 更新 taskCountContent 中的内容
  taskCountContent.innerHTML = newContent;
}

// 页面通知提示
function notify(text, time) {
  // 创建通知元素
  var notification = document.createElement('div');
  notification.className = 'notification';
  // 设置通知内容
  notification.innerHTML = '<div class="notification-content"><h2 style="font-size: 16px;font-weight: bold;color: #307dff;font-family: fangsong;">' + 'Sweek高教在线刷课助手提示' + '</h2><p style="font-family: fangsong;font-size: 13px;font-weight: bold;">' + text + '</p></div>';
  // 将通知添加到页面中
  document.body.appendChild(notification);
  // 设置通知样式
  notification.style.position = 'fixed';
  notification.style.top = '50px';
  notification.style.left = '-400px'; // 从左边弹出
  notification.style.transform = 'translateY(-50%)'; // 垂直居中
  notification.style.transition = 'left 0.5s ease-in-out'; // 添加过渡效果
  notification.style.zIndex = '999999';
  notification.style.backgroundColor = '#fff';
  notification.style.border = '1px solid #ccc';
  notification.style.padding = '10px';
  notification.style.borderRadius = '5px';
  notification.style.lineHeight = '25px';

  // 等待一小段时间后,移动通知到左边
  setTimeout(function() {
      notification.style.left = '20px'; // 移动到左边
  }, 100);

  // 设置定时器,在指定时间后移除通知
  setTimeout(function() {
      // 移动通知到左边以外
      notification.style.left = '-400px';
      // 等待过渡效果完成后,移除通知元素
      setTimeout(function() {
      notification.remove();
      }, 500);
  }, time);
}



/* 处理页面路由跳转 */
/* 处理页面路由跳转 */
/* 处理页面路由跳转 */

let lastPath = location.pathname;

function handlePathChange() {
  if (lastPath !== location.pathname) {
    lastPath = location.pathname;
    handleInPageNotify()
  }
}

// 进入页面提示逻辑
function handleInPageNotify() {
  // console.log('location.pathname:::+ ', location.pathname)
  switch (location.pathname) {
    case '/index/home':
      notify('高教在线刷课助手已成功启动,已进入高教在线首页,请鼠标悬浮右上角头像点击在学课程', 2500)
      destroyPopup()
      break;
    case '/account/course':
      notify('已进入在学课程页面', 2500)
      destroyPopup()
      break;
    case '/course/detail/courseStudy':
      notify('已进入课程学习页面', 2500)
      // 初始化窗口
      initPopup()
      setTimeout(() => {
        // 执行任务流
        startTaskFlow();
      }, 2500);
      break;
    default:
      break;
  }
}

// 劫持 history.pushState 和 replaceState
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;

history.pushState = function (...args) {
  originalPushState.apply(this, args);
  handlePathChange();
};

history.replaceState = function (...args) {
  originalReplaceState.apply(this, args);
  handlePathChange();
};

window.addEventListener("popstate", handlePathChange);



// 方法执行入口
// 方法执行入口
// 方法执行入口
(async function () {
  // 引入Bootstrap Icons
  let link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = "https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.10.0/font/bootstrap-icons.css";
  document.head.appendChild(link);
  handleInPageNotify()
})();