tronclass-auto 1.2.0 → 1.2.3

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.2.0",
3
+ "version": "1.2.3",
4
4
  "description": "自動觀看 eclass/TronClass 影片、填寫表單的 CLI 工具",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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 = remaining.reduce((sum, v) => sum + 120, 0);
76
+ stats.estimatedTimeForRemaining = estWatchTime;
55
77
  }
56
78
 
57
79
  for (let i = 0; i < remaining.length; i++) {
@@ -71,8 +93,11 @@ 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) {
package/src/tui.js CHANGED
@@ -70,19 +70,13 @@ async function runMenu(config) {
70
70
  return;
71
71
  }
72
72
 
73
- const courseChoices = config.courses.map((url, i) => {
74
- const match = url.match(/\/course\/(\d+)\//);
75
- const id = match ? match[1] : '?';
76
- return { name: `[${id}] ${url}`, value: i, checked: true };
77
- });
78
-
79
- const { selectedCourses } = await inquirer.prompt([{
80
- type: 'checkbox',
81
- name: 'selectedCourses',
82
- message: '選擇要觀看的課程:',
83
- choices: courseChoices,
84
- validate: (ans) => ans.length > 0 ? true : '至少選一個課程'
73
+ const { confirm } = await inquirer.prompt([{
74
+ type: 'confirm',
75
+ name: 'confirm',
76
+ message: `開始觀看全部 ${config.courses.length} 個課程?`,
77
+ default: true
85
78
  }]);
79
+ if (!confirm) return;
86
80
 
87
81
  const { headless } = await inquirer.prompt([{
88
82
  type: 'confirm',
@@ -94,14 +88,14 @@ async function runMenu(config) {
94
88
  console.log('');
95
89
  box([
96
90
  chalk.green('開始自動觀看'),
97
- `課程: ${chalk.cyan(selectedCourses.length)} 個`,
91
+ `課程: ${chalk.cyan(config.courses.length)} 個`,
98
92
  `模式: ${headless ? chalk.gray('無頭') : chalk.white('有頭')}`,
99
93
  `倍速: ${chalk.yellow(config.playbackRate || 2)}x`,
100
94
  ]);
101
95
  console.log('');
102
96
 
103
97
  const { runWithSelection } = require('./index');
104
- await runWithSelection(selectedCourses.map(i => config.courses[i]), headless);
98
+ await runWithSelection(config.courses, headless);
105
99
  await pressAnyKey();
106
100
  }
107
101
 
package/src/video.js CHANGED
@@ -1,9 +1,12 @@
1
1
  const chalk = require('chalk');
2
2
 
3
3
  function formatTime(seconds) {
4
- const h = Math.floor(seconds / 3600);
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 = Math.floor(seconds % 60);
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`;
@@ -114,7 +117,14 @@ async function watchVideo(page, stats) {
114
117
  continue;
115
118
  }
116
119
 
117
- if (state.paused && elapsed > 5) {
120
+ if (state.paused && !state.stalled && elapsed > 10) {
121
+ await page.waitForTimeout(3000);
122
+ const stillPaused = await page.evaluate(() => {
123
+ const v = document.querySelector('video');
124
+ return v ? v.paused : false;
125
+ });
126
+ if (!stillPaused) continue;
127
+
118
128
  restartCount++;
119
129
  if (restartCount > 10) {
120
130
  log(chalk.red(' [VIDEO] Too many restarts, giving up'));