tronclass-auto 1.3.2 → 1.3.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/config.js +8 -2
- package/src/index.js +56 -22
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -54,7 +54,13 @@ function saveProgress(data) {
|
|
|
54
54
|
|
|
55
55
|
function markDone(courseId, activityId) {
|
|
56
56
|
const p = loadProgress();
|
|
57
|
-
p[`${courseId}_${activityId}`] =
|
|
57
|
+
p[`${courseId}_${activityId}`] = 'done';
|
|
58
|
+
saveProgress(p);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function markLocked(courseId, activityId) {
|
|
62
|
+
const p = loadProgress();
|
|
63
|
+
p[`${courseId}_${activityId}`] = 'locked';
|
|
58
64
|
saveProgress(p);
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -154,5 +160,5 @@ module.exports = {
|
|
|
154
160
|
CONFIG_DIR, CONFIG_FILE, PROGRESS_FILE, COOKIE_FILE, SALT_FILE,
|
|
155
161
|
BASE_URL, DEFAULT_CONFIG,
|
|
156
162
|
ensureDir, loadConfig, saveConfig,
|
|
157
|
-
loadProgress, saveProgress, markDone, isDone, showConfig, manageCourses
|
|
163
|
+
loadProgress, saveProgress, markDone, markLocked, isDone, showConfig, manageCourses
|
|
158
164
|
};
|
package/src/index.js
CHANGED
|
@@ -32,14 +32,21 @@ async function getUncompleted(page, courseId) {
|
|
|
32
32
|
let isExpired = false;
|
|
33
33
|
try { isUpcoming = scope.activityUpcoming(a); } catch (e) {}
|
|
34
34
|
try { isExpired = scope.activityExpired(a); } catch (e) {}
|
|
35
|
-
if (isUpcoming) return;
|
|
36
35
|
|
|
37
36
|
let timeRange = '';
|
|
37
|
+
let startTime = '';
|
|
38
38
|
try {
|
|
39
39
|
const txt = actEl.textContent || '';
|
|
40
40
|
const tm = txt.match(/時間:\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\s*~\s*(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})/);
|
|
41
|
-
if (tm)
|
|
41
|
+
if (tm) {
|
|
42
|
+
timeRange = tm[0];
|
|
43
|
+
startTime = tm[1];
|
|
44
|
+
}
|
|
42
45
|
} catch (e) {}
|
|
46
|
+
|
|
47
|
+
const now = new Date();
|
|
48
|
+
const startDate = startTime ? new Date(startTime.replace(' ', 'T')) : null;
|
|
49
|
+
if (isUpcoming && startDate && startDate > now) return;
|
|
43
50
|
let durationSec = 0;
|
|
44
51
|
try {
|
|
45
52
|
const durText = actEl.querySelector('.activity-attribute');
|
|
@@ -73,25 +80,31 @@ async function processCourse(page, courseId, stats) {
|
|
|
73
80
|
let done = 0;
|
|
74
81
|
let skip = 0;
|
|
75
82
|
let fail = 0;
|
|
83
|
+
let totalProcessed = 0;
|
|
84
|
+
let consecutiveLocked = 0;
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
const videos = uncompleted.filter(u => u.type === 'online_video');
|
|
79
|
-
const remaining = videos.filter(v => !isDone(courseId, v.id));
|
|
86
|
+
console.log(chalk.cyan(` [fetching course activities...]`));
|
|
80
87
|
|
|
81
|
-
|
|
88
|
+
while (true) {
|
|
89
|
+
const uncompleted = await getUncompleted(page, courseId);
|
|
90
|
+
const videos = uncompleted.filter(u => u.type === 'online_video');
|
|
91
|
+
const remaining = videos.filter(v => !isDone(courseId, v.id));
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
const estWatchTime = Math.ceil(totalDuration / 2) + remaining.length * 15;
|
|
85
|
-
console.log(chalk.cyan(` 預估時間: ${formatTime(estWatchTime)}`));
|
|
93
|
+
if (remaining.length === 0) break;
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
if (totalProcessed === 0) {
|
|
96
|
+
console.log(chalk.cyan(` Available: ${videos.length} videos, remaining: ${remaining.length}`));
|
|
97
|
+
const totalDuration = remaining.reduce((sum, v) => sum + (v.durationSec || 120), 0);
|
|
98
|
+
const estWatchTime = Math.ceil(totalDuration / 2) + remaining.length * 15;
|
|
99
|
+
console.log(chalk.cyan(` 預估時間: ${formatTime(estWatchTime)}`));
|
|
100
|
+
if (stats) {
|
|
101
|
+
stats.remainingVideos = remaining.length;
|
|
102
|
+
stats.estimatedTimeForRemaining = estWatchTime;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
console.log(chalk.white(`\n [${i + 1}/${remaining.length}] ${act.title}`));
|
|
106
|
+
const act = remaining[0];
|
|
107
|
+
console.log(chalk.white(`\n [${totalProcessed + 1}] ${act.title}`));
|
|
95
108
|
|
|
96
109
|
const url = `${BASE_URL}/course/${courseId}/learning-activity/full-screen#/${act.id}`;
|
|
97
110
|
try {
|
|
@@ -100,15 +113,35 @@ async function processCourse(page, courseId, stats) {
|
|
|
100
113
|
await page.waitForTimeout(5000);
|
|
101
114
|
|
|
102
115
|
if (!page.url().includes('learning-activity')) {
|
|
103
|
-
console.log(chalk.yellow('
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
console.log(chalk.yellow(' Locked (page redirect), skipping rest'));
|
|
117
|
+
markLocked(courseId, act.id);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const hasVideo = await page.evaluate(() => document.querySelector('video') !== null);
|
|
122
|
+
if (!hasVideo) {
|
|
123
|
+
consecutiveLocked++;
|
|
124
|
+
if (consecutiveLocked >= 2) {
|
|
125
|
+
console.log(chalk.yellow(' Locked (no video), stopping this course'));
|
|
126
|
+
markLocked(courseId, act.id);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
console.log(chalk.yellow(' No video, might be loading... waiting'));
|
|
130
|
+
await page.waitForTimeout(5000);
|
|
131
|
+
const hasVideoNow = await page.evaluate(() => document.querySelector('video') !== null);
|
|
132
|
+
if (!hasVideoNow) {
|
|
133
|
+
console.log(chalk.yellow(' Still no video, locked'));
|
|
134
|
+
markLocked(courseId, act.id);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
consecutiveLocked = 0;
|
|
106
139
|
}
|
|
107
140
|
|
|
108
141
|
try {
|
|
109
142
|
const thisDuration = Math.ceil((act.durationSec || 120) / 2) + 15;
|
|
110
143
|
if (stats) {
|
|
111
|
-
stats.remainingVideos
|
|
144
|
+
stats.remainingVideos--;
|
|
112
145
|
stats.estimatedTimeForRemaining -= thisDuration;
|
|
113
146
|
if (stats.estimatedTimeForRemaining < 0) stats.estimatedTimeForRemaining = 0;
|
|
114
147
|
}
|
|
@@ -116,15 +149,16 @@ async function processCourse(page, courseId, stats) {
|
|
|
116
149
|
if (success) {
|
|
117
150
|
markDone(courseId, act.id);
|
|
118
151
|
done++;
|
|
119
|
-
console.log(chalk.green('
|
|
152
|
+
console.log(chalk.green(' ✓ Saved'));
|
|
120
153
|
} else {
|
|
121
154
|
fail++;
|
|
122
155
|
}
|
|
123
156
|
} catch (e) {
|
|
124
|
-
console.log(chalk.red(`
|
|
157
|
+
console.log(chalk.red(` [ERROR] ${e.message}`));
|
|
125
158
|
fail++;
|
|
126
159
|
}
|
|
127
160
|
|
|
161
|
+
totalProcessed++;
|
|
128
162
|
await page.waitForTimeout(2000);
|
|
129
163
|
}
|
|
130
164
|
|