tronclass-auto 1.4.6 → 1.5.1
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 +46 -47
- package/src/video.js +77 -60
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -81,15 +81,14 @@ async function processCourse(page, courseId, stats) {
|
|
|
81
81
|
let skip = 0;
|
|
82
82
|
let fail = 0;
|
|
83
83
|
let totalProcessed = 0;
|
|
84
|
-
let
|
|
85
|
-
let consecutiveLocked = 0;
|
|
84
|
+
let triedIds = new Set();
|
|
86
85
|
|
|
87
86
|
console.log(chalk.cyan(` [fetching course activities...]`));
|
|
88
87
|
|
|
89
88
|
while (true) {
|
|
90
89
|
const uncompleted = await getUncompleted(page, courseId);
|
|
91
90
|
const videos = uncompleted.filter(u => u.type === 'online_video');
|
|
92
|
-
const remaining = videos.filter(v => !isDone(courseId, v.id));
|
|
91
|
+
const remaining = videos.filter(v => !isDone(courseId, v.id) && !triedIds.has(v.id));
|
|
93
92
|
|
|
94
93
|
if (remaining.length === 0) break;
|
|
95
94
|
|
|
@@ -115,8 +114,9 @@ async function processCourse(page, courseId, stats) {
|
|
|
115
114
|
|
|
116
115
|
if (!page.url().includes('learning-activity')) {
|
|
117
116
|
console.log(chalk.yellow(' Page redirect, skipping'));
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
triedIds.add(act.id);
|
|
118
|
+
totalProcessed++;
|
|
119
|
+
continue;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
const playerType = await page.evaluate(() => {
|
|
@@ -128,25 +128,22 @@ async function processCourse(page, courseId, stats) {
|
|
|
128
128
|
}
|
|
129
129
|
return 'none';
|
|
130
130
|
});
|
|
131
|
+
|
|
131
132
|
if (playerType === 'none') {
|
|
132
|
-
console.log(chalk.yellow(' No
|
|
133
|
+
console.log(chalk.yellow(' No player, waiting more...'));
|
|
133
134
|
await page.waitForTimeout(10000);
|
|
134
135
|
const hasVideoNow = await page.evaluate(() => {
|
|
135
|
-
if (document.querySelector('video')) return
|
|
136
|
+
if (document.querySelector('video')) return 'html5';
|
|
136
137
|
const yt = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"], iframe[src*="vimeo"]');
|
|
137
|
-
if (yt) return
|
|
138
|
-
|
|
139
|
-
try { if (f.contentDocument && f.contentDocument.querySelector('video')) return true; } catch (e) {}
|
|
140
|
-
}
|
|
141
|
-
return false;
|
|
138
|
+
if (yt) return 'iframe';
|
|
139
|
+
return 'none';
|
|
142
140
|
});
|
|
143
|
-
if (
|
|
144
|
-
console.log(chalk.yellow('
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
if (hasVideoNow === 'none') {
|
|
142
|
+
console.log(chalk.yellow(' No player found, skipping'));
|
|
143
|
+
triedIds.add(act.id);
|
|
144
|
+
totalProcessed++;
|
|
145
|
+
continue;
|
|
147
146
|
}
|
|
148
|
-
} else {
|
|
149
|
-
consecutiveLocked = 0;
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
try {
|
|
@@ -158,48 +155,50 @@ async function processCourse(page, courseId, stats) {
|
|
|
158
155
|
}
|
|
159
156
|
const success = await watchVideo(page, stats);
|
|
160
157
|
if (success) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
+
}
|
|
178
178
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
return false;
|
|
180
|
+
}, act.id);
|
|
181
|
+
|
|
182
|
+
if (serverComplete) break;
|
|
183
|
+
console.log(chalk.gray(` Checking server... (${attempt + 1}/5)`));
|
|
184
|
+
}
|
|
182
185
|
|
|
183
186
|
if (serverComplete) {
|
|
184
187
|
markDone(courseId, act.id);
|
|
185
188
|
done++;
|
|
186
189
|
console.log(chalk.green(' ✓ Saved (server confirmed)'));
|
|
187
190
|
} else {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
markDone(courseId, act.id);
|
|
192
|
-
done++;
|
|
193
|
-
} else {
|
|
194
|
-
console.log(chalk.yellow(' Server not confirmed, will retry...'));
|
|
195
|
-
fail++;
|
|
196
|
-
}
|
|
191
|
+
console.log(chalk.yellow(' Server not confirmed after retries, skipping'));
|
|
192
|
+
triedIds.add(act.id);
|
|
193
|
+
fail++;
|
|
197
194
|
}
|
|
198
195
|
} else {
|
|
196
|
+
triedIds.add(act.id);
|
|
199
197
|
fail++;
|
|
200
198
|
}
|
|
201
199
|
} catch (e) {
|
|
202
200
|
console.log(chalk.red(` [ERROR] ${e.message}`));
|
|
201
|
+
triedIds.add(act.id);
|
|
203
202
|
fail++;
|
|
204
203
|
}
|
|
205
204
|
|
package/src/video.js
CHANGED
|
@@ -60,32 +60,64 @@ async function watchYouTube(page, stats) {
|
|
|
60
60
|
let rate = 1;
|
|
61
61
|
|
|
62
62
|
try {
|
|
63
|
+
await page.evaluate(() => {
|
|
64
|
+
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
65
|
+
if (f) f.scrollIntoView({ block: 'center' });
|
|
66
|
+
});
|
|
67
|
+
await page.waitForTimeout(2000);
|
|
68
|
+
|
|
63
69
|
const ytFrame = page.locator('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
64
70
|
await ytFrame.click({ timeout: 5000 }).catch(() => {});
|
|
65
71
|
await page.waitForTimeout(5000);
|
|
66
72
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
const postMsg = async (msg) => {
|
|
74
|
+
await page.evaluate((m) => {
|
|
75
|
+
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
76
|
+
if (f) f.contentWindow.postMessage(m, '*');
|
|
77
|
+
}, typeof msg === 'string' ? msg : JSON.stringify(msg));
|
|
78
|
+
};
|
|
73
79
|
|
|
74
|
-
await
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
await postMsg('{"event":"listening"}');
|
|
81
|
+
await page.waitForTimeout(2000);
|
|
82
|
+
await postMsg({ event: 'command', func: 'mute', args: [] });
|
|
83
|
+
await page.waitForTimeout(1000);
|
|
84
|
+
await postMsg({ event: 'command', func: 'setPlaybackRate', args: [2] });
|
|
85
|
+
await page.waitForTimeout(2000);
|
|
86
|
+
await postMsg({ event: 'command', func: 'playVideo', args: [] });
|
|
87
|
+
await page.waitForTimeout(5000);
|
|
88
|
+
|
|
89
|
+
rate = await page.evaluate(() => {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
92
|
+
if (!f) { resolve(1); return; }
|
|
93
|
+
let resolved = false;
|
|
94
|
+
const handler = (e) => {
|
|
95
|
+
try {
|
|
96
|
+
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
97
|
+
if (d.event === 'infoDelivery' && d.info && d.info.playbackRate !== undefined && !resolved) {
|
|
98
|
+
resolved = true;
|
|
99
|
+
window.removeEventListener('message', handler);
|
|
100
|
+
resolve(d.info.playbackRate);
|
|
101
|
+
}
|
|
102
|
+
} catch(e) {}
|
|
103
|
+
};
|
|
104
|
+
window.addEventListener('message', handler);
|
|
105
|
+
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getPlaybackRate', args: [] }), '*');
|
|
106
|
+
setTimeout(() => { if (!resolved) { resolved = true; window.removeEventListener('message', handler); resolve(1); } }, 5000);
|
|
107
|
+
});
|
|
78
108
|
});
|
|
79
|
-
|
|
109
|
+
log(chalk.blue(` Speed: ${rate}x`));
|
|
80
110
|
|
|
81
111
|
const isPlaying = await page.evaluate(() => {
|
|
82
112
|
return new Promise((resolve) => {
|
|
83
113
|
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
84
114
|
if (!f) { resolve(false); return; }
|
|
115
|
+
let resolved = false;
|
|
85
116
|
const handler = (e) => {
|
|
86
117
|
try {
|
|
87
118
|
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
88
|
-
if (d.event === 'infoDelivery' && d.info) {
|
|
119
|
+
if (d.event === 'infoDelivery' && d.info && !resolved) {
|
|
120
|
+
resolved = true;
|
|
89
121
|
window.removeEventListener('message', handler);
|
|
90
122
|
resolve(d.info.currentTime > 0);
|
|
91
123
|
}
|
|
@@ -93,67 +125,52 @@ async function watchYouTube(page, stats) {
|
|
|
93
125
|
};
|
|
94
126
|
window.addEventListener('message', handler);
|
|
95
127
|
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getCurrentTime', args: [] }), '*');
|
|
96
|
-
setTimeout(() => { window.removeEventListener('message', handler); resolve(false); }, 5000);
|
|
128
|
+
setTimeout(() => { if (!resolved) { resolved = true; window.removeEventListener('message', handler); resolve(false); } }, 5000);
|
|
97
129
|
});
|
|
98
130
|
});
|
|
99
131
|
|
|
100
|
-
if (
|
|
101
|
-
log(chalk.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await frame.locator('.ytp-large-play-button, .ytp-play-button').first().click({ timeout: 5000 }).catch(() => {});
|
|
105
|
-
} catch(e) {}
|
|
106
|
-
await page.waitForTimeout(3000);
|
|
107
|
-
|
|
132
|
+
if (isPlaying) {
|
|
133
|
+
log(chalk.green(' Playing ✓'));
|
|
134
|
+
} else {
|
|
135
|
+
log(chalk.yellow(' Not playing, retrying...'));
|
|
108
136
|
await page.evaluate(() => {
|
|
109
137
|
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
110
138
|
if (!f) return;
|
|
111
139
|
f.contentWindow.postMessage('{"event":"listening"}', '*');
|
|
112
140
|
});
|
|
113
|
-
await page.waitForTimeout(
|
|
141
|
+
await page.waitForTimeout(3000);
|
|
114
142
|
await page.evaluate(() => {
|
|
115
143
|
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
116
144
|
if (!f) return;
|
|
117
145
|
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'playVideo', args: [] }), '*');
|
|
118
146
|
});
|
|
119
|
-
await page.waitForTimeout(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const handler = (e) => {
|
|
141
|
-
try {
|
|
142
|
-
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
143
|
-
if (d.event === 'infoDelivery' && d.info && d.info.playbackRate) {
|
|
144
|
-
window.removeEventListener('message', handler);
|
|
145
|
-
resolve(d.info.playbackRate);
|
|
146
|
-
}
|
|
147
|
-
} catch(e) {}
|
|
148
|
-
};
|
|
149
|
-
window.addEventListener('message', handler);
|
|
150
|
-
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getPlaybackRate', args: [] }), '*');
|
|
151
|
-
setTimeout(() => { window.removeEventListener('message', handler); resolve(1); }, 3000);
|
|
147
|
+
await page.waitForTimeout(5000);
|
|
148
|
+
|
|
149
|
+
const retry = await page.evaluate(() => {
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
const f = document.querySelector('iframe[src*="youtube"], iframe[src*="youtu.be"]');
|
|
152
|
+
if (!f) { resolve(false); return; }
|
|
153
|
+
let resolved = false;
|
|
154
|
+
const handler = (e) => {
|
|
155
|
+
try {
|
|
156
|
+
const d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
|
|
157
|
+
if (d.event === 'infoDelivery' && d.info && !resolved) {
|
|
158
|
+
resolved = true;
|
|
159
|
+
window.removeEventListener('message', handler);
|
|
160
|
+
resolve(d.info.currentTime > 0);
|
|
161
|
+
}
|
|
162
|
+
} catch(e) {}
|
|
163
|
+
};
|
|
164
|
+
window.addEventListener('message', handler);
|
|
165
|
+
f.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'getCurrentTime', args: [] }), '*');
|
|
166
|
+
setTimeout(() => { if (!resolved) { resolved = true; window.removeEventListener('message', handler); resolve(false); } }, 5000);
|
|
167
|
+
});
|
|
152
168
|
});
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
if (retry) log(chalk.green(' Playing ✓'));
|
|
170
|
+
else log(chalk.yellow(' Could not verify playback'));
|
|
171
|
+
}
|
|
155
172
|
} catch (e) {
|
|
156
|
-
log(chalk.yellow(` YouTube
|
|
173
|
+
log(chalk.yellow(` YouTube error: ${e.message}`));
|
|
157
174
|
}
|
|
158
175
|
|
|
159
176
|
await page.waitForTimeout(2000);
|
|
@@ -167,8 +184,8 @@ async function watchYouTube(page, stats) {
|
|
|
167
184
|
return 180;
|
|
168
185
|
});
|
|
169
186
|
|
|
170
|
-
const waitTime = ytDuration + 30;
|
|
171
|
-
log(chalk.blue(` ~${formatTime(ytDuration)} video → ~${formatTime(waitTime)} (
|
|
187
|
+
const waitTime = (rate >= 2 ? Math.ceil(ytDuration / 2) : ytDuration) + 30;
|
|
188
|
+
log(chalk.blue(` ~${formatTime(ytDuration)} video → ~${formatTime(waitTime)} (${rate}x)`));
|
|
172
189
|
|
|
173
190
|
if (stats) {
|
|
174
191
|
stats.currentVideoDuration = ytDuration;
|