tronclass-auto 1.5.0 → 1.5.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 +1 -1
- package/src/index.js +27 -21
- package/src/video.js +43 -111
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -155,34 +155,40 @@ async function processCourse(page, courseId, stats) {
|
|
|
155
155
|
}
|
|
156
156
|
const success = await watchVideo(page, stats);
|
|
157
157
|
if (success) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
158
|
+
let serverComplete = false;
|
|
159
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
160
|
+
await page.waitForTimeout(10000);
|
|
161
|
+
await page.goto(`${BASE_URL}/course/${courseId}/content#/`, {
|
|
162
|
+
waitUntil: 'domcontentloaded', timeout: 15000
|
|
163
|
+
}).catch(() => {});
|
|
164
|
+
await page.waitForTimeout(5000);
|
|
165
|
+
|
|
166
|
+
serverComplete = await page.evaluate((actId) => {
|
|
167
|
+
const el = document.querySelector('.learning-activities');
|
|
168
|
+
if (!el) return false;
|
|
169
|
+
const scope = angular.element(el).scope();
|
|
170
|
+
if (!scope) return false;
|
|
171
|
+
const activities = document.querySelectorAll('.learning-activity.sortable');
|
|
172
|
+
for (const actEl of activities) {
|
|
173
|
+
const actScope = angular.element(actEl).scope();
|
|
174
|
+
if (!actScope || !actScope.activity) continue;
|
|
175
|
+
if (actScope.activity.id == actId) {
|
|
176
|
+
try { return scope.getActivityCompleteness(actScope.activity) === 'full'; } catch (e) {}
|
|
177
|
+
}
|
|
175
178
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
return false;
|
|
180
|
+
}, act.id);
|
|
181
|
+
|
|
182
|
+
if (serverComplete) break;
|
|
183
|
+
console.log(chalk.gray(` Checking server... (${attempt + 1}/5)`));
|
|
184
|
+
}
|
|
179
185
|
|
|
180
186
|
if (serverComplete) {
|
|
181
187
|
markDone(courseId, act.id);
|
|
182
188
|
done++;
|
|
183
189
|
console.log(chalk.green(' ✓ Saved (server confirmed)'));
|
|
184
190
|
} else {
|
|
185
|
-
console.log(chalk.yellow(' Server not confirmed, skipping'));
|
|
191
|
+
console.log(chalk.yellow(' Server not confirmed after retries, skipping'));
|
|
186
192
|
triedIds.add(act.id);
|
|
187
193
|
fail++;
|
|
188
194
|
}
|
package/src/video.js
CHANGED
|
@@ -25,18 +25,22 @@ async function detectPlayerType(page) {
|
|
|
25
25
|
const v = document.querySelector('video');
|
|
26
26
|
if (v) return 'html5';
|
|
27
27
|
|
|
28
|
+
const yt = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"]');
|
|
29
|
+
if (yt) return 'youtube';
|
|
30
|
+
|
|
28
31
|
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
32
|
try {
|
|
33
|
-
if (f.contentDocument)
|
|
34
|
-
const fv = f.contentDocument.querySelector('video');
|
|
35
|
-
if (fv) return 'html5';
|
|
36
|
-
}
|
|
33
|
+
if (f.contentDocument && f.contentDocument.querySelector('video')) return 'html5';
|
|
37
34
|
} catch (e) {}
|
|
38
35
|
}
|
|
39
36
|
return 'none';
|
|
37
|
+
}).then(async (type) => {
|
|
38
|
+
if (type === 'none') {
|
|
39
|
+
for (const f of page.frames) {
|
|
40
|
+
if (f.url && (f.url.includes('youtube') || f.url.includes('youtu.be'))) return 'youtube';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return type;
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -60,117 +64,45 @@ async function watchYouTube(page, stats) {
|
|
|
60
64
|
let rate = 1;
|
|
61
65
|
|
|
62
66
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (f
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
let ytFrame = null;
|
|
68
|
+
for (const f of page.frames) {
|
|
69
|
+
if (f.url && (f.url.includes('youtube') || f.url.includes('youtu.be'))) {
|
|
70
|
+
ytFrame = f;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
if (!ytFrame) {
|
|
76
|
+
log(chalk.yellow(' YouTube frame not found'));
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
const evalYT = async (code) => {
|
|
81
|
+
return await ytFrame.evaluate(code).catch(() => null);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
log(chalk.blue(' Starting video...'));
|
|
85
|
+
await evalYT("() => { const p = document.querySelector('#movie_player'); if (p) p.playVideo(); }");
|
|
78
86
|
await page.waitForTimeout(3000);
|
|
79
87
|
|
|
80
|
-
await
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
const state1 = await evalYT("() => { const p = document.querySelector('#movie_player'); return p ? p.getPlayerState() : -1; }");
|
|
89
|
+
if (state1 !== 1) {
|
|
90
|
+
log(chalk.yellow(' Retrying play...'));
|
|
91
|
+
await evalYT("() => { const p = document.querySelector('#movie_player'); if (p) p.playVideo(); }");
|
|
92
|
+
await page.waitForTimeout(3000);
|
|
93
|
+
}
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
90
|
-
if (!f) { resolve(1); return; }
|
|
91
|
-
|
|
92
|
-
let resolved = false;
|
|
93
|
-
const handler = (e) => {
|
|
94
|
-
try {
|
|
95
|
-
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
96
|
-
if (d.event === 'infoDelivery' && d.info && !resolved) {
|
|
97
|
-
resolved = true;
|
|
98
|
-
window.removeEventListener('message', handler);
|
|
99
|
-
resolve(d.info.playbackRate || 1);
|
|
100
|
-
}
|
|
101
|
-
} catch(e) {}
|
|
102
|
-
};
|
|
103
|
-
window.addEventListener('message', handler);
|
|
104
|
-
|
|
105
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'mute', args: [] }), '*');
|
|
106
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'setPlaybackRate', args: [2] }), '*');
|
|
107
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getPlaybackRate', args: [] }), '*');
|
|
108
|
-
|
|
109
|
-
setTimeout(() => { if (!resolved) { resolved = true; window.removeEventListener('message', handler); resolve(1); } }, 5000);
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
log(chalk.blue(` Speed: ${rate}x`));
|
|
95
|
+
await evalYT("() => { const p = document.querySelector('#movie_player'); if (p) { p.mute(); p.setPlaybackRate(2); } }");
|
|
96
|
+
await page.waitForTimeout(2000);
|
|
113
97
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
117
|
-
if (!f) { resolve(false); return; }
|
|
118
|
-
let resolved = false;
|
|
119
|
-
const handler = (e) => {
|
|
120
|
-
try {
|
|
121
|
-
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
122
|
-
if (d.event === 'infoDelivery' && d.info && !resolved) {
|
|
123
|
-
resolved = true;
|
|
124
|
-
window.removeEventListener('message', handler);
|
|
125
|
-
resolve(d.info.currentTime > 0);
|
|
126
|
-
}
|
|
127
|
-
} catch(e) {}
|
|
128
|
-
};
|
|
129
|
-
window.addEventListener('message', handler);
|
|
130
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getCurrentTime', args: [] }), '*');
|
|
131
|
-
setTimeout(() => { if (!resolved) { resolved = true; window.removeEventListener('message', handler); resolve(false); } }, 5000);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
98
|
+
rate = await evalYT("() => { const p = document.querySelector('#movie_player'); return p ? p.getPlaybackRate() : 1; }") || 1;
|
|
99
|
+
log(chalk.blue(` Speed: ${rate}x`));
|
|
134
100
|
|
|
135
|
-
|
|
101
|
+
const state2 = await evalYT("() => { const p = document.querySelector('#movie_player'); return p ? p.getPlayerState() : -1; }");
|
|
102
|
+
if (state2 === 1) {
|
|
136
103
|
log(chalk.green(' Playing ✓'));
|
|
137
104
|
} else {
|
|
138
|
-
log(chalk.yellow(
|
|
139
|
-
await page.evaluate(() => {
|
|
140
|
-
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
141
|
-
if (!f) return;
|
|
142
|
-
f.contentWindow.postMessage('{"event":"listening"}', '*');
|
|
143
|
-
});
|
|
144
|
-
await page.waitForTimeout(3000);
|
|
145
|
-
await page.evaluate(() => {
|
|
146
|
-
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
147
|
-
if (!f) return;
|
|
148
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'playVideo', args: [] }), '*');
|
|
149
|
-
});
|
|
150
|
-
await page.waitForTimeout(5000);
|
|
151
|
-
|
|
152
|
-
const retry = await page.evaluate(() => {
|
|
153
|
-
return new Promise((resolve) => {
|
|
154
|
-
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
155
|
-
if (!f) { resolve(false); return; }
|
|
156
|
-
let resolved = false;
|
|
157
|
-
const handler = (e) => {
|
|
158
|
-
try {
|
|
159
|
-
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
160
|
-
if (d.event === 'infoDelivery' && d.info && !resolved) {
|
|
161
|
-
resolved = true;
|
|
162
|
-
window.removeEventListener('message', handler);
|
|
163
|
-
resolve(d.info.currentTime > 0);
|
|
164
|
-
}
|
|
165
|
-
} catch(e) {}
|
|
166
|
-
};
|
|
167
|
-
window.addEventListener('message', handler);
|
|
168
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getCurrentTime', args: [] }), '*');
|
|
169
|
-
setTimeout(() => { if (!resolved) { resolved = true; window.removeEventListener('message', handler); resolve(false); } }, 5000);
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
if (retry) log(chalk.green(' Playing ✓'));
|
|
173
|
-
else log(chalk.yellow(' Could not verify playback'));
|
|
105
|
+
log(chalk.yellow(` State: ${state2} (may still be loading)`));
|
|
174
106
|
}
|
|
175
107
|
} catch (e) {
|
|
176
108
|
log(chalk.yellow(` YouTube error: ${e.message}`));
|
|
@@ -187,8 +119,8 @@ async function watchYouTube(page, stats) {
|
|
|
187
119
|
return 180;
|
|
188
120
|
});
|
|
189
121
|
|
|
190
|
-
const waitTime = ytDuration + 30;
|
|
191
|
-
log(chalk.blue(` ~${formatTime(ytDuration)} video → ~${formatTime(waitTime)} (
|
|
122
|
+
const waitTime = (rate >= 2 ? Math.ceil(ytDuration / 2) : ytDuration) + 30;
|
|
123
|
+
log(chalk.blue(` ~${formatTime(ytDuration)} video → ~${formatTime(waitTime)} (${rate}x)`));
|
|
192
124
|
|
|
193
125
|
if (stats) {
|
|
194
126
|
stats.currentVideoDuration = ytDuration;
|