tronclass-auto 1.2.1 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +28 -3
- package/src/video.js +29 -15
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -27,10 +27,28 @@ async function getUncompleted(page, courseId) {
|
|
|
27
27
|
let completeness = '';
|
|
28
28
|
try { completeness = scope.getActivityCompleteness(a); } catch (e) {}
|
|
29
29
|
if (completeness !== 'full') {
|
|
30
|
+
let durationSec = 0;
|
|
31
|
+
try {
|
|
32
|
+
const durText = actEl.querySelector('.activity-attribute');
|
|
33
|
+
if (durText) {
|
|
34
|
+
const m = durText.textContent.match(/(\d{2}):(\d{2}):(\d{2})/);
|
|
35
|
+
if (m) durationSec = parseInt(m[1]) * 3600 + parseInt(m[2]) * 60 + parseInt(m[3]);
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {}
|
|
38
|
+
if (durationSec === 0) {
|
|
39
|
+
try {
|
|
40
|
+
const durScope = angular.element(actEl).scope();
|
|
41
|
+
if (durScope && durScope.activity) {
|
|
42
|
+
const d = durScope.activity.data;
|
|
43
|
+
if (d && d.duration) durationSec = d.duration;
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {}
|
|
46
|
+
}
|
|
30
47
|
result.push({
|
|
31
48
|
id: a.id,
|
|
32
49
|
title: (a.title || '').substring(0, 60),
|
|
33
|
-
type: a.type || ''
|
|
50
|
+
type: a.type || '',
|
|
51
|
+
durationSec
|
|
34
52
|
});
|
|
35
53
|
}
|
|
36
54
|
});
|
|
@@ -49,9 +67,13 @@ async function processCourse(page, courseId, stats) {
|
|
|
49
67
|
|
|
50
68
|
console.log(chalk.cyan(` Uncompleted: ${videos.length} videos, remaining: ${remaining.length}`));
|
|
51
69
|
|
|
70
|
+
const totalDuration = remaining.reduce((sum, v) => sum + (v.durationSec || 120), 0);
|
|
71
|
+
const estWatchTime = Math.ceil(totalDuration / 2) + remaining.length * 15;
|
|
72
|
+
console.log(chalk.cyan(` 預估時間: ${formatTime(estWatchTime)}`));
|
|
73
|
+
|
|
52
74
|
if (stats) {
|
|
53
75
|
stats.remainingVideos = remaining.length;
|
|
54
|
-
stats.estimatedTimeForRemaining =
|
|
76
|
+
stats.estimatedTimeForRemaining = estWatchTime;
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
for (let i = 0; i < remaining.length; i++) {
|
|
@@ -71,14 +93,17 @@ async function processCourse(page, courseId, stats) {
|
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
try {
|
|
96
|
+
const thisDuration = Math.ceil((act.durationSec || 120) / 2) + 15;
|
|
74
97
|
if (stats) {
|
|
75
98
|
stats.remainingVideos = remaining.length - i - 1;
|
|
99
|
+
stats.estimatedTimeForRemaining -= thisDuration;
|
|
100
|
+
if (stats.estimatedTimeForRemaining < 0) stats.estimatedTimeForRemaining = 0;
|
|
76
101
|
}
|
|
77
102
|
const success = await watchVideo(page, stats);
|
|
78
103
|
if (success) {
|
|
79
104
|
markDone(courseId, act.id);
|
|
80
105
|
done++;
|
|
81
|
-
console.log(chalk.green('
|
|
106
|
+
console.log(chalk.green(' ✓ Saved'));
|
|
82
107
|
} else {
|
|
83
108
|
fail++;
|
|
84
109
|
}
|
package/src/video.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
|
|
3
3
|
function formatTime(seconds) {
|
|
4
|
-
|
|
4
|
+
seconds = Math.max(0, Math.floor(seconds));
|
|
5
|
+
const d = Math.floor(seconds / 86400);
|
|
6
|
+
const h = Math.floor((seconds % 86400) / 3600);
|
|
5
7
|
const m = Math.floor((seconds % 3600) / 60);
|
|
6
|
-
const s =
|
|
8
|
+
const s = seconds % 60;
|
|
9
|
+
if (d > 0) return `${d}d ${h}h ${m}m`;
|
|
7
10
|
if (h > 0) return `${h}h ${m}m ${s}s`;
|
|
8
11
|
if (m > 0) return `${m}m ${s}s`;
|
|
9
12
|
return `${s}s`;
|
|
@@ -12,11 +15,11 @@ function formatTime(seconds) {
|
|
|
12
15
|
async function watchVideo(page, stats) {
|
|
13
16
|
const hasVideo = await page.evaluate(() => document.querySelector('video') !== null);
|
|
14
17
|
if (!hasVideo) {
|
|
15
|
-
log(chalk.yellow('
|
|
18
|
+
log(chalk.yellow(' No video element'));
|
|
16
19
|
return false;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
log(chalk.blue('
|
|
22
|
+
log(chalk.blue(' Muted + 2x'));
|
|
20
23
|
|
|
21
24
|
await page.evaluate(() => {
|
|
22
25
|
document.querySelectorAll('video').forEach(v => {
|
|
@@ -35,7 +38,7 @@ async function watchVideo(page, stats) {
|
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
if (!isPlaying) {
|
|
38
|
-
log(chalk.yellow('
|
|
41
|
+
log(chalk.yellow(' Clicking play...'));
|
|
39
42
|
await page.evaluate(() => {
|
|
40
43
|
document.querySelectorAll('[class*="play"], .vjs-big-play-button, button').forEach(btn => {
|
|
41
44
|
if (btn.offsetParent && (btn.className.includes('play') || btn.className.includes('Play'))) {
|
|
@@ -53,14 +56,14 @@ async function watchVideo(page, stats) {
|
|
|
53
56
|
});
|
|
54
57
|
|
|
55
58
|
if (!duration || duration <= 0) {
|
|
56
|
-
log(chalk.yellow('
|
|
59
|
+
log(chalk.yellow(' Cannot get duration, waiting 30s...'));
|
|
57
60
|
await page.waitForTimeout(30000);
|
|
58
61
|
if (stats) stats.videosWatched++;
|
|
59
62
|
return true;
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
const waitTime = (duration / 2) + 15;
|
|
63
|
-
log(chalk.blue(`
|
|
66
|
+
log(chalk.blue(` ${formatTime(duration)} video → ~${formatTime(waitTime)} (2x)`));
|
|
64
67
|
|
|
65
68
|
if (stats) {
|
|
66
69
|
stats.currentVideoDuration = duration / 2;
|
|
@@ -90,37 +93,48 @@ async function watchVideo(page, stats) {
|
|
|
90
93
|
|
|
91
94
|
if (state.p > lastPct) {
|
|
92
95
|
const pct = state.p;
|
|
96
|
+
const barWidth = 20;
|
|
97
|
+
const filled = Math.floor(barWidth * pct / 100);
|
|
98
|
+
const empty = barWidth - filled;
|
|
99
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
93
100
|
const remaining = Math.max(0, waitTime - elapsed);
|
|
94
101
|
let etaStr = '';
|
|
95
102
|
if (stats && stats.remainingVideos > 0) {
|
|
96
103
|
const totalRemaining = remaining + stats.estimatedTimeForRemaining;
|
|
97
|
-
etaStr = chalk.gray(` | ETA
|
|
104
|
+
etaStr = chalk.gray(` | ETA ${formatTime(totalRemaining)}`);
|
|
98
105
|
} else {
|
|
99
|
-
etaStr = chalk.gray(` |
|
|
106
|
+
etaStr = chalk.gray(` | ${formatTime(remaining)} left`);
|
|
100
107
|
}
|
|
101
|
-
log(chalk.blue(`
|
|
108
|
+
log(chalk.blue(` ${bar} ${String(pct).padStart(3)}% (${formatTime(elapsed)})${etaStr}`));
|
|
102
109
|
lastPct = pct;
|
|
103
110
|
}
|
|
104
111
|
|
|
105
112
|
if (state.e) {
|
|
106
|
-
log(chalk.green('
|
|
113
|
+
log(chalk.green(' Done ✓'));
|
|
107
114
|
break;
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
if (state.stalled) {
|
|
111
118
|
if (elapsed % 15 === 0) {
|
|
112
|
-
log(chalk.gray(`
|
|
119
|
+
log(chalk.gray(` Buffering... (${formatTime(elapsed)})`));
|
|
113
120
|
}
|
|
114
121
|
continue;
|
|
115
122
|
}
|
|
116
123
|
|
|
117
|
-
if (state.paused && elapsed >
|
|
124
|
+
if (state.paused && !state.stalled && elapsed > 10) {
|
|
125
|
+
await page.waitForTimeout(3000);
|
|
126
|
+
const stillPaused = await page.evaluate(() => {
|
|
127
|
+
const v = document.querySelector('video');
|
|
128
|
+
return v ? v.paused : false;
|
|
129
|
+
});
|
|
130
|
+
if (!stillPaused) continue;
|
|
131
|
+
|
|
118
132
|
restartCount++;
|
|
119
133
|
if (restartCount > 10) {
|
|
120
|
-
log(chalk.red('
|
|
134
|
+
log(chalk.red(' Too many restarts, giving up'));
|
|
121
135
|
return false;
|
|
122
136
|
}
|
|
123
|
-
log(chalk.yellow(`
|
|
137
|
+
log(chalk.yellow(` Paused, restarting (#${restartCount})...`));
|
|
124
138
|
await page.evaluate(() => document.querySelector('video').play());
|
|
125
139
|
await page.waitForTimeout(1000);
|
|
126
140
|
}
|