tronclass-auto 1.3.5 → 1.3.6
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 +9 -5
- package/src/video.js +88 -3
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -118,18 +118,22 @@ async function processCourse(page, courseId, stats) {
|
|
|
118
118
|
break;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
const
|
|
122
|
-
if (document.querySelector('video')) return
|
|
121
|
+
const playerType = await page.evaluate(() => {
|
|
122
|
+
if (document.querySelector('video')) return 'html5';
|
|
123
|
+
const yt = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"]');
|
|
124
|
+
if (yt) return 'iframe';
|
|
123
125
|
for (const f of document.querySelectorAll('iframe')) {
|
|
124
|
-
try { if (f.contentDocument && f.contentDocument.querySelector('video')) return
|
|
126
|
+
try { if (f.contentDocument && f.contentDocument.querySelector('video')) return 'html5'; } catch (e) {}
|
|
125
127
|
}
|
|
126
|
-
return
|
|
128
|
+
return 'none';
|
|
127
129
|
});
|
|
128
|
-
if (
|
|
130
|
+
if (playerType === 'none') {
|
|
129
131
|
console.log(chalk.yellow(' No video, waiting more...'));
|
|
130
132
|
await page.waitForTimeout(10000);
|
|
131
133
|
const hasVideoNow = await page.evaluate(() => {
|
|
132
134
|
if (document.querySelector('video')) return true;
|
|
135
|
+
const yt = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"]');
|
|
136
|
+
if (yt) return true;
|
|
133
137
|
for (const f of document.querySelectorAll('iframe')) {
|
|
134
138
|
try { if (f.contentDocument && f.contentDocument.querySelector('video')) return true; } catch (e) {}
|
|
135
139
|
}
|
package/src/video.js
CHANGED
|
@@ -20,13 +20,98 @@ function log(msg) {
|
|
|
20
20
|
console.log(msg);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
async function detectPlayerType(page) {
|
|
24
|
+
return page.evaluate(() => {
|
|
25
|
+
const v = document.querySelector('video');
|
|
26
|
+
if (v) return 'html5';
|
|
27
|
+
|
|
28
|
+
for (const f of document.querySelectorAll('iframe')) {
|
|
29
|
+
const src = (f.src || f.getAttribute('data-src') || '').toLowerCase();
|
|
30
|
+
if (src.includes('youtube') || src.includes('youtu.be')) return 'youtube';
|
|
31
|
+
if (src.includes('vimeo')) return 'vimeo';
|
|
32
|
+
try {
|
|
33
|
+
if (f.contentDocument) {
|
|
34
|
+
const fv = f.contentDocument.querySelector('video');
|
|
35
|
+
if (fv) return 'html5';
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {}
|
|
38
|
+
}
|
|
39
|
+
return 'none';
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
async function watchVideo(page, stats) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
const playerType = await detectPlayerType(page);
|
|
45
|
+
|
|
46
|
+
if (playerType === 'none') {
|
|
47
|
+
log(chalk.yellow(' No player found'));
|
|
27
48
|
return false;
|
|
28
49
|
}
|
|
29
50
|
|
|
51
|
+
if (playerType === 'youtube') {
|
|
52
|
+
return await watchYouTube(page, stats);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return await watchHTML5(page, stats);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function watchYouTube(page, stats) {
|
|
59
|
+
log(chalk.blue(' YouTube embed (platform tracks progress)'));
|
|
60
|
+
|
|
61
|
+
await page.evaluate(() => {
|
|
62
|
+
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
63
|
+
if (!f) return;
|
|
64
|
+
try {
|
|
65
|
+
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'setPlaybackRate', args: [2] }), '*');
|
|
66
|
+
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'mute', args: [] }), '*');
|
|
67
|
+
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'playVideo', args: [] }), '*');
|
|
68
|
+
} catch (e) {}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const ytDuration = await page.evaluate(() => {
|
|
72
|
+
const actText = document.querySelector('.activity-attribute, [class*="attribute"]');
|
|
73
|
+
if (actText) {
|
|
74
|
+
const m = actText.textContent.match(/(\d{2}):(\d{2}):(\d{2})/);
|
|
75
|
+
if (m) return parseInt(m[1]) * 3600 + parseInt(m[2]) * 60 + parseInt(m[3]);
|
|
76
|
+
}
|
|
77
|
+
return 180;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const waitTime = Math.ceil(ytDuration / 2) + 30;
|
|
81
|
+
log(chalk.blue(` ~${formatTime(ytDuration)} video → waiting ~${formatTime(waitTime)} (2x)`));
|
|
82
|
+
|
|
83
|
+
let elapsed = 0;
|
|
84
|
+
let lastPct = -1;
|
|
85
|
+
|
|
86
|
+
while (elapsed < waitTime) {
|
|
87
|
+
await page.waitForTimeout(5000);
|
|
88
|
+
elapsed += 5;
|
|
89
|
+
|
|
90
|
+
const pct = Math.min(100, Math.floor((elapsed / waitTime) * 100));
|
|
91
|
+
if (pct > lastPct) {
|
|
92
|
+
const barWidth = 20;
|
|
93
|
+
const filled = Math.floor(barWidth * pct / 100);
|
|
94
|
+
const empty = barWidth - filled;
|
|
95
|
+
const bar = chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(empty));
|
|
96
|
+
let etaStr = '';
|
|
97
|
+
const remaining = Math.max(0, waitTime - elapsed);
|
|
98
|
+
if (stats && stats.remainingVideos > 0) {
|
|
99
|
+
etaStr = chalk.gray(` | ETA ${formatTime(remaining + stats.estimatedTimeForRemaining)}`);
|
|
100
|
+
} else {
|
|
101
|
+
etaStr = chalk.gray(` | ${formatTime(remaining)} left`);
|
|
102
|
+
}
|
|
103
|
+
progress(`${bar} ${String(pct).padStart(3)}% (${formatTime(elapsed)})${etaStr}`);
|
|
104
|
+
lastPct = pct;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
log(chalk.green(' Done ✓'));
|
|
109
|
+
await page.waitForTimeout(2000);
|
|
110
|
+
if (stats) stats.videosWatched++;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function watchHTML5(page, stats) {
|
|
30
115
|
log(chalk.blue(' Muted + 2x'));
|
|
31
116
|
|
|
32
117
|
await page.evaluate(() => {
|