Greasy Fork

Greasy Fork is available in English.

学起Plus、弘成教育挂课自动连续播放

一个网课挂机自动连续播放工具,仅适用于学起Plus、弘成教育 sccchina.net chinaedu.net,反馈与交流QQ群:715307684,更新日期:2022年11月14日

当前为 2022-11-20 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         学起Plus、弘成教育挂课自动连续播放
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  一个网课挂机自动连续播放工具,仅适用于学起Plus、弘成教育 sccchina.net chinaedu.net,反馈与交流QQ群:715307684,更新日期:2022年11月14日
// @author       哆哆啦啦梦
// @match        *://*.chinaedu.net/*
// @match        *://*.sccchina.net/*
// @match        *://*.edu.cn/*
// @match        *://*.bnude.cn/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chinaedu.net
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @run-at       document-end
// @license      GPLv3
// ==/UserScript==

const lessionRules = {
  "play.html": {
    selector: ['.page-sidebar li>a>span[class="title"]'],
  },
  "study.do": {
    beforeFun: () => {
      const catalogDiv = document.getElementById("catalogDiv");
      if (catalogDiv.childElementCount === 0) {
        const catalog = document.getElementById("catalogA");
        catalog && catalog.className.indexOf("Cur") === -1 && catalog.click();
      }
    },
    selector: ["#catalogDiv span[onclick]", "#catalogDiv span[class='tit']"],
  },
  "mp4_video_index.html": {
    selector: [".ui-folder .ui-leaf span"],
  },
};

const currentRules = {
  "videolearning.html": {
    selector: [".page-sidebar li.active>a>span"],
  },
  "play.html": {
    selector: [".page-breadcrumb li>a", ".page-sidebar li.active>a>span"],
  },
  "mp4_video_index.html": {
    selector: [".ui-folder .ui-leaf.ui-selected span"],
  },
  "study.do": {
    selector: [
      "#catalogDiv .cur span",
      '.study-video-title span[class$="title"]',
    ],
  },
};

const videoRules = {
  "video.html": {
    selector: ["#videoFrame video"],
  },
  "play.html": {
    selector: ["#draggable video"],
  },
  "mp4_video_index.html": {
    selector: [".plyr__video-wrapper video"],
  },
  "study.do": {
    selector: ["videobox video"],
  },
};

const noNeedAutoPlayRules = ["mp4_video_index"];

function isInNoNeedAutoPlay() {
  return noNeedAutoPlayRules.find((e) => document.URL.indexOf(e) > 0);
}

function urlIn(rules) {
  for (let key in rules) {
    if (document.URL.indexOf(key) > 0) {
      return true;
    }
  }

  return false;
}

function getDataForRules(rules) {
  for (let key in rules) {
    if (document.URL.indexOf(key) > 0) {
      for (let i = 0; i < rules[key].selector.length; i++) {
        rules[key].beforeFun && rules[key].beforeFun(rules[key].selector[i]);
        const res = document.querySelectorAll(rules[key].selector[i]);
        rules[key].afterFun && rules[key].afterFun(rules[key].selector[i], res);

        if (res.length > 0) {
          return res;
        }
      }
    }
  }

  return null;
}

function getCurrentLession() {
  const arr = getDataForRules(currentRules);

  if (arr) {
    GM_setValue("current", arr[arr.length - 1].innerText);
  }
}

function getLessionsInfo() {
  const arr = getDataForRules(lessionRules);

  if (arr) {
    const lessions = [];
    for (let i = 0; i < arr.length; i++) {
      const className = "api20221120-" + i;
      if (arr[i].className.indexOf(className) === -1) {
        arr[i].className += " " + className;
      }
      lessions.push({ title: arr[i].innerText, className });
    }
    GM_setValue("lessions", lessions);
  }
}

let findVideoCount = 0;
const findVideoMaxCount = 3;

function getVideo() {
  const status = GM_getValue("play_end");
  if (GM_getValue("video") || status) {
    return;
  }

  if (findVideoCount >= findVideoMaxCount) {
    if (status !== "not found") {
      GM_setValue("play_end", "not found");
      findVideoCount = 0;
    }
    return;
  }

  if (document.querySelector("video")) {
    GM_setValue("video", document.URL);

    setTimeout(() => {
      playCheck();
    }, 5000);
  } else {
    findVideoCount++;
  }
}

function playCheck() {
  if (GM_getValue("play_end")) {
    return;
  }

  const video = document.querySelector("video");

  if (video) {
    video.muted = true;
    video.playbackRate = 1;

    const currentTime = video.currentTime.toFixed(1);
    const totalTime = video.duration.toFixed(1);

    console.log(`当前进度:${currentTime}/${totalTime}`);

    if (video.ended) {
      setTimeout(() => {
        GM_setValue("play_end", "over");
      }, 5000);
    } else {
      if (video.paused) {
        console.log("视频被暂停,继续播放!");
        video.play();
      }

      setTimeout(() => {
        playCheck();
      }, 5000);
    }
  } else {
    console.log("异常:找不到视频元素了");
  }
}

function nextCheck() {
  const status = GM_getValue("play_end");
  const lessions = GM_getValue("lessions");
  if (status && lessions && lessions.length) {
    let currentText = GM_getValue("current");
    const lastCurrent = GM_getValue("last_current");
    let step = GM_getValue("step");

    if (!lastCurrent || (currentText && lastCurrent !== currentText)) {
      GM_setValue("last_current", currentText);
      step = 1;
    } else {
      currentText = lastCurrent;
      step += 1;
    }

    GM_setValue("step", step);

    let index = GM_getValue("last_pos") ?? 0;

    if (status === "not found" && !currentText) {
      step = 0;
    } else if (isInNoNeedAutoPlay()) {
      return;
    }

    const newIndex = lessions.findIndex((e) => e.title === currentText);

    if (newIndex !== -1) {
      index = newIndex;
    }

    GM_setValue("last_pos", index);

    if (index + step < lessions.length) {
      document.querySelector("." + lessions[index + step].className).click();
      GM_deleteValue("play_end");
      GM_deleteValue("video");
    } else {
      alert("课程播放结束");
      return;
    }
  }

  setTimeout(() => {
    nextCheck();
  }, 5000);
}

function getResource() {
  getCurrentLession();
  getLessionsInfo();
}

function init() {
  GM_deleteValue("play_end");
  GM_deleteValue("video");
  GM_deleteValue("current");
  GM_deleteValue("last_current");
  GM_deleteValue("lessions");
  GM_setValue("step", 1);
  GM_setValue("last_pos", 0);
}

function popupClose() {
  const tips = document.querySelector(".win-content");

  if (tips && tips.innerText.indexOf("继续学习") > 0) {
    const btn = document.querySelector(".win-content .close-win-bt");
    btn && btn.click();
  }

  const pop = document.querySelector("#pop");

  pop && pop.querySelector(".pop_close").click();
}

function work() {
  init();

  setTimeout(() => {
    urlIn(lessionRules) && nextCheck();
  }, 5000);

  setInterval(() => {
    urlIn(videoRules) && getVideo();
  }, 10000);

  setInterval(() => {
    getResource();
    popupClose();
  }, 3000);
}

(function () {
  "use strict";

  work();
})();