// ==UserScript==
// @name 云班课高效助手
// @name:zh-CN 云班课高效助手
// @author bellamy.n.h
// @namespace http://tampermonkey.net/
// @version 1.88
// @description 【高效再升级😃!高效使用云班课,一个脚本就够了!😎】 【🧡视频倍速:新增视频倍速控件(支持 倍速递加、递减;倍速重置;一键最佳倍速;视频快进、快退)】、【💛视频连播:新版视频连播功能,支持从当前视频开始连播(配合视频控件,体验更佳)】、【💙快捷键:新增快捷键系统,常用功能都已加入,高效更进一步】、【💚资源处理:批量点击、下载、批处理】
// @match https://www.mosoteach.cn/web/index.php*
// @include *://www.mosoteach.cn/web/index.php*
// @note Version 1.88 修复视频播放界面点击连播相关按钮无效;修复对全局键盘的频繁操作误判,改为只判断快捷键的频繁操作;修改视频快进、视频快退的速度为一次前进或后退5s;
// @note Version 1.85 —— 1.87 修复连播视频时数量错误BUG;重构快捷键视图生成代码,降冗余;Add Statistical Analysis System;限制对快捷键的频繁操作;特殊处理部分高频使用的快捷键;Fix Some Bugs。
// @note Version 1.80 😁【新增视频倍速控件(支持 倍速递加、递减;倍速重置;一键最佳倍速;视频快进、快退)】、【新版视频连播功能,支持从当前视频开始连播(配合视频控件,可达到极度自由)】、【新增快捷键系统,常用功能已都加入,高效更进一步】、【修复模拟点击/下载失效Bug】、【限制全部连播最大速度为8倍】
// @note Version 1.70 视频最高16倍速连播;调用系统通知,反馈更佳;
// @note Version 1.65 偷偷改了些小Bug 🤭,使连播更顺畅。下个版本上16倍速连播喽😊
// @note Version 1.60 新增测试功能,支持 连续播放所有视频、 立即看完当前视频(测试阶段,还请反馈)
// @note Version 1.50 加强对输入值约束; 支持多栏处理; chrome浏览器自动打开 设置页面地址更改; 其他Bug修复。
// @note Version 1.40 优化代码; 新增浏览器类型判断,支持chrome浏览器自动打开 设置页面。
// @note Version 1.32 优化操作反馈 (可以重置已选择的资源栏数)
// @note Version 1.31 修复可能存在的Bug (页面无法自动关闭)
// @icon https://s1.ax1x.com/2020/05/18/Yf6Kcd.png
// @require https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/layer/2.3/layer.js
// @require https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
// @require https://cdn.jsdelivr.net/npm/qs@6.9.4/dist/qs.min.js
// @grant GM_openInTab
// @grant GM_notification
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_listValues
// ==/UserScript==
$(function () {
'use strict';
var config = {
isCRX: false,
notificationTitle: "云班课高效助手",
icon128: "https://s1.ax1x.com/2020/05/18/Yf6pp4.png",
icon48: "https://s1.ax1x.com/2020/05/18/Yf6Kcd.png",
icon32: "https://s1.ax1x.com/2020/05/18/Yf6BBq.png",
icon16: 'https://s1.ax1x.com/2020/05/18/Yfg71e.png',
layer_css: "https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/layer.min.css",
layer_js: "https://cdnjs.cloudflare.com/ajax/libs/layer/2.3/layer.js",
jquery_js: "https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js",
layui_js: "https://cdn.jsdelivr.net/npm/layui-src@2.5.5/dist/layui.min.js",
fontawesome_css: "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.13.0/css/all.min.css",
base: "https://mst.bellamy.top:8443",
};
var openInTab;
var setVal;
var getVal;
var notification;
var delVal;
var listVals;
var qs = Qs;
if (config.isCRX) {
console.log("in CRX");
/**
* ***********
* For CRX Begin
* ***********
*
* ***********
* Override the following apis provided by TamperMonkey
* these apis can only work well in TamperMonkey Script
* but they can not work in CRX
*/
/**
* [GM_openInTab : send message to bg.js to create New Tab according to these following parameters]
* @param {[String]} forWhat [onDownload or offDownload]
* @param {[String]} _url [new tab]
* @param {[Boolean]} _option [is or not active]
*/
function GM_openInTab(_url, _option, forWhat) {
chrome.runtime.sendMessage({
createTab: forWhat,
url: _url,
option: "active" === _option,
});
}
function GM_setValue(name, value) {
}
function GM_getValue(name, defaultValue) {
}
/**
* send message to chrome API
* chrome.notifications.create(string notificationId, NotificationOptions options, function callback)
* @param {[type]} notificationDetails [description]
* @param {Function} callback [description]
*/
function GM_notification(notificationDetails, callback) {
chrome.runtime.sendMessage({
notifDetails: {
details: notificationDetails,
callbackFunc: callback
}
});
}
function GM_deleteValue(name) {
}
function GM_listValues() {
}
/**
* ***********
* For CRX End
* ***********
*/
openInTab = GM_openInTab; //GM_openInTab(url, option);
setVal = GM_setValue; // GM_setValue(name, value)
getVal = GM_getValue; // GM_getValue(name, defaultValue)
notification = GM_notification; // GM_notification(text, title, image, onclick)
delVal = GM_deleteValue; // GM_deleteValue(name)
listVals = GM_listValues; // GM_listValues()
// inject layer.css
$("<link>")
.attr({
rel: "stylesheet",
type: "text/css",
href: config.fontawesome_css
})
.appendTo("head");
} else {
console.log("in Script ");
openInTab = GM_openInTab; //GM_openInTab(url, option);
setVal = GM_setValue; // GM_setValue(name, value)
getVal = GM_getValue; // GM_getValue(name, defaultValue)
notification = GM_notification; // GM_notification(text, title, image, onclick)
delVal = GM_deleteValue; // GM_deleteValue(name)
listVals = GM_listValues; // GM_listValues()
// inject layer.css
$("<link>")
.attr({
rel: "stylesheet",
type: "text/css",
href: config.layer_css
})
.appendTo("head");
// inject layer.css
$("<link>")
.attr({
rel: "stylesheet",
type: "text/css",
href: config.fontawesome_css
})
.appendTo("head");
}
/**
* For notification function
*
* text - the text of the notification (required unless highlight is set)
* title - the notificaton title
* image - the image
* highlight - a boolean flag whether to highlight the tab that sends the notfication (required unless text is set)
* silent - a boolean flag whether to not play a sound
* timeout - the time after that the notification will be hidden (0 = disabled)
* ondone - called when the notification is closed (no matter if this was triggered by a timeout or a click) or the tab was highlighted
* onclick - called in case the user clicks the notification
*/
function getNotificationDetails(_text, _timeout, _title, _image, _highlight, _silent, _ondone, _onclick) {
let details = {
text: _text === undefined ? '' : _text,
title: _title === undefined || _title === null ? config.notificationTitle : _title,
image: _image === undefined || _image === null ? config.icon48 : _image,
highlight: _highlight === undefined || _highlight === null ? true : _highlight,
silent: _silent === undefined || _silent === null ? false : _silent,
timeout: _timeout === undefined || _timeout === null ? 6000 : _timeout,
ondone: _ondone === undefined || _ondone === null ? null : _ondone,
onclick: _onclick === undefined || _onclick === null ? null : _onclick
};
return details;
};
/**
* Determine the browser type
*/
function browserType() {
var userAgent = navigator.userAgent; //get browser userAgent string
var isOpera = userAgent.indexOf("Opera") > -1;
if (isOpera) {
return "Opera"
}; //is Opera or not
if (userAgent.indexOf("Firefox") > -1) {
return "FF";
} //is Firefox or not
if (userAgent.indexOf("Chrome") > -1) {
return "Chrome";
}
if (userAgent.indexOf("Safari") > -1) {
return "Safari";
} //is Safari or not
if (userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera) {
return "IE";
}; //is IE or not
}
/**
* sleep function
* @param numberMillis -- 要睡眠的毫秒数
*/
function sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime)
return;
}
}
/**
* Remove duplicate value in array
*/
function removeDuplicate(arr) {
let x = new Set(arr);
return [...x];
}
/**
* download resources function
*/
function download(name, href) {
var a = document.createElement("a"), //创建a标签
e = document.createEvent("MouseEvents"); //创建鼠标事件对象
e.initEvent("click", false, false); //初始化事件对象
a.href = href; //设置下载地址
a.download = name; //设置下载文件名
a.dispatchEvent(e); //给指定的元素,执行事件click事件
}
/**
* Refresh page tips
*/
function refreshPage() {
alert("操作完成,请小可爱刷新页面查看结果!!!");
}
/**
* cancel action
*/
function cancel() {
alert("已取消操作!");
}
/**
* 点击和下载前以弹窗二次确认
* modeName
* return boolean
**/
function popupComfirm(modeName) {
let conf_str = false;
conf_str = confirm("小可爱,你即将执行“" + modeName + "”操作!!!" + "\n\n" +
"根据选择资源数量的不同,会打开相应数量的页面,如果数量较多,请不要惊慌,因为这些页面会自动关闭的哦!!!" + "\n\n" +
"你是否按照上一个提示,进行了相应的操作?" + "\n\n" + "如果是,你是否要开始执行本次操作?");
return conf_str;
}
/**
* cleaning data 数据清洗 inputString -> idsArr
*/
function cleanData(inputString) {
//去除字符串中的所有空格
inputString.replace(/\s*/g, "");
//去掉首尾的 - 字符
if (inputString.charAt(0) == "-") {
if (inputString.charAt(inputString.length - 1) == "-")
inputString = inputString.substring(1, inputString.length - 1);
else
inputString = inputString.substring(1, inputString.length);
} else if (inputString.charAt(inputString.length - 1) == "-") {
inputString = inputString.substring(0, inputString.length - 1);
}
//console.log(inputString + "/" + inputString.charAt(0) +"/" + inputString.charAt(inputString.length-1));
//分割出要点击的栏号,存入数组,用于映射出对应的资源栏id
let idsArr = inputString.split("-");
//去重并排序
idsArr = removeDuplicate(idsArr).sort();
//去除超出资源栏总数的无效值
let temp = [];
for (let i = 0; i < idsArr.length; i++) {
// console.log("srcBarSum is" + srcBarSum);
if (idsArr[i] <= srcBarSum && idsArr[i] > 0) {
temp.push(idsArr[i]);
// console.log("temp is" + temp);
}
}
// console.log("idsArr is" + idsArr);
return idsArr = temp;
}
/**
* 根据模式名执行对应的批量处理操作
*
* 点击确认按钮弹出确认弹窗,
* 如果确认执行,则执行点击操作,
* 否则执行 取消操作
*/
function batchForMoreSrcBars(modeName, ids) {
if (ids.length == 0)
ids.push(".res-row-box");
let isDownloadMode = modeName == "模拟点击" ? "false" : (modeName == "批量下载" ? "true" : "其他");
if (popupComfirm(modeName)) {
try {
// console.log(chosenIDs);
let startIndex = $("#head").val();
let endIndex = $("#tail").val();
for (let id of ids) {
// console.log(thisID);
try {
batch(isDownloadMode, id, startIndex, endIndex);
} catch (e) {
console.log(id + "该栏执行异常,跳过执行");
continue;
}
}
} finally {
//点击完成,提示刷新页面
setTimeout(refreshPage, 0);
//置空栏号输入框
$(".indexNum").val("");
}
} else {
cancel();
}
}
/**
* Click or download in bulk according to
* isDownload : true -> Download Mode ; false -> Click Mode
* thisBarID : 此次要执行的资源栏 id
* startIndex : 此次资源栏中执行的开始资源编号
* endIndex : 此次资源栏中执行的结束资源编号
*
*/
function batch(isDownload, thisBarID, startIndex, endIndex) {
//let isDownloadMesg = isDownload == "false" ? "模拟点击" : "批量下载";
// 以下五个等价,实现相同功能,但写法是逐步优化
// var list = document.getElementsByClassName("res-row-open-enable");
// var list = $(".res-row-open-enable");
// var list = $(".hide-div").children();
// var list = $(".res-row-box").children(".hide-div").children();
let list = $(thisBarID).children(".hide-div").children();
let succNum = 0;
let failNum = 0;
let tempUrl;
let win;
let actualStartIndex = startIndex <= list.length && startIndex > 0 ? startIndex : (startIndex <= 0 ? 1 : list.length); //小于0则为 1 ; 大于 最大值 则为 最大值
let actualEndIndex = endIndex <= list.length && endIndex > 0 ? endIndex : (endIndex <= 0 ? 1 : list.length); //输入值超出资源总数的值,则将输入值置为总数的值
if (actualStartIndex > actualEndIndex) {
//console.log("here");
alert("小可爱😀,你的起始结束值写反了哟!");
return;
}
// console.log("actualStartIndex: " + actualStartIndex);
// console.log("actualEndIndex: " + actualEndIndex);
// list 存在并不为空
if (null == list || list.length == 0) {
console.log(thisBarID + "对应的资源栏为空");
} else {
for (let i = actualStartIndex - 1; i < actualEndIndex; i++) {
// console.log(i);
// console.log(list);
// console.log(list[i]);
try {
tempUrl = list[i].getAttribute("data-href");
if (null == tempUrl || tempUrl == "") {
console.log("资源栏:" + thisBarId + "的第 " + (i + 1) + " 条资源未获取到URL");
} else {
win = window.open(tempUrl);
if (isDownload == 'false') {
sleep(100); //睡眠,是为了确保每个资源都被正常获取
win.close();
}
succNum++;
// console.log(tempUrl);
}
} catch (e) {
console.log(e.name + ": " + e.message);
console.log("资源栏:" + thisBarId + "的第 " + (i + 1) + " 条未成功执行 ;URL : " + list[i].getAttribute("data-href"));
failNum++;
continue;
}
}
}
console.log("共检索到 " + list.length + "条; 成功执行 " + succNum + " 次! 失败 " + failNum + " 次! 操作范围:从第 " + actualStartIndex + " 条 至 第 " + actualEndIndex + " 条。");
}
/**
* click all resources in two ways according to 'isPositive'
*/
function clickAll(isPositive) {
let isPositiveMesg = isPositive == "true" ? "正序点击" : "倒序点击";
let conf_str = false;
conf_str = confirm("小可爱,你即将执行“" + isPositiveMesg +
"全部资源”操作,如果资源量较大(> 1000),耗时就会较久,打开的页面也会较多哦!不过都会自动关闭的哦!!!" + "\n\n" +
"小可爱,资源较多时,还请三思啊!!!" + "\n\n" + "你是否要执行?");
if (conf_str) {
let list = document.getElementsByClassName("res-row-open-enable");
let succNum = 0;
let failNum = 0;
let tempUrl;
let win;
if (isPositive == "true") {
for (let i = 0; i < list.length; i++) {
try {
tempUrl = list[i].getAttribute("data-href");
win = window.open(tempUrl);
sleep(100); //睡眠,是为了确保每个资源都被正常获取
win.close();
succNum++;
// console.log(tempUrl);
} catch (e) {
console.log(e.name + ": " + e.message);
console.log("该条未成功执行 ;URL : " + list[i].getAttribute("data-href"));
failNum++;
continue;
}
}
} else {
for (let i = list.length - 1; i >= 0; i--) {
try {
tempUrl = list[i].getAttribute("data-href");
win = window.open(tempUrl);
sleep(100); //睡眠,是为了确保每个资源都被正常获取
win.close();
succNum++;
// console.log(tempUrl);
} catch (e) {
console.log(e.name + ": " + e.message);
console.log("该条未成功执行 ;URL : " + list[i].getAttribute("data-href"));
failNum++;
continue;
}
}
}
console.log(isPositiveMesg + ": 共检索到 " + list.length + "条; 成功执行 " + succNum + " 次! 失败 " + failNum + " 次!");
setTimeout(refreshPage, 0);
} else {
alert("已取消操作!");
}
}
/**
* open a new tab according the url and execute callback function
*/
function newTabAlert(forWhat, url, option, callback) {
if (config.isCRX)
openInTab(url, option, forWhat);
else
openInTab(url, option);
if (typeof callback === "function") {
callback();
}
}
/******************************************
* play videos
*
*/
let playVideoConfig = {
isContinuous: false,
isPlayAll: false,
isPlayPart: false,
videoDuration: '',
};
/**********************************************
* Play all videos continuously
*/
let arr = [];
let count = 0;
let interval;
let timeout;
let intervalTime = 4000; //millisecond
let isContinuousPaly = false;
let rate = 1; // <=10
let weight = 1000 / rate;
let currentVideoIndex = 0;
let nextVideoIndex = 0; //当前第几个视频
let bufferTime = 10000; // millisecond
let maxRate = 8;
let log = '';
//将所有视频资源存入数组,以作点击使用
let a = $("div[data-mime='video']");
let tempArr = Object.keys(a);
let tpArr = tempArr.slice(0, tempArr.length - 2);
tpArr.forEach((key) => {
//console.log(key, a[key]);
arr.push(a[key]);
});
//console.log(arr.length);
// for (let a in arr) {
// console.log(a);
// }
//
playVideoConfig.videoDuration = $('.video-duration');
function onContinuousPlayFunc() {
if (playVideoConfig.isContinuous) {
layer.msg('【无效操作】 : 连播功能已开启');
return;
}
playVideoConfig.isContinuous = true;
if (typeof ($("#continuousPlay").attr("class")) != "undefined") {
let text = "连续播放已开启,无需重复开启";
//alert(text);
notification(getNotificationDetails(text), null);
//layer.msg("test");
return;
}
alert("请先关闭 【 Win10 专注助手 】 再使用,否则无法正常提示信息 \n\n 提示:在通知托盘中关闭");
// $('<div id = "continuousPlay" class="mejs__button">\
// <button type="button" aria-controls="mep_0" title="开始连续播放" aria-label="Play" tabindex="0"></button>\
// </div>\
// <div id = "stopContinuousPlay" class="mejs__button mejs__playpause-button mejs__pause">\
// <button type="button" aria-controls="mep_0" title="暂停连续播放" aria-label="Pause" tabindex="0"></button>\
// </div>\
// <div id = "continuousPlayN" class="mejs__button">\
// <button type="button" aria-controls="mep_0" title="开始连续播放(n)" aria-label="Play" tabindex="0"></button>\
// </div>\
// <div id = "stopContinuousPlayN" class="mejs__button mejs__playpause-button mejs__pause">\
// <button type="button" aria-controls="mep_0" title="暂停连续播放(n)" aria-label="Pause" tabindex="0"></button>\
// </div>\
// ').insertAfter(".mejs__fullscreen-button");
$('<div id="helper-btn" class="content-center" style="background-color:rgba(255, 255, 255, 0.5);">\
<span class="video-btn content-center"><i id="continuousPlayAll" class="fa fa-play-circle" aria-hidden="true" style="cursor:pointer"></i></span>\
<span class="video-btn content-center"><i id="stopContinuousPlayAll" class="fa fa-stop-circle" aria-hidden="true" style="cursor:pointer"></i></span>\
<span class="video-btn content-center"><i id="continuousPlayPart" class="fa fa-play" aria-hidden="true" style="cursor:pointer"></i></span>\
<span class="video-btn content-center"><i id="stopContinuousPlayPart" class="fa fa-stop" aria-hidden="true" style="cursor:pointer"></i></span>\
</div>').insertBefore("#preview-video");
//For all
$("#continuousPlayAll").click(() => {
startContinuousPlayAll();
});
$("#stopContinuousPlayAll").click(() => {
stopContinuousPlayAll();
});
//For part
$("#continuousPlayPart").click(() => {
startContinuousPlayForPart();
});
$("#stopContinuousPlayPart").click(() => {
stopContinuousPlayForPart();
});
let txt = "连续播放已开启,请到视频播放页面使用";
notification(getNotificationDetails(txt), null);
}
/**
* close continuous play
*
*/
function offContinuousPlayFunc() {
if (!playVideoConfig.isContinuous) {
layer.msg('【无效操作】 : 连播功能已关闭');
return;
}
$('#continuousplayAll, #stopContinusPlayAll, #continuousPlayPart, #stopContinusPlayPart').unbind();
$('#helper-btn').remove();
if (playVideoConfig.isPlayAll) {
stopContinuousPlayAll();
}
if (playVideoConfig.isPlayPart) {
stopContinuousPlayForPart();
}
playVideoConfig.isContinuous = false;
layer.msg('连播功能已关闭!');
}
/**
* For all
* steps that must be taken when start playing continuously
*/
function startContinuousPlayAll() {
if (playVideoConfig.isPlayPart) {
layer.msg('【无效操作】 : 正常连播进行中...');
return;
}
if (isContinuousPaly) {
layer.msg("【无效操作】:全部连播进行中...");
return;
}
if (playBySpecifiedRateAndNotify()) {
$('.video-duration').remove();
playVideoConfig.isPlayAll = true;
isContinuousPaly = true;
layer.msg('禁用进度条', function () {
clickDiv();
});
} else {
notification(getNotificationDetails("已取消本次操作!"), null);
}
}
/**
* For all
* steps that must be taken when stopping playing continuously
*/
function stopContinuousPlayAll() {
if (playVideoConfig.isPlayPart) {
stopContinuousPlayForPart();
playVideoConfig.isPlayPart = false;
}
if (!isContinuousPaly) {
layer.msg("【无效操作】:全部连播未执行");
return;
}
// console.log("llllllllll:"+$('.video-duration') );
if ($('.video-duration').length == 0) {
$(playVideoConfig.videoDuration).insertAfter('#mep_0');
}
isContinuousPaly = false;
playVideoConfig.isPlayAll = false;
//停掉当前还未执行完的 interval timeout
clearInterval(interval);
clearTimeout(timeout);
layer.msg('全部连播已关闭');
let stopContinusPlayText = "已退出连续播放模式,但保留了关闭视频即可看完功能;\n下一次连续播放从第 " + (nextVideoIndex + 1) + " 个视频开始。";
//alert(stopContinusPlayText);
notification(getNotificationDetails(stopContinusPlayText), null);
}
/**
* For part
* start playing continuously part of all the specified videos
*/
function startContinuousPlayForPart() {
if (playVideoConfig.isPlayAll) {
layer.msg('【无效操作】 : 全部连播进行中...');
return;
}
if (videoConfig.isContinuousPaly) {
layer.msg("【无效操作】:正常连播进行中...");
return;
}
playVideoConfig.isPlayPart = true;
videoConfig.isContinuousPaly = true;
play(videoConfig.currentVideoDivs);
}
//部分连播
//开始时不需要设置播放速度,
//结束时不需要提示下次播放位置
//只需开始/结束提即可
/**
* For part
* stop playing continuously part of all the specified videos
*/
function stopContinuousPlayForPart() {
if (playVideoConfig.isPlayAll) {
stopContinuousPlayAll();
playVideoConfig.isPlayAll = false;
}
if (!videoConfig.isContinuousPaly) {
layer.msg("【无效操作】:正常连播未执行");
return;
}
videoConfig.isContinuousPaly = false;
playVideoConfig.isPlayPart = false;
let video = document.querySelector('video');
let isPaused = video.paused;
if (isPaused) {
video.play();
video.pause();
} else {
video.pause();
video.play();
}
}
/**
* unlock progress bar and click this div
* return the index of layer
*/
function unlockBarAndClickDiv(div, func) {
layer.msg('解锁进度条中...', {
time: 1500,
},
function () {
let info = '未上锁';
if ($(div).attr('data-drag') == 'N') {
$(div).attr('data-drag', 'Y');
info = '已解锁!';
}
layer.msg(info, {
time: 1500
},
function () {
$(div).trigger('click');
if (typeof func === "function") {
func();
}
});
});
}
/**
* is or not a number
* @param {[type]} val [description]
* @return {Boolean} [description]
*/
function isNumber(val) {
var regPos = /^\d+(\.\d+)?$/; //非负浮点数
var regNeg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; //负浮点数
if (regPos.test(val) || regNeg.test(val)) {
return true;
} else {
return false;
}
}
/**
* play next videos according to the specified rate inputed by user and notify user
*/
function playBySpecifiedRateAndNotify() {
let inputRate = prompt("以几倍速度进行连续播放呀🧐(最高" + maxRate + "倍哦!)建议1.8倍最佳🤭");
//console.log(inputRate);
if (inputRate == null) {
return false;
}
if (!isNumber(inputRate)) {
let text = "啥❓ 你输入了啥,那是数字吗?\n 再输一次吧,别输出咯!😀";
notification(getNotificationDetails(text), null);
return false;
}
rate = inputRate <= 0 ? 1 : (inputRate > maxRate ? maxRate : inputRate);
weight = 1000 / rate;
let text = "连续播放已开始!\n将以 " + rate + " 倍速 播放 " + (arr.length - nextVideoIndex) + " 个视频。";
//console.log(text);
notification(getNotificationDetails(text), null);
return true;
}
/**
* edit those value about duration before sending ajax
*/
function send() {
$.ajaxSetup({
beforeSend: function () {
let argsData = arguments[1].data
let falseArgsData = "";
let falseVal;
for (let k in argsData) {
if (k.includes("watch_to")) {
//console.log("before: " + k + " : " + argsData[k]);
falseVal = argsData.duration;
//console.log("after: " + k + " : " + falseVal);
} else {
falseVal = argsData[k];
}
falseArgsData = falseArgsData + "&" + k + "=" + falseVal;
}
arguments[1].data = falseArgsData.substring(1, falseArgsData.length);
},
processData: false,
complete: function () {
console.log("send completed");
}
});
}
/**
* trigger the click action of the current DIV
*
*/
function clickDiv() {
currentVideoIndex = count++;
nextVideoIndex = currentVideoIndex + 1;
if (isContinuousPaly == false) {
//console.log("在播放第 " + (nextVideoIndex) + " 个视频时退出了连续播放");
return;
}
//第一次使用连续播放开启 关闭即可看完
if (currentVideoIndex == 0) {
send();
}
if (currentVideoIndex < arr.length) {
// $(arr[currentVideoIndex]).trigger("click");
unlockBarAndClickDiv(arr[currentVideoIndex], playThisVideo);
// setTimeout(function() {layer.close(index)}, videoConfig.loadingTime);
//console.log(currentVideoIndex + " : " + arr[currentVideoIndex]);
// playThisVideo();
} else {
setTimeout(() => {
clearInterval(interval);
}, 0);
//console.log("currentVideoIndex: " + currentVideoIndex);
//关掉最后一个视频
$(".close-window").trigger("click");
alert("连续播放结束, 共连续播放了 " + arr.length + " 个视频,即将刷新页面");
location.reload();
}
}
/**
* play the current video until it is over and play the next video
*/
function playThisVideo() {
if (currentVideoIndex >= arr.length) {
return;
}
let duration;
let currentTime;
setTimeout(() => {
let video = document.querySelector('video');
let duration = video.duration;
let currentTime = video.currentTime;
let isPaused = video.paused;
//console.log(isPaused ? "暂停" : "未停");
// if video has paused then play the video
if (isPaused) {
$(".mejs__replay").trigger("click");
//console.log("开始播放");
}
setTimeout(() => {
//console.log(document.querySelector('video').paused ? "暂停" : "未停");
}, 500);
video.playbackRate = rate;
//second --> millisecond
let remain = (duration - currentTime) * weight;
console.log("该视频剩余播放时长 :" + remain + " 毫秒");
//停掉上一个interval timeout
clearInterval(interval);
clearTimeout(timeout);
//is NaN
if (duration != duration || currentTime != currentTime || remain != remain) {
stopContinuousPlayAll();
notification(getNotificationDetails("执行异常,已停止本次连播。" +
"\n下一次连续播放从第 " + (nextVideoIndex + 1) + " 个视频开始。", 10000), null);
return;
}
interval = setInterval(clickDiv, remain + intervalTime);
timeout = setTimeout(() => {
//console.log("当前视频播放到:" + document.querySelector('video').currentTime);
$(".close-window").trigger("click");
//console.log("关闭第" + nextVideoIndex + "个视频");
//console.log(intervalTime + " 毫秒后播放下一个视频");
}, remain);
}, bufferTime);
}
/**
* play all the videos since this video
*/
let videoConfig = {
videoSum: 0,
currentVideoId: '',
currentVideoDivs: arr,
isContinuousPaly: false,
loadingTime: 6000,
rate: 1,
}
$("div[data-mime='video']").each(function (i, e) {
let ts = $(this);
ts.attr('id', 'vdoId_' + i);
ts.bind('click', function (event) {
/* Act on the event */
let id = videoConfig.currentVideoId = ts.attr('id');
let split = id.split('_');
let newFirstIndex = Number(split[1]);
videoConfig.currentVideoDivs = arr.slice(newFirstIndex);
//console.log(videoConfig.currentVideoDivs);
});
videoConfig.videoSum = ++i;
});
//每拿一个阻塞一次,
function play(videosArr) {
layer.msg(
"连播开始!(共" + videosArr.length + "个)", {
time: 3000
},
async function () {
let isOver = true;
for (let i = 0; isOver && i < videosArr.length; i++) {
//console.log("time:" + i);
isOver = await playOne(videosArr[i]);
}
//console.log("Done all");
videoConfig.isContinuousPaly = false;
layer.msg("连播结束!");
}
);
}
function playOne(div) {
unlockBarAndClickDiv(div);
let index = layer.load();
// if(document.querySelector('video').readyState == 4){
// layer.msg("OK");
// };
return new Promise(resolve => {
setTimeout(() => {
//close load
layer.close(index);
let video = document.querySelector('video');
let onPause = function () {
let a = video.currentTime == 0 || video.currentTime == video.duration;
let b = videoConfig.isContinuousPaly;
if (b && a) {
//视频播完会回到开头如果没有回到开头应该在结尾
resolve(true);
//console.log("连播&结束");
} else if (!b && !a) {
//如果按下暂停前关闭了连续播放 == 结束本次列表循环
resolve(false);
//console.log("play stopped");
} else if (b && !a) {
//如果还在连续播放但是按下暂停 == 暂停 ,什么也不做
//console.log("play blocked");
} else if (!b && a) {
//不再连播但播放结束
resolve(false);
//console.log("不连播&结束");
}
}
video.removeEventListener('pause', onPause, false);
// let duration = video.duration;
// let currentTime = video.currentTime;
let isPaused = video.paused;
//console.log(isPaused ? "本是暂停" : "本是播放");
// if video has paused then play the video
if (isPaused) {
$(".mejs__replay").trigger("click");
//console.log("暂停-》开始播放");
}
setTimeout(() => {
//console.log(document.querySelector('video').paused ? "依旧暂停" : "已打开播放");
}, 500);
video.playbackRate = keyboardEvent.currentSpeed;
// video.addEventListener("ended", function() {
// resolve(true);
// console.log("this over");
// });
video.addEventListener('pause', onPause);
}, videoConfig.loadingTime);
});
}
/**********************************************
* keyMap module
*/
let keyboardEvent = {
keyBindings: [],
speedStep: 0,
rewindTime: 0,
advanceTime: 0,
fastSpeed: 0,
slowerKeyCode: 0,
fasterKeyCode: 0,
rewindKeyCode: 0,
advanceKeyCode: 0,
resetKeyCode: 0,
fasterKeyCode: 0,
currentSpeed: 1.0,
functionKey: {
keyMap: 0,
playAll: 0,
stopPlayAll: 0,
playPart: 0,
stopPlayPart: 0,
onContinuousPlayFunc: 0,
offContinuousPlayFunc: 0,
showTips: 0
},
keyMapInfo: ``,
keyMapDetail: []
};
// for video
keyboardEvent.keyBindings.push({
action: "slower",
key: Number(keyboardEvent.slowerKeyCode) || 83,
value: Number(keyboardEvent.speedStep) || 0.1,
force: false,
predefined: true
}); // default S
keyboardEvent.keyBindings.push({
action: "faster",
key: Number(keyboardEvent.fasterKeyCode) || 87,
value: Number(keyboardEvent.speedStep) || 0.1,
force: false,
predefined: true
}); // default: W
keyboardEvent.keyBindings.push({
action: "rewind",
key: Number(keyboardEvent.rewindKeyCode) || 65,
value: Number(keyboardEvent.rewindTime) || 5,
force: false,
predefined: true
}); // default: A
keyboardEvent.keyBindings.push({
action: "advance",
key: Number(keyboardEvent.advanceKeyCode) || 68,
value: Number(keyboardEvent.advanceTime) || 5,
force: false,
predefined: true
}); // default: D
keyboardEvent.keyBindings.push({
action: "reset",
key: Number(keyboardEvent.resetKeyCode) || 82,
value: 1.0,
force: false,
predefined: true
}); // default: R
keyboardEvent.keyBindings.push({
action: "fast",
key: Number(keyboardEvent.fastKeyCode) || 71,
value: Number(keyboardEvent.fastSpeed) || 1.8,
force: false,
predefined: true
}); // default: G
// for functions
keyboardEvent.keyBindings.push({
action: 'keyMap',
key: Number(keyboardEvent.functionKey.keyMap) || 77
}); // M
keyboardEvent.keyBindings.push({
action: 'playAll',
key: Number(keyboardEvent.functionKey.playAll) || 90
}); // Z
keyboardEvent.keyBindings.push({
action: 'stopPlayAll',
key: Number(keyboardEvent.functionKey.stopPlayAll) || 88
}); // X
keyboardEvent.keyBindings.push({
action: 'playPart',
key: Number(keyboardEvent.functionKey.playPart) || 67
}); // C
keyboardEvent.keyBindings.push({
action: 'stopPlayPart',
key: Number(keyboardEvent.functionKey.stopPlayPart) || 86
}); // V
keyboardEvent.keyBindings.push({
action: 'onContinuousPlayFunc',
key: Number(keyboardEvent.functionKey.onContinuousPlayFunc) || 66
}); // B
keyboardEvent.keyBindings.push({
action: 'offContinuousPlayFunc',
key: Number(keyboardEvent.functionKey.offContinuousPlayFunc) || 78
}); // N
keyboardEvent.keyBindings.push({
action: 'showTips',
key: Number(keyboardEvent.functionKey.showTips) || 84
}); // T
/**
* get the content of the action specified
* the action bound to some event
* @return json
*/
function getKeyBindingsByAction(action) {
let item = keyboardEvent.keyBindings.find(item => item.action === action);
return item;
}
/**
* get the value by specified action and keyname
* @param {string} action [the action bound to some event]
* @param {string} keyname
* @return {[type]}
*/
function getValueByActionAndKeyname(action, keyname) {
return getKeyBindingsByAction(action)[keyname];
}
/**
* [get all values by specified keyname ]
* @return {[array]} [all values]
*/
function getAllValuesByKeyname(keyname) {
let all = [];
let arr = keyboardEvent.keyBindings;
for (let i in arr) {
let x = arr[i];
/**
* access value by variable key
* x.keyname ==> x[keyname]
*/
all.push(x[keyname]);
}
// console.log('all:'+ all);
return all;
}
function changeKeycode(keycodeArr, toLowercase) {
let arr = [];
for (let i in keycodeArr) {
// if (toLowercase) {
// arr.push(keycodeArr[i] + 32);
// }else{
// arr.push(keycodeArr[i] - 32);
// }
toLowercase == true ? arr.push(keycodeArr[i] + 32) : arr.push(keycodeArr[i] - 32);
}
//console.log(keycodeArr + '****' + arr);
return arr;
}
/**
* initialize keyboardEvent: keyMapInfo keyMapDetail
* @type {[type]}
*/
keyboardEvent.keyMapDetail = [
['强制关闭Chrome', 'Alt + F4'],
['查看快捷键', 'shift + m'],
['弹出提示', 'shift + t'],
[`视频加速 (+${getKeyBindingsByAction('faster').value})`, 'W'],
[`视频减速 (-${getKeyBindingsByAction('slower').value})`, 'S'],
[`视频快退 ${getKeyBindingsByAction('rewind').value}s`, 'A'],
[`视频快进 ${getKeyBindingsByAction('advance').value}s`, 'D'],
[`最佳倍速 (${getKeyBindingsByAction('fast').value})`, 'G'],
[`重置倍速 (${getKeyBindingsByAction('reset').value})`, 'R'],
['开启连播', 'shift + b'],
['关闭连播', 'shift + n'],
['开始正常连播', 'shift + c'],
['结束正常连播', 'shift + v'],
['开始全部连播', 'shift + z'],
['结束全部连播', 'shift + x']
];
//获取 快捷键列表
function getKeyMapView() {
let viewArr = keyboardEvent.keyMapDetail.map((item) => {
return `<p class="content-center"><span class="keyMap-name"> ${item[0]} </span> <span class="keyMap-value"> ${item[1]} </span></p>`
});
return viewArr.join(' ');
}
keyboardEvent.keyMapInfo = `
<div id="keyMapInfo">
<p class="content-center keyMap-head"><span class="keyMap-name">功能</span><span class="keyMap-value">快捷键</span></p>
<hr>
${getKeyMapView()}
</div>
`;
/**
* bind keyboard eventListener to document
*/
let lastTimeStamp = 0;
let isSameKey = false;
let lastKeyCode = 0;
let recent2KeysInterval = 0;
let requiredInterval = 200;
/**
* 如果连续两次操作同一个快捷键的时间间隔小于要求的时间间隔,则不执行
*/
function isFrequent(interval, requiredInterval){
if (isSameKey && (interval < requiredInterval) ) {
layer.msg("操作过于频繁");
return;
}
}
$(document).bind('keypress', function (event) {
/* 禁止频繁操作 */
let curTimeStamp = event.timeStamp;
recent2KeysInterval = curTimeStamp - lastTimeStamp;
lastTimeStamp = curTimeStamp;
//此处写法会导致对所有keydown生效
// if (recent2KeysInterval < 200) {
// layer.msg("操作过于频繁");
// return;
// }
/* Act on the event */
let keyCode = event.keyCode;
let altKey = event.altKey;
let ctrlKey = event.ctrlKey;
let shiftKey = event.shiftKey;
//console.log("keyCode:" + keyCode);
/* 记录最近两次按下是否为同一个 key */
isSameKey = lastKeyCode == keyCode ? true : false;
lastKeyCode = keyCode;
let lowercase = changeKeycode(getAllValuesByKeyname('key').slice(0, 6), true);
// console.log('[119, 115, 97, 100, 114, 103]:' + lowercase);
let funcKeyLowercase = changeKeycode(getAllValuesByKeyname('key').slice(6), true);
// console.log("[109, 122, 120, 99, 118, 98, 110]:" + funcKeyLowercase);
let funcKeyUppercase = getAllValuesByKeyname('key').slice(6);
// console.log("[77, 90, 88, 67, 86, 66, 78]:" + funcKeyUppercase);
let playVdoFuncKeyLowercase = getAllValuesByKeyname('key').slice(7, 11);
// shift + lowercase => uppercase 小写键盘
let shiftAndLowercase = shiftKey && ((funcKeyUppercase.find(item => item === keyCode) === undefined ? false : true));
// shift + uppercase => lowercase 大写键盘
let shiftAndUppercase = shiftKey && ((funcKeyLowercase.find(item => item === keyCode) === undefined ? false : true));
let shiftAndPlayVdoLowercase = shiftKey && ((playVdoFuncKeyLowercase.find(item => item === keyCode) === undefined ? false : true));
if (!document.querySelector('video').paused) {
if (lowercase.find(item => item === keyCode)) {
//console.log("is pause:"+ document.querySelector('video').paused);
layer.msg('请打开大写键盘 以使用 【视频控件】');
return;
}
if (shiftAndUppercase) {
layer.msg('请关闭大写键盘 以使用完整的快捷键功能');
return;
}
if (shiftAndPlayVdoLowercase) {
if (!playVideoConfig.isContinuous) {
layer.msg('请先开启连播功能');
return;
}
}
} else if (document.querySelector('video').paused) {
if (shiftAndUppercase) {
layer.msg('请关闭大写键盘!以使用完整的快捷键功能');
return;
}
//四个连播功能(ZXCV)在没有开启连播时,提醒开启连播功能
if (shiftAndPlayVdoLowercase) {
if (!playVideoConfig.isContinuous) {
layer.msg('请先开启连播功能');
return;
}
}
if (!shiftAndLowercase) {
return;
}
}
let item = keyboardEvent.keyBindings.find(item => item.key === keyCode);
if (item) {
let video = document.querySelector('video');
doAction(item, video);
}
});
/**
* [doAction description]
* @param {[type]} item [that event triggered]
* @param {[type]} video [description]
*/
function doAction(item, video) {
//避免频繁的快捷键操作(只对存在的快捷键有效)
//如果连续两次操作同一个快捷键的时间间隔小于要求的时间间隔,则操作无效
if (isSameKey && (recent2KeysInterval < requiredInterval) ) {
layer.msg("操作过于频繁");
return;
}
let action = item.action;
let value = item.value;
let num = (video.playbackRate).toFixed(1);
/**
* send a record ( special Keys )
*/
if (keyboardEventMap.has(action)) {
if (
!(specialKeyboardEventMap.has(action)
&& isSameKey
&& recent2KeysInterval < statConfig.specialKeysInterval)
) {
// console.log('not special keys')
// console.log(specialKeyboardEventMap.has(action));
// console.log(isSameKey);
// console.log(recent2KeysInterval);
record(keyboardEventMap.get(action))
}
}
if (action == 'slower') {
video.playbackRate -= value;
num = (video.playbackRate).toFixed(1);
keyboardEvent.currentSpeed = num;
layer.msg(num + " 倍");
} else if (action == 'faster') {
video.playbackRate += value;
num = (video.playbackRate).toFixed(1);
keyboardEvent.currentSpeed = num;
layer.msg(num + " 倍");
} else if (action == 'rewind') {
video.currentTime -= value;
layer.msg("- " + value + 's');
return;
} else if (action == 'advance') {
video.currentTime += value;
layer.msg("+ " + value + 's');
return;
} else if (action == 'reset') {
video.playbackRate = value;
num = (video.playbackRate).toFixed(1);
keyboardEvent.currentSpeed = num;
layer.msg(num + " 倍");
} else if (action == 'fast') {
video.playbackRate = value;
num = (video.playbackRate).toFixed(1);
keyboardEvent.currentSpeed = num;
layer.msg(num + " 倍");
} else if (action == 'keyMap') {
let i = layer.alert(
keyboardEvent.keyMapInfo, {
//icon: 1
anim: 2
},
function (index) {
//layer.msg('操作成功!');
layer.close(index);
});
layer.title('Key Map', i);
return;
} else if (action == 'playAll') {
$("#continuousPlayAll").trigger('click');
return;
} else if (action == 'stopPlayAll') {
$("#stopContinuousPlayAll").trigger('click');
return;
} else if (action == 'playPart') {
$("#continuousPlayPart").trigger('click');
return;
} else if (action == 'stopPlayPart') {
$("#stopContinuousPlayPart").trigger('click');
return;
} else if (action == 'onContinuousPlayFunc') {
onContinuousPlayFunc();
return;
} else if (action == 'offContinuousPlayFunc') {
offContinuousPlayFunc();
return;
} else if (action == 'showTips') {
showTips();
return;
}
}
/************************************
* tips module
*/
let tipsConfig = {
params: {
tipsMore: true,
tips: 1,
time: 6000
},
};
function showTips() {
layer.tips('全部连播', '#continuousPlayAll', tipsConfig.params);
layer.tips('终止全部连播', '#stopContinuousPlayAll', tipsConfig.params);
layer.tips('正常连播', '#continuousPlayPart', tipsConfig.params);
layer.tips('终止正常连播', '#stopContinuousPlayPart', tipsConfig.params);
}
/**********************************
* statistics
*/
var meta = '<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"/>';
$("head").prepend(meta);
const clickEventMap = new Map([
['mode-click', 1],
['mode-download', 2],
['refresh', 3],
['reset', 4],
['choose', 5],
['confirm', 7],
['downloadSrc', 8],
['download-res-btn', 9],
['forward', 10],
['reverse', 11],
['continuousPlayAll', 26],
['stopContinuousPlayAll', 27],
['continuousPlayPart', 28],
['stopContinuousPlayPart', 29],
]);
const keyboardEventMap = new Map([
['keyMap', 12],
['showTips', 13],
['faster', 14],
['slower', 15],
['rewind', 16],
['advance', 17],
['fast', 18],
['reset', 19],
['onContinuousPlayFunc', 20],
['offContinuousPlayFunc', 21],
['playPart', 22],
['stopPlayPart', 23],
['playAll', 24],
['stopPlayAll', 25]
]);
// ?s 内的操作记为 1 次 有效记录
const specialKeyboardEventMap = new Map([
['faster', 14],
['slower', 15],
['rewind', 16],
['advance', 17],
]);
let statConfig = {
recordURL: config.base + '/hits/saveOrUpdateUsePostWithoutCORS',
//? s 内记 1
specialKeysInterval: 5000
}
let record = (fcId) => {
let params = {
htFcId: fcId
}
axios({
method: 'POST',
url: statConfig.recordURL,
data: qs.stringify(params),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then((response) => {
//succ
console.log("succ")
}).catch((error) => {
//err
console.log("err")
});
}
window.addEventListener("click", (event) => {
let id = event.target.id;
if (clickEventMap.has(id)) {
record(clickEventMap.get(id))
}
});
$(document).on('click','#forward, #reverse, #download-res-btn, .download-res-button ', (event) => {
let id = event.target.id;
let classNames = event.target.className;
if( classNames.includes('download-res-button')){
id = 'download-res-btn';
}
if (clickEventMap.has(id)) {
record(clickEventMap.get(id))
}
});
/**
* MosoteachHelper CSS
*/
const styleTag = `
<style>
.helper-btn{
border:1px solid #aaa;
border-radius:25px;
width:10%;
color:#fff;
font-weight:1000;
box-shadow:darkgrey 3px 3px 7px 2px;
cursor:pointer;
transition: .2s;
}
.helper-btn-a:hover{
// opacity: 0.6; //透明度
// background-color: #4d79ff !important;
background-color: rgba(0, 151, 179,1) !important;
box-shadow: darkgrey 2px 2px 5px 1px !important;
}
.helper-btn-b:hover{
background-color:rgba(204, 0, 0,1) !important;
}
.helper-btn:active{
background-color:#002b80 !important;
border:3px solid #eee !important;
box-shadow: darkgrey 1px 1px 2px 1px !important;
}
#refresh{
float:right;
background-color:rgba(204, 0, 0,0.6);
}
#reset{
float:right;
background-color:rgba(204, 0, 0,0.6);
}
#mode-click{ background:rgba(0, 151, 179,0.7);}
#mode-download{ background:rgba(0, 151, 179,0.7);}
#confirm{ background:rgba(0, 151, 179,0.7);}
#downloadSrc{ background:rgba(0, 151, 179,0.7);}
#choose{ background:rgba(204, 0, 0,0.6);}
//#refresh{ background:rgba(0, 151, 179,0.7);}
.content-center{
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
/*垂直居中*/
-webkit-box-align: center;
-moz-box-align: center;
-ms-flex-align: center;
-webkit-align-items: center;
justify-content: center;
align-items: center;
justify-content: center;
}
.video-btn{
color:white;
font-size:20px;
width:20%;
height: 34px;
}
#keyMapInfo{
width:300px;
}
.keyMap-head{
font-size: 16px;
font-weight: 700;
}
.keyMap-name{
width: 50%;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.keyMap-value{
width: 50%;
font-size: 16px;
font-weight: 700;
color: orange;
}
</style>`;
$(styleTag).appendTo('head');
//为每个资源添加下载按钮
$(".res-row-open-enable").each(function () {
if ($(this).find(".download-res-button").length > 0) return; //如果已经存在下载按钮(例如mp3),则不再添加
$(this).find("ul").html('<li id="download-res-btn" class="download-ress download-res-button">下载</li>' + $(this).find("ul").html());
// $(this).find("ul").html('<li id="forward">正序点击</li>' + $(this).find("ul").html());
// $(this).find("ul").html('<li id="reverse">倒序点击</li>' + $(this).find("ul").html());
});
//单个资源下载
$(document).on('click', '#download-res-btn', function () {
var resHref = $(this).parents(".res-row-open-enable").attr('data-href');
window.open(resHref);
});
$('<div id="functionAreaTitle" style="padding:0 20px">\
<div class="clear20"></div>\
<HR style="FILTER: alpha(opacity=100,finishopacity=0,style=3)" width="100%" color=#0BD SIZE=4>\
<div class="clear10"></div>\
<div class="res-row-title">\
<span style="color: #0BD;font-weight:600; font-size:16px"> 功能区 </span>\
<span > Powered by </span>\
<span ><a href="https://greasyfork.org/zh-CN/scripts/390978-%E4%BA%91%E7%8F%AD%E8%AF%BE%E9%AB%98%E6%95%88%E5%8A%A9%E6%89%8B">云班课高效助手 </a></span>\
<span style="color: orange;font-weight:500; font-size:14px"> 查看快捷键 :shift + m </span>\
<i class="slidedown-button manual-order-hide-part icon-angle-down" data-sort="1001"></i>\
</div>\
</div>\
<div class="clear20"></div>\
<!-- helper area Start -->\
<div id="functionAreaContent" class="hide-div" data-status="N" data-sort="1001" style="display: none;">\
<div id="helper" style="padding:0 40px;">\
<div class="res-row-title" >\
<span class="res-group-name">当前模式: </span>\
<span id="modeName" style="color: #0BD;font-weight:600">未选择 </span>\
<span class="span-display" style="color: red"> | ( 选择模式后,请按照提示操作,否则会出错;“模拟点击/下载”执行完毕后需刷新页面,数据才会更新。)</span>\
<i class="icon-angle-down slidedown-button manual-order-hide-part" data-sort="997"></i>\
</div>\
<div class="hide-div" data-status="N" data-sort="997" style="display: none;">\
<form class="appendTxt res-row" style="padding:20px 20px 0px 20px ; !important">\
<input id="mode-click" class="helper-btn helper-btn-a" type="button" value="模拟点击">\
<input id="mode-download" class="helper-btn helper-btn-a" type="button" value="批量下载">\
<input id="reset" class="helper-btn helper-btn-a helper-btn-b" type="button" value="重置">\
<input id="refresh" class="helper-btn helper-btn-a helper-btn-b" type="button" value="刷新页面">\
</form>\
</div>\
<div id="module-3">\
<div class="clear30"></div>\
<div class="res-row-title" >\
<span class="res-group-name" >已选栏号:</span>\
<span id="barID" style="color: #0BD;font-weight:600"> 全选 </span>\
<span class="span-display" style="color: #0BD" > | (范围: 最大值为资源栏总数 / 不填写 则视为全选)</span>\
<span class="span-display" style="color: red">(注意:资源栏号是从资源区里第一栏开始)</span>\
<i class="icon-angle-down slidedown-button manual-order-hide-part" data-sort="1000"></i>\
</div>\
<div class="hide-div" data-status="N" data-sort="1000" style="display: none;">\
<form class="appendTxt res-row" style="padding:20px 20px 0px 20px ; !important">\
<input id="bar_index" placeholder="选择栏号 [ if (value < 1) --> 1 ; if (value > max) --> max ] 选择多栏语法: 3-2-4-6 " \
onkeyup="this.value=this.value.replace(/[^\\d][-]/g,\'\')" onafterpaste="this.value=this.value.replace(/[^\\d][-]/g,\'\')" style="border:1px solid #0BD; border-radius:8px;width:86%"> \
<input id="choose" class="helper-btn helper-btn-a helper-btn-b" type="button" value="重置">\
</form>\
</div>\
</div>\
<div id="module-1">\
<div class="clear30"></div>\
<div class="res-row-title" >\
<span class="res-group-name" >模拟批量点击/下载</span>\
<span class="span-display" style="color: #0BD" >(范围:以资源总数值作为范围最大值)</span>\
<span class="span-display" style="color: red">( 点击对应按钮,将打开较多页面,请耐心等待其自动关闭。可在“控制台”里查看运行日志)</span>\
<i class="icon-angle-down slidedown-button manual-order-hide-part" data-sort="998"></i>\
</div>\
<div class="hide-div" data-status="N" data-sort="998" style="display: none;">\
<form class="appendTxt res-row" style="padding:20px 20px 0px 20px ; !important">\
<input id="head" class="indexNum" placeholder="起始位置 [ if (value < 1) --> 1 ; if (value > max) --> max ]" style="border:1px solid #0BD; border-radius:8px;width:42%" > \
<input id="tail" class="indexNum" placeholder="结束位置 [ if (value < 1) --> 1 ; if (value > max) --> max ]" style="border:1px solid #0BD; border-radius:8px;width:42%"> \
<input id="confirm" class="helper-btn helper-btn-a" type="button" value="模拟点击">\
<input id="downloadSrc" class="helper-btn helper-btn-a" type="button" value="批量下载">\
</form>\
</div>\
</div>\
<div id="module-2">\
<div class="clear30"></div>\
<div class="res-row-title" >\
<span class="res-group-name" >模拟全部点击(耗时较长)</span>\
<span class="span-display" style="color: #0BD" >(范围:所有资源)</span>\
<span class="span-display" style="color: red">( 点击后,将会自动打开较多页面,请耐心等待其自动关闭。可在“控制台(F12 -> console)”里查看运行日志)</span>\
<i class="icon-angle-down slidedown-button manual-order-hide-part" data-sort="999"></i>\
</div>\
<div class="hide-div" data-status="N" data-sort="999" style="display: none;">\
<div class="res-row drag-res-row" style="height:37px !important">\
<div class="operation manual-order-hide-part" style="float:left;!important">\
<ul style="margin-top:0px;"><li id="reverse">倒序点击</li><li id="forward">正序点击</li>\
<div class="clear"></div>\
</ul>\
</div>\
</div>\
</div>\
</div>\
<div id="module-4">\
<div class="clear30"></div>\
<div class="res-row-title" >\
<span class="res-group-name" >功能测试模块 </span>\
<span style="color: red"><a href = "https://greasyfork.org/en/scripts/390978-%E4%BA%91%E7%8F%AD%E8%AF%BE%E9%AB%98%E6%95%88%E5%8A%A9%E6%89%8B/feedback"> 点此反馈 (维护不易,还请好评 🙇 )</a></span>\
<i class="icon-angle-down slidedown-button manual-order-hide-part" data-sort="1002"></i>\
</div>\
<div class="hide-div" data-status="N" data-sort="1002" style="display: none;">\
<div class="res-row drag-res-row" style="height:37px !important">\
<div class="operation manual-order-hide-part" style="float:left;!important">\
<ul style="margin-top:0px;">\
<li id ="continuousPlayMode">视频连续播放控件(按钮在视频界面)</li>\
<li > 快捷键系统( shift + m )</li>\
<div class="clear"></div>\
</ul>\
</div>\
</div>\
</div>\
</div>\
</div>\
</div>\
<!-- helper area End -->\
<div id="sourceTitle" style="padding:0 20px">\
<div class="clear10"></div>\
<HR style="FILTER: alpha(opacity=100,finishopacity=0,style=3)" width="100%" color=#0BD SIZE=4>\
<div class="clear10"></div>\
<div class="res-row-title">\
<span style="color: #0BD;font-weight:600; font-size:16px"> 资源区 </span>\
</div>\
</div>\
').insertAfter("#res-view-way");
// 初始化
$("#module-1,#module-2").css("display", "none");
$("#confirm, #downloadSrc, #mode-click, #mode-download").css("display", "inline");
// change mode
$(document).on('click', '#mode-click', function () {
$("#module-1, #module-2").css("display", "block");
// 等价于
// document.getElementById("module-1").style.display="block";
// document.getElementById("module-2").style.display="block";
// document.getElementById('confirm').style.display = document.getElementById('confirm').style.display=="inline"?"inline":"none";
$("#downloadSrc, #mode-download").css("display", "none");
// $("#mode-click").css({"background-color":"#0BD","color":"#fff"});
$("#modeName").text("模拟点击");
if (browserType() == "Chrome") {
newTabAlert("onDownload", "chrome://settings/downloads", 'active', function () {
alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" +
"(识别到您使用的是Chrome浏览器)" + "\n\n" +
" 已自动为你打开浏览器【设置】页面" + "\n" +
" 【提醒】:如果没有结果可在搜索框中搜索【保存位置】" + "\n" +
" 【 打开 】 “下载前询问每个文件的保存位置” 右侧按钮");
});
} else {
alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" +
"(以下只是 chrome 浏览器操作步骤)" + "\n" +
" 1. 新建 Tab 页\n" + " -->\n" +
" 2. 地址栏输入: chrome://settings/?search=downloads\n" + " -->\n" +
" 3. 打开 “下载前询问每个文件的保存位置” 右侧按钮");
}
});
$(document).on('click', '#mode-download', function () {
document.getElementById("module-1").style.display = "block";
$("#module-2, #confirm, #mode-click").css("display", "none");
// $("#mode-download").css({"background-color":"#0BD","color":"#fff"});
$("#modeName").text("批量下载");
if (browserType() == "Chrome") {
newTabAlert("offDownload", "chrome://settings/downloads", 'active', function () {
alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" +
"(识别到您使用的是Chrome浏览器)" + "\n\n" +
" 已自动为你打开浏览器【设置】页面" + "\n" +
" 【提醒】:如果没有结果可在搜索框中搜索【保存位置】" + "\n" +
" 【 关闭 】 “下载前询问每个文件的保存位置” 右侧按钮")
});
} else {
alert("操作提醒:\n" + "务必操作,否则请不要向下执行任何操作!!!\n" + "\n" +
"(以下只是 chrome 浏览器操作步骤)" + "\n" +
" 1. 新建 Tab 页\n" + " -->\n" +
" 2. 地址栏输入:chrome://settings/?search=downloads\n" + " -->\n" +
" 3. 关闭 “下载前询问每个文件的保存位置” 右侧按钮");
}
});
$(document).on('click', '#reset', function () {
$("#module-1,#module-2").css("display", "none");
$("#confirm, #downloadSrc, #mode-click, #mode-download").css("display", "inline");
// $("#mode-download, #mode-click").css({"background-color":"#fff","color":"#000"});
$("#modeName").text("未选择");
});
// 刷新
$(document).on('click', '#refresh', function () {
layer.msg('即将刷新...', {
time: 2500 //如果不配置,默认是3秒
}, function(){
location.reload()
});
})
//资源栏总数
var srcBarSum = 0;
// 给分栏添加 id 易于按栏操作
$(".res-row-box").each(function (i, e) {
$(this).attr('id', 'id_' + i);
srcBarSum = i + 1;
});
//存储所有被选择的资源栏 id
var chosenIDs = [];
$(document).on('click', '#choose', function () {
//获取点击时按钮值
var val = $("#choose").val();
//接受用户输入的id 字符串
let inputString = $("#bar_index").val().trim();
//inputString经过清洗后得到的有效资源栏编号
let idsArr = cleanData(inputString);
if (val == "确认选择") {
/**
* 用户修改要选择的资源栏点击确认后
* 根据有效资源栏编号生成对应资源栏id存入数组备用
* 并显示被选择的所有有效资源栏
*/
//无输入内容,选择全部栏
if (idsArr.length == 0) {
chosenIDs.push(".res-row-box");
} else {
//有输入内容,转化成对应的id,放入数组备用
for (let id of idsArr) {
chosenIDs.push("#id_" + (id - 1));
}
}
//test
// console.log(idsArr);
//var barID = $("#bar_index").val();
let barID_str = idsArr.length == 0 ? "全选" : idsArr;
//var barID_str = (barID > 0 && barID < 21) ? barID : "全选";
alert("小可爱,你已将要操作的资源栏修改为: " + barID_str);
$("#barID").text(barID_str);
$("#choose").val("重置");
$("#choose").css('background-color', 'rgba(204, 0, 0,0.6)');
} else {
/**
* 用户重置资源栏输入框
* 置空输入框 和 存储的所有资源栏id
* 被选择的资源栏设为全选
*/
$("#bar_index").val("");
chosenIDs = [];
$("#choose").val("确认选择");
$("#choose").click();
}
});
// reset bar_index
$('#bar_index').bind("input propertychange", function (event) {
$("#choose").val("确认选择");
$("#choose").css('background-color', 'rgba(0, 151, 179,0.7)');
});
/**
* Main body
*
*/
/**
* 根据指定的所有资源栏id,进行模拟点击
*/
$(document).on('click', '#confirm', function () {
batchForMoreSrcBars("模拟点击", chosenIDs)
});
/**
* 根据指定的所有资源栏id,进行批量下载
*
*/
$(document).on('click', '#downloadSrc', function () {
batchForMoreSrcBars("批量下载", chosenIDs)
});
/**
* 模拟正序点击全部资源
*
*/
$(document).on('click', '#forward', function () {
clickAll("true")
});
/**
* 模拟倒序点击全部资源
*
*/
$(document).on('click', '#reverse', function () {
clickAll("false")
});
/**
* Play videos continuously
*/
$(document).on('click', '#continuousPlayMode', () => {
continuousPlay()
})
});