Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(zjooc): 新增 在浙学 刷课的支持 #52

Open
wants to merge 1 commit into
base: 3.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions packages/core/src/components/zjooc/StudySettingPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { defineComponent, ref } from 'vue';

import { switchPlaybackRate } from '../../script/zjooc/study';
import { useContext, useSettings } from '../../store';
import { Tooltip } from '../Tooltip';

export const StudySettingPanel = defineComponent({
setup () {
const settings = useSettings().zjooc.video;
const ctx = useContext();

// 切换倍速防抖
const switching = ref(false);

return () => (
<div class="ocs-setting-panel">
<div class="ocs-setting-items">
<label>视频倍速 </label>
<div>
<Tooltip title="最高4倍速">
<input type="range"
step="0.25"
max="4"
min="0.5"
value={settings.playbackRate}
disabled={switching.value}
onChange={async (e:any) => {
switching.value = true;
settings.playbackRate = e.target.valueAsNumber;
await switchPlaybackRate(settings.playbackRate);
switching.value = false;
}}></input>
</Tooltip>
<span>{settings.playbackRate}x</span>
</div>

<label>音量调节</label>
<div>
<input
type="range"
min="0"
max="1"
step="0.05"
value={settings.volume}
onInput={(e: any) => {
settings.volume = e.target.valueAsNumber;
if (ctx.common.currentMedia) ctx.common.currentMedia.volume = e.target.valueAsNumber;
}}
></input>
<span> {Math.round(settings.volume * 100)}% </span>
</div>

<label>复习模式</label>
<div>
<Tooltip title="将播放过的视频再播放一遍。">
<input
class="input-switch"
type="checkbox"
checked={settings.restudy}
onChange={(e: any) => (settings.restudy = e.target.checked)}
></input>
</Tooltip>
</div>
</div>
</div>
);
}
});
4 changes: 2 additions & 2 deletions packages/core/src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface OCSStore {
zhs: {
/** 是否正在识别文字 */
isRecognizing: boolean
}

},
zjooc: {}
}
}
3 changes: 2 additions & 1 deletion packages/core/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CommonScript } from './script/common';
import { CXScript } from './script/cx';
import { ICVEScript } from './script/icve';
import { ZHSScript } from './script/zhs';
import { ZJOOCScript } from './script/zjooc';
import { app, loaded, panel, start } from './start';
import { getStore } from './store';

Expand All @@ -16,4 +17,4 @@ export { getStore, start, app, panel, loaded, message };
export const VERSION = process.env._VERSION_;

/** 默认脚本列表 */
export const definedScripts = [CommonScript, CXScript, ZHSScript, ICVEScript];
export const definedScripts = [CommonScript, CXScript, ZHSScript, ICVEScript, ZJOOCScript];
42 changes: 42 additions & 0 deletions packages/core/src/script/zjooc/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// import { createNote, createSearchResultPanel, createTerminalPanel } from '../../components';
import { createNote, createTerminalPanel } from '../../components';
import { StudySettingPanel } from '../../components/zjooc/StudySettingPanel';
import { defineScript } from '../../core/define.script';
// import { useContext, useSettings } from '../../store';
import { sleep } from '../../core/utils';
import { logger } from '../../logger';
import { study } from './study';

export const ZJOOCScript = defineScript({
name: '在浙学',
routes: [
{
name: '课程脚本',
url: 'www.zjooc.cn/ucenter/student/course/study/*/plan/detail/*',
async onload() {
logger('info', '5s后开始在浙学课程学习');
await sleep(5000);
await study();
}
}
],
panels: [
{
name: '在浙学助手',
url: 'https://www.zjooc.cn/ucenter/student/course/build/list',
el: () => createNote('请点击任意的课程进入。')
},
{
name: '在浙学课程学习助手',
url: 'https://www.zjooc.cn/ucenter/student/course/study/*/plan',
el: () => createNote('进入 学习设置面板 可以调整课程学习设置'),
children: [
{
name: '学习设置',
el: () => StudySettingPanel
},
createTerminalPanel()
]
}
]
});
179 changes: 179 additions & 0 deletions packages/core/src/script/zjooc/study.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { domSearch, domSearchAll, sleep } from '../../core/utils';
import { logger } from '../../logger';
import { useContext, useSettings } from '../../store';

const stop = false;

/**
* zjooc 视频学习
*/
export async function study() {
const { restudy } = useSettings().zjooc.video;
logger('info', 'zjooc 学习任务开始');
// 查找任务
let list: HTMLLIElement[] = Array.from(document.querySelectorAll('#pane-Chapter li.el-menu-item[role=menuitem]'));

if (list.length === 0) {
logger('warn', '课程章数为 0 !');
} else {
logger('info', '课程章数', list.length);

for (let index = 0; index < list.length; index++) {
const chapter = list[index];
if ((chapter as Element).classList.contains('is-active')) {
list = list.slice(index);
break;
}
}
/** 遍历任务进行学习 */
for (const item of list) {
try {
if (stop) {
break;
} else {
logger(
'debug',
`即将开始学习 -- ${item.innerText}`
);
item.click();
await sleep(1000);
let lessonList: HTMLLIElement[] = Array.from(document.querySelectorAll('div[role=tab] > span'));
if (lessonList.length === 0) {
logger('warn', '本章小节数本章小节数为 0 !');
} else {
logger('info', '本章小节数', lessonList.length);
for (let index = 0; index < lessonList.length; index++) {
const lesson = lessonList[index];
if ((lesson as Element).classList.contains('is-active')) {
lessonList = lessonList.slice(index);
break;
}
}
for (const lesson of lessonList) {
if (!(lesson.childNodes[0] as Element).classList.contains('complete') || restudy) {
logger('info', '开始学习 -- ', lesson.innerText);
lesson.click();
await sleep(5000);
if ((lesson.childNodes[0] as Element).classList.contains('icon-shipin')) {
logger('debug', '开始播放视频');
await watch();
} else if (document.querySelector('iframe')) {
logger('debug', '开始阅读文档');
await read();
}
} else {
logger('info', '跳过 -- ', lesson.innerText);
}
}
await sleep(5000);
}
}
} catch (e) {
logger('error', e);
}
}
}

logger('info', 'zjooc 学习任务结束');
}

/**
* 阅读
* @returns
*/
export async function read() {
return new Promise<void>((resolve, reject) => {
try {
const btn = document.querySelector('.contain-bottom button.el-button.el-button--default') as HTMLButtonElement;

Promise.resolve(async () => {
if (btn) {
sleep(5000);
btn.click();
}
resolve();
}).then((func) => {
func();
}).catch((err) => {
logger('error', err);
});
} catch (e) {
reject(e);
}
});
}

/**
* 观看视频
* @param setting
* @returns
*/
export async function watch() {
const { volume, playbackRate } = useSettings().zjooc.video;

return new Promise<void>((resolve, reject) => {
try {
const video = document.querySelector('video') as HTMLVideoElement;
const { common } = useContext();
// 设置当前视频
common.currentMedia = video;
// 如果已经播放完了,则重置视频进度
video.currentTime = 0;
// 音量
video.onvolumechange = function() {
video.volume = volume;
};
video.volume = volume;

Promise.resolve(async () => {
await sleep(1000);

// video.play();
// setInterval(video.play,1000);
(document.querySelector('#video-show div[data-title="点击播放"]') as HTMLDivElement).click();
// 设置播放速度
await switchPlaybackRate(playbackRate);

video.onpause = async function () {
if (!video.ended) {
if (stop) {
resolve();
} else {
await sleep(50);
(document.querySelector('#video-show div[data-title="点击播放"]') as HTMLDivElement).click();
}
}
};
video.onended = function () {
resolve();
};
}).then((func) => {
func();
}).catch((err) => {
logger('error', err);
});
} catch (e) {
reject(e);
}
});
}

/**
* 切换播放速度
* @param playbackRate 播放速度
*/
export async function switchPlaybackRate(playbackRate: number) {
await sleep(500);
const { btn } = domSearch({ btn: '#video-show div[data-title="点击选择速度"]' });
btn?.click();
await sleep(500);
const { rates } = domSearchAll({ rates: '#video-show div[class^="playbackrate"] p' });
for (const rate of rates) {
if (rate.innerText.startsWith(`${playbackRate === 1.0 ? '正常' : playbackRate}`)) {
rate?.click();
logger('info', '切换播放速度成功');
return;
}
}
logger('warn', '无匹配播放速度');
}
23 changes: 23 additions & 0 deletions packages/core/src/scripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,24 @@ export interface ICVESetting {
}
}

export interface ZJOOCSetting {
video: {
/** 播放速度 */
playbackRate: number
/** 音量 */
volume: number
/** 复习模式 */
restudy: boolean,
}
work: CommonWorkSetting
exam: CommonWorkSetting
}

export interface ScriptSettings {
zhs: ZHSSetting
cx: CXSetting
icve: ICVESetting
zjooc: ZJOOCSetting
common: {
answererWrappers: AnswererWrapper[]
}
Expand Down Expand Up @@ -208,6 +222,15 @@ export const defaultOCSSetting: ScriptSettings = {
cells: []
}
},
zjooc: {
video: {
playbackRate: 1,
volume: 0,
restudy: false
},
work: defaultWorkSetting,
exam: defaultWorkSetting
},
common: {
answererWrappers: [] as AnswererWrapper[]
},
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export function createStore(): OCSStore {
},
zhs: {
isRecognizing: false
}
},
zjooc: {}
}
};
}
Expand Down
2 changes: 2 additions & 0 deletions userjs.template
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
// @connect enncy.cn
// @connect icodef.com
// @connect ocsjs.com
// @connect zjooc.cn
// @connect localhost
// @match *://*.chaoxing.com/*
// @match *://*.edu.cn/*
// @match *://*.org.cn/*
// @match *://*.zhihuishu.com/*
// @match *://*.icve.com.cn/*
// @match *://www.zjooc.cn/*
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @grant GM_setValue
Expand Down