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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tronclass-auto",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "自動觀看 eclass/TronClass 影片、填寫表單的 CLI 工具",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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}`] = true;
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) timeRange = tm[0];
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
- const uncompleted = await getUncompleted(page, courseId);
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
- console.log(chalk.cyan(` Uncompleted: ${videos.length} videos, remaining: ${remaining.length}`));
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
- const totalDuration = remaining.reduce((sum, v) => sum + (v.durationSec || 120), 0);
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
- if (stats) {
88
- stats.remainingVideos = remaining.length;
89
- stats.estimatedTimeForRemaining = estWatchTime;
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
- for (let i = 0; i < remaining.length; i++) {
93
- const act = remaining[i];
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(' [WARN] Failed to load activity page'));
104
- skip++;
105
- continue;
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 = remaining.length - i - 1;
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(' ✓ Saved'));
152
+ console.log(chalk.green(' ✓ Saved'));
120
153
  } else {
121
154
  fail++;
122
155
  }
123
156
  } catch (e) {
124
- console.log(chalk.red(` [ERROR] ${e.message}`));
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