tronclass-auto 1.4.0 → 1.4.2

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.4.0",
3
+ "version": "1.4.2",
4
4
  "description": "自動觀看 eclass/TronClass 影片、填寫表單的 CLI 工具",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -81,6 +81,7 @@ async function processCourse(page, courseId, stats) {
81
81
  let skip = 0;
82
82
  let fail = 0;
83
83
  let totalProcessed = 0;
84
+ let serverRejects = {};
84
85
  let consecutiveLocked = 0;
85
86
 
86
87
  console.log(chalk.cyan(` [fetching course activities...]`));
@@ -184,8 +185,15 @@ async function processCourse(page, courseId, stats) {
184
185
  done++;
185
186
  console.log(chalk.green(' ✓ Saved (server confirmed)'));
186
187
  } else {
187
- console.log(chalk.yellow(' Server not confirmed yet, will retry next run'));
188
- fail++;
188
+ serverRejects[act.id] = (serverRejects[act.id] || 0) + 1;
189
+ if (serverRejects[act.id] >= 2) {
190
+ console.log(chalk.yellow(' Server rejected twice, marking done locally'));
191
+ markDone(courseId, act.id);
192
+ done++;
193
+ } else {
194
+ console.log(chalk.yellow(' Server not confirmed, will retry...'));
195
+ fail++;
196
+ }
189
197
  }
190
198
  } else {
191
199
  fail++;
package/src/video.js CHANGED
@@ -57,37 +57,62 @@ async function watchVideo(page, stats) {
57
57
 
58
58
  async function watchYouTube(page, stats) {
59
59
  log(chalk.blue(' YouTube embed'));
60
+ let rate = 1;
60
61
 
61
62
  try {
62
63
  const ytFrame = page.locator('iframe[src*="youtube"], iframe[src*="youtu.be"]');
63
64
  await ytFrame.click({ timeout: 5000 }).catch(() => {});
64
- await page.waitForTimeout(2000);
65
+ await page.waitForTimeout(3000);
65
66
 
66
- const frame = page.frameLocator('iframe[src*="youtube"], iframe[src*="youtu.be"]');
67
- const playBtn = frame.locator('.ytp-large-play-button');
68
- await playBtn.click({ timeout: 5000 }).catch(() => {});
67
+ await page.evaluate(() => {
68
+ const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
69
+ if (!f) return;
70
+ f.contentWindow.postMessage('{"event":"listening"}', '*');
71
+ });
69
72
  await page.waitForTimeout(2000);
70
73
 
71
- try {
72
- await frame.locator('.ytp-settings-button').click({ timeout: 3000 });
73
- await page.waitForTimeout(500);
74
- await frame.locator('.ytp-menuitem:has-text("播放速度"), .ytp-menuitem:has-text("Playback speed")').click({ timeout: 3000 });
75
- await page.waitForTimeout(500);
76
- await frame.locator('.ytp-menuitem:has-text("2"), .ytp-menuitem:has-text("2x")').click({ timeout: 3000 });
77
- log(chalk.blue(' Speed: 2x ✓'));
78
- } catch (e) {
79
- log(chalk.yellow(' Speed: 1x (settings menu unavailable)'));
80
- }
74
+ await page.evaluate(() => {
75
+ const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
76
+ if (!f) return;
77
+ f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'playVideo', args: [] }), '*');
78
+ });
79
+ await page.waitForTimeout(1000);
81
80
 
82
- try {
83
- await frame.locator('.ytp-settings-button').click({ timeout: 2000 });
84
- await page.waitForTimeout(500);
85
- await frame.locator('.ytp-menuitem:has-text("字幕"), .ytp-menuitem:has-text("Subtitles"), .ytp-menuitem:has-text("CC")').click({ timeout: 2000 }).catch(() => {});
86
- await frame.locator('.ytp-menuitem:has-text("關閉"), .ytp-menuitem:has-text("Off"), .ytp-menuitem:has-text("None")').click({ timeout: 2000 }).catch(() => {});
87
- await page.keyboard.press('Escape');
88
- } catch (e) {}
81
+ await page.evaluate(() => {
82
+ const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
83
+ if (!f) return;
84
+ f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'mute', args: [] }), '*');
85
+ });
86
+ await page.waitForTimeout(1000);
87
+
88
+ await page.evaluate(() => {
89
+ const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
90
+ if (!f) return;
91
+ f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'setPlaybackRate', args: [2] }), '*');
92
+ });
93
+ await page.waitForTimeout(2000);
94
+
95
+ rate = await page.evaluate(() => {
96
+ return new Promise((resolve) => {
97
+ const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
98
+ if (!f) { resolve(1); return; }
99
+ const handler = (e) => {
100
+ try {
101
+ const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
102
+ if (d.event === 'infoDelivery' && d.info && d.info.playbackRate) {
103
+ window.removeEventListener('message', handler);
104
+ resolve(d.info.playbackRate);
105
+ }
106
+ } catch(e) {}
107
+ };
108
+ window.addEventListener('message', handler);
109
+ f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getPlaybackRate', args: [] }), '*');
110
+ setTimeout(() => { window.removeEventListener('message', handler); resolve(1); }, 3000);
111
+ });
112
+ });
113
+ log(chalk.blue(` Speed: ${rate}x`));
89
114
  } catch (e) {
90
- log(chalk.yellow(' YouTube setup failed, falling back'));
115
+ log(chalk.yellow(` YouTube setup error: ${e.message}`));
91
116
  }
92
117
 
93
118
  await page.waitForTimeout(2000);
@@ -101,8 +126,8 @@ async function watchYouTube(page, stats) {
101
126
  return 180;
102
127
  });
103
128
 
104
- const waitTime = ytDuration + 30;
105
- log(chalk.blue(` ~${formatTime(ytDuration)} video → ~${formatTime(waitTime)} (YouTube 1x safe)`));
129
+ const waitTime = (rate >= 2 ? Math.ceil(ytDuration / 2) : ytDuration) + 30;
130
+ log(chalk.blue(` ~${formatTime(ytDuration)} video → ~${formatTime(waitTime)} (${rate}x)`));
106
131
 
107
132
  if (stats) {
108
133
  stats.currentVideoDuration = ytDuration;