@mo7yw4ng/openape 1.0.3 → 1.0.5
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/README.md +30 -5
- package/esm/deno.js +1 -1
- package/esm/src/commands/announcements.d.ts.map +1 -1
- package/esm/src/commands/announcements.js +16 -17
- package/esm/src/commands/assignments.d.ts +3 -0
- package/esm/src/commands/assignments.d.ts.map +1 -0
- package/esm/src/commands/assignments.js +230 -0
- package/esm/src/commands/auth.d.ts.map +1 -1
- package/esm/src/commands/auth.js +45 -15
- package/esm/src/commands/calendar.d.ts.map +1 -1
- package/esm/src/commands/calendar.js +20 -21
- package/esm/src/commands/courses.js +6 -6
- package/esm/src/commands/forums.d.ts.map +1 -1
- package/esm/src/commands/forums.js +128 -36
- package/esm/src/commands/grades.js +3 -3
- package/esm/src/commands/materials.d.ts.map +1 -1
- package/esm/src/commands/materials.js +115 -224
- package/esm/src/commands/quizzes.d.ts.map +1 -1
- package/esm/src/commands/quizzes.js +179 -68
- package/esm/src/commands/skills.d.ts.map +1 -1
- package/esm/src/commands/skills.js +4 -8
- package/esm/src/commands/upload.d.ts +3 -0
- package/esm/src/commands/upload.d.ts.map +1 -0
- package/esm/src/commands/upload.js +58 -0
- package/esm/src/commands/videos.d.ts.map +1 -1
- package/esm/src/commands/videos.js +10 -9
- package/esm/src/index.d.ts.map +1 -1
- package/esm/src/index.js +12 -1
- package/esm/src/lib/auth.d.ts +23 -1
- package/esm/src/lib/auth.d.ts.map +1 -1
- package/esm/src/lib/auth.js +36 -3
- package/esm/src/lib/logger.d.ts +1 -1
- package/esm/src/lib/logger.d.ts.map +1 -1
- package/esm/src/lib/logger.js +7 -4
- package/esm/src/lib/moodle.d.ts +183 -1
- package/esm/src/lib/moodle.d.ts.map +1 -1
- package/esm/src/lib/moodle.js +498 -13
- package/esm/src/lib/types.d.ts +81 -164
- package/esm/src/lib/types.d.ts.map +1 -1
- package/esm/src/lib/types.js +1 -0
- package/esm/src/lib/utils.d.ts +20 -0
- package/esm/src/lib/utils.d.ts.map +1 -1
- package/esm/src/lib/utils.js +48 -1
- package/package.json +1 -1
- package/script/deno.js +1 -1
- package/script/src/commands/announcements.d.ts.map +1 -1
- package/script/src/commands/announcements.js +15 -16
- package/script/src/commands/assignments.d.ts +3 -0
- package/script/src/commands/assignments.d.ts.map +1 -0
- package/script/src/commands/assignments.js +269 -0
- package/script/src/commands/auth.d.ts.map +1 -1
- package/script/src/commands/auth.js +44 -14
- package/script/src/commands/calendar.d.ts.map +1 -1
- package/script/src/commands/calendar.js +19 -20
- package/script/src/commands/courses.js +5 -5
- package/script/src/commands/forums.d.ts.map +1 -1
- package/script/src/commands/forums.js +128 -36
- package/script/src/commands/grades.js +3 -3
- package/script/src/commands/materials.d.ts.map +1 -1
- package/script/src/commands/materials.js +115 -224
- package/script/src/commands/quizzes.d.ts.map +1 -1
- package/script/src/commands/quizzes.js +177 -66
- package/script/src/commands/skills.d.ts.map +1 -1
- package/script/src/commands/skills.js +4 -8
- package/script/src/commands/upload.d.ts +3 -0
- package/script/src/commands/upload.d.ts.map +1 -0
- package/script/src/commands/upload.js +64 -0
- package/script/src/commands/videos.d.ts.map +1 -1
- package/script/src/commands/videos.js +10 -9
- package/script/src/index.d.ts.map +1 -1
- package/script/src/index.js +12 -1
- package/script/src/lib/auth.d.ts +23 -1
- package/script/src/lib/auth.d.ts.map +1 -1
- package/script/src/lib/auth.js +70 -3
- package/script/src/lib/logger.d.ts +1 -1
- package/script/src/lib/logger.d.ts.map +1 -1
- package/script/src/lib/logger.js +7 -4
- package/script/src/lib/moodle.d.ts +183 -1
- package/script/src/lib/moodle.d.ts.map +1 -1
- package/script/src/lib/moodle.js +511 -13
- package/script/src/lib/types.d.ts +81 -164
- package/script/src/lib/types.d.ts.map +1 -1
- package/script/src/lib/types.js +1 -0
- package/script/src/lib/utils.d.ts +20 -0
- package/script/src/lib/utils.d.ts.map +1 -1
- package/script/src/lib/utils.js +52 -0
- package/skills/openape/SKILL.md +74 -270
|
@@ -1,107 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { launchAuthenticated } from "../lib/auth.js";
|
|
5
|
-
import { extractSessionInfo } from "../lib/session.js";
|
|
6
|
-
import { closeBrowserSafely } from "../lib/auth.js";
|
|
1
|
+
import { getOutputFormat, sanitizeFilename, formatFileSize } from "../lib/utils.js";
|
|
2
|
+
import { getEnrolledCoursesApi, getResourcesByCoursesApi, updateActivityCompletionStatusManually, getSiteInfoApi, moodleApiCall } from "../lib/moodle.js";
|
|
3
|
+
import { createApiContext } from "../lib/auth.js";
|
|
7
4
|
import { formatAndOutput } from "../index.js";
|
|
8
|
-
import { loadWsToken } from "../lib/token.js";
|
|
9
5
|
import path from "node:path";
|
|
10
6
|
import fs from "node:fs";
|
|
11
7
|
export function registerMaterialsCommand(program) {
|
|
12
8
|
const materialsCmd = program.command("materials");
|
|
13
9
|
materialsCmd.description("Material/resource operations");
|
|
14
|
-
//
|
|
15
|
-
async function
|
|
16
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
17
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
18
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
19
|
-
const log = createLogger(opts.verbose, silent);
|
|
20
|
-
const baseDir = getBaseDir();
|
|
21
|
-
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
22
|
-
// Check if session exists
|
|
23
|
-
if (!fs.existsSync(sessionPath)) {
|
|
24
|
-
log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
|
|
25
|
-
log.info(`Session 預期位置: ${sessionPath}`);
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
// Try to load WS token
|
|
29
|
-
const wsToken = loadWsToken(sessionPath);
|
|
30
|
-
if (!wsToken) {
|
|
31
|
-
log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
log,
|
|
36
|
-
session: {
|
|
37
|
-
wsToken,
|
|
38
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
// Helper function to create session context (for download commands)
|
|
43
|
-
async function createSessionContext(options, command) {
|
|
44
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
45
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
46
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
47
|
-
const log = createLogger(opts.verbose, silent);
|
|
48
|
-
const baseDir = getBaseDir();
|
|
49
|
-
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
50
|
-
if (!fs.existsSync(sessionPath)) {
|
|
51
|
-
log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
const config = {
|
|
55
|
-
username: "",
|
|
56
|
-
password: "",
|
|
57
|
-
courseUrl: "",
|
|
58
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
59
|
-
headless: !options.headed,
|
|
60
|
-
slowMo: 0,
|
|
61
|
-
authStatePath: sessionPath,
|
|
62
|
-
ollamaBaseUrl: "",
|
|
63
|
-
};
|
|
64
|
-
log.info("啟動瀏覽器...");
|
|
65
|
-
const { browser, context, page } = await launchAuthenticated(config, log);
|
|
66
|
-
try {
|
|
67
|
-
const session = await extractSessionInfo(page, config, log);
|
|
68
|
-
return { log, page, session, browser, context };
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
await context.close();
|
|
72
|
-
await browser.close();
|
|
73
|
-
throw err;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// Helper to download a single resource
|
|
77
|
-
async function downloadResource(page, resource, outputDir, log) {
|
|
10
|
+
// Helper to download a single resource via HTTP (no browser needed)
|
|
11
|
+
async function downloadResourceHttp(resource, outputDir, log, token) {
|
|
78
12
|
try {
|
|
79
13
|
// Only download resource type (skip url)
|
|
80
14
|
if (resource.modType !== "resource") {
|
|
81
15
|
log.debug(` Skipping ${resource.modType}: ${resource.name}`);
|
|
82
16
|
return null;
|
|
83
17
|
}
|
|
84
|
-
// Create course directory
|
|
85
18
|
const courseDir = path.join(outputDir, sanitizeFilename(resource.course_name));
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Navigate to resource page
|
|
90
|
-
log.debug(` Downloading: ${resource.name}`);
|
|
91
|
-
await page.goto(resource.url, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
92
|
-
// Try to find download link on the page
|
|
93
|
-
const downloadLinks = await page.$$eval('a[href*="forcedownload=1"]', (links) => links.map((a) => a.href));
|
|
94
|
-
if (downloadLinks.length === 0) {
|
|
95
|
-
log.warn(` No download link found for: ${resource.name}`);
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
// Download the first available file
|
|
99
|
-
const downloadUrl = downloadLinks[0];
|
|
100
|
-
// Extract filename from URL or use resource name
|
|
101
|
-
const urlObj = new URL(downloadUrl);
|
|
102
|
-
const filenameParam = urlObj.searchParams.get("filename");
|
|
103
|
-
let filename = filenameParam || sanitizeFilename(resource.name);
|
|
104
|
-
// Add extension if missing
|
|
19
|
+
await fs.promises.mkdir(courseDir, { recursive: true });
|
|
20
|
+
// Build filename
|
|
21
|
+
let filename = sanitizeFilename(resource.name);
|
|
105
22
|
if (resource.mimetype && !path.extname(filename)) {
|
|
106
23
|
const extMap = {
|
|
107
24
|
"application/pdf": ".pdf",
|
|
@@ -120,27 +37,54 @@ export function registerMaterialsCommand(program) {
|
|
|
120
37
|
}
|
|
121
38
|
}
|
|
122
39
|
const outputPath = path.join(courseDir, filename);
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
40
|
+
// Skip if already exists
|
|
41
|
+
if (fs.existsSync(outputPath)) {
|
|
42
|
+
log.debug(` Skipping (exists): ${filename}`);
|
|
43
|
+
const stats = await fs.promises.stat(outputPath);
|
|
44
|
+
return { filename, path: outputPath, size: stats.size, course_id: resource.course_id, course_name: resource.course_name };
|
|
45
|
+
}
|
|
46
|
+
// Download via HTTP with WS token
|
|
47
|
+
const separator = resource.url.includes("?") ? "&" : "?";
|
|
48
|
+
const downloadUrl = `${resource.url}${separator}token=${token}`;
|
|
49
|
+
log.debug(` Downloading: ${resource.name}`);
|
|
50
|
+
const response = await fetch(downloadUrl);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
log.warn(` Failed to download ${resource.name}: HTTP ${response.status}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
56
|
+
const data = new Uint8Array(arrayBuffer);
|
|
57
|
+
await fs.promises.writeFile(outputPath, data);
|
|
58
|
+
log.success(` Downloaded: ${filename} (${formatFileSize(data.byteLength, 1)} KB)`);
|
|
59
|
+
return { filename, path: outputPath, size: data.byteLength, course_id: resource.course_id, course_name: resource.course_name };
|
|
138
60
|
}
|
|
139
61
|
catch (err) {
|
|
140
62
|
log.warn(` Failed to download ${resource.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
141
63
|
return null;
|
|
142
64
|
}
|
|
143
65
|
}
|
|
66
|
+
// Helper to build material list from API resources
|
|
67
|
+
function buildMaterialsList(courses, apiResources) {
|
|
68
|
+
const courseMap = new Map(courses.map(c => [c.id, c]));
|
|
69
|
+
const materials = [];
|
|
70
|
+
for (const resource of apiResources) {
|
|
71
|
+
const course = courseMap.get(resource.courseId);
|
|
72
|
+
if (course) {
|
|
73
|
+
materials.push({
|
|
74
|
+
course_id: resource.courseId,
|
|
75
|
+
course_name: course.fullname,
|
|
76
|
+
cmid: resource.cmid,
|
|
77
|
+
name: resource.name,
|
|
78
|
+
url: resource.url,
|
|
79
|
+
modType: resource.modType,
|
|
80
|
+
mimetype: resource.mimetype,
|
|
81
|
+
filesize: resource.filesize,
|
|
82
|
+
modified: resource.modified,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return materials;
|
|
87
|
+
}
|
|
144
88
|
materialsCmd
|
|
145
89
|
.command("list-all")
|
|
146
90
|
.description("List all materials/resources across all courses")
|
|
@@ -206,150 +150,97 @@ export function registerMaterialsCommand(program) {
|
|
|
206
150
|
});
|
|
207
151
|
materialsCmd
|
|
208
152
|
.command("download")
|
|
209
|
-
.description("Download all materials from a specific course
|
|
153
|
+
.description("Download all materials from a specific course")
|
|
210
154
|
.argument("<course-id>", "Course ID")
|
|
211
155
|
.option("--output-dir <path>", "Output directory", "./downloads")
|
|
212
156
|
.action(async (courseId, options, command) => {
|
|
213
|
-
const
|
|
214
|
-
if (!
|
|
157
|
+
const apiContext = await createApiContext(options, command);
|
|
158
|
+
if (!apiContext) {
|
|
215
159
|
process.exitCode = 1;
|
|
216
160
|
return;
|
|
217
161
|
}
|
|
218
|
-
const { log,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
name: a.textContent?.trim() || "",
|
|
235
|
-
}));
|
|
236
|
-
});
|
|
237
|
-
for (const link of resourceLinks) {
|
|
238
|
-
const cmidMatch = link.url.match(/id=(\d+)/);
|
|
239
|
-
if (cmidMatch) {
|
|
240
|
-
materials.push({
|
|
241
|
-
course_id: course.id,
|
|
242
|
-
course_name: course.fullname,
|
|
243
|
-
cmid: cmidMatch[1],
|
|
244
|
-
name: link.name,
|
|
245
|
-
url: link.url,
|
|
246
|
-
modType: "resource",
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
log.info(`Found ${materials.length} materials in course: ${course.fullname}`);
|
|
251
|
-
const downloadedFiles = [];
|
|
252
|
-
for (const material of materials) {
|
|
253
|
-
const result = await downloadResource(page, material, options.outputDir, log);
|
|
254
|
-
if (result) {
|
|
255
|
-
downloadedFiles.push(result);
|
|
256
|
-
}
|
|
162
|
+
const { log, session } = apiContext;
|
|
163
|
+
const courses = await getEnrolledCoursesApi(session);
|
|
164
|
+
const course = courses.find((c) => c.id === parseInt(courseId, 10));
|
|
165
|
+
if (!course) {
|
|
166
|
+
log.error(`Course not found: ${courseId}`);
|
|
167
|
+
process.exitCode = 1;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const apiResources = await getResourcesByCoursesApi(session, [course.id]);
|
|
171
|
+
const materials = buildMaterialsList(courses, apiResources);
|
|
172
|
+
log.info(`Found ${materials.length} materials in course: ${course.fullname}`);
|
|
173
|
+
const downloadedFiles = [];
|
|
174
|
+
for (const material of materials) {
|
|
175
|
+
const result = await downloadResourceHttp(material, options.outputDir, log, session.wsToken);
|
|
176
|
+
if (result) {
|
|
177
|
+
downloadedFiles.push(result);
|
|
257
178
|
}
|
|
258
|
-
|
|
179
|
+
}
|
|
180
|
+
const output = {
|
|
181
|
+
status: "success",
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
downloaded_files: downloadedFiles.map(f => ({
|
|
184
|
+
filename: f.filename,
|
|
185
|
+
path: f.path,
|
|
186
|
+
size: f.size,
|
|
187
|
+
course_id: f.course_id,
|
|
188
|
+
course_name: f.course_name,
|
|
189
|
+
})),
|
|
190
|
+
summary: {
|
|
259
191
|
total_materials: materials.length,
|
|
260
192
|
downloaded: downloadedFiles.length,
|
|
261
193
|
skipped: materials.length - downloadedFiles.length,
|
|
262
194
|
total_size: downloadedFiles.reduce((sum, f) => sum + f.size, 0),
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
timestamp: new Date().toISOString(),
|
|
267
|
-
downloaded_files: downloadedFiles.map(f => ({
|
|
268
|
-
filename: f.filename,
|
|
269
|
-
path: f.path,
|
|
270
|
-
size: f.size,
|
|
271
|
-
course_id: f.course_id,
|
|
272
|
-
course_name: f.course_name,
|
|
273
|
-
})),
|
|
274
|
-
summary,
|
|
275
|
-
};
|
|
276
|
-
console.log(JSON.stringify(output));
|
|
277
|
-
}
|
|
278
|
-
finally {
|
|
279
|
-
await closeBrowserSafely(browser, browserContext);
|
|
280
|
-
}
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
console.log(JSON.stringify(output));
|
|
281
198
|
});
|
|
282
199
|
materialsCmd
|
|
283
200
|
.command("download-all")
|
|
284
|
-
.description("Download all materials from all courses
|
|
201
|
+
.description("Download all materials from all courses")
|
|
285
202
|
.option("--output-dir <path>", "Output directory", "./downloads")
|
|
286
203
|
.option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
|
|
287
204
|
.action(async (options, command) => {
|
|
288
|
-
const
|
|
289
|
-
if (!
|
|
205
|
+
const apiContext = await createApiContext(options, command);
|
|
206
|
+
if (!apiContext) {
|
|
290
207
|
process.exitCode = 1;
|
|
291
208
|
return;
|
|
292
209
|
}
|
|
293
|
-
const { log,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
});
|
|
307
|
-
for (const link of resourceLinks) {
|
|
308
|
-
const cmidMatch = link.url.match(/id=(\d+)/);
|
|
309
|
-
if (cmidMatch) {
|
|
310
|
-
allMaterials.push({
|
|
311
|
-
course_id: course.id,
|
|
312
|
-
course_name: course.fullname,
|
|
313
|
-
cmid: cmidMatch[1],
|
|
314
|
-
name: link.name,
|
|
315
|
-
url: link.url,
|
|
316
|
-
modType: "resource",
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
log.info(`Found ${allMaterials.length} materials across ${courses.length} courses`);
|
|
322
|
-
const downloadedFiles = [];
|
|
323
|
-
for (const material of allMaterials) {
|
|
324
|
-
const result = await downloadResource(page, material, options.outputDir, log);
|
|
325
|
-
if (result) {
|
|
326
|
-
downloadedFiles.push(result);
|
|
327
|
-
}
|
|
210
|
+
const { log, session } = apiContext;
|
|
211
|
+
const classification = options.level === "all" ? undefined : "inprogress";
|
|
212
|
+
const courses = await getEnrolledCoursesApi(session, { classification });
|
|
213
|
+
log.info(`Scanning ${courses.length} courses for materials...`);
|
|
214
|
+
const courseIds = courses.map((c) => c.id);
|
|
215
|
+
const apiResources = await getResourcesByCoursesApi(session, courseIds);
|
|
216
|
+
const allMaterials = buildMaterialsList(courses, apiResources);
|
|
217
|
+
log.info(`Found ${allMaterials.length} materials across ${courses.length} courses`);
|
|
218
|
+
const downloadedFiles = [];
|
|
219
|
+
for (const material of allMaterials) {
|
|
220
|
+
const result = await downloadResourceHttp(material, options.outputDir, log, session.wsToken);
|
|
221
|
+
if (result) {
|
|
222
|
+
downloadedFiles.push(result);
|
|
328
223
|
}
|
|
329
|
-
|
|
224
|
+
}
|
|
225
|
+
const output = {
|
|
226
|
+
status: "success",
|
|
227
|
+
timestamp: new Date().toISOString(),
|
|
228
|
+
downloaded_files: downloadedFiles.map(f => ({
|
|
229
|
+
filename: f.filename,
|
|
230
|
+
path: f.path,
|
|
231
|
+
size: f.size,
|
|
232
|
+
course_id: f.course_id,
|
|
233
|
+
course_name: f.course_name,
|
|
234
|
+
})),
|
|
235
|
+
summary: {
|
|
330
236
|
total_courses: courses.length,
|
|
331
237
|
total_materials: allMaterials.length,
|
|
332
238
|
downloaded: downloadedFiles.length,
|
|
333
239
|
skipped: allMaterials.length - downloadedFiles.length,
|
|
334
240
|
total_size: downloadedFiles.reduce((sum, f) => sum + f.size, 0),
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
timestamp: new Date().toISOString(),
|
|
339
|
-
downloaded_files: downloadedFiles.map(f => ({
|
|
340
|
-
filename: f.filename,
|
|
341
|
-
path: f.path,
|
|
342
|
-
size: f.size,
|
|
343
|
-
course_id: f.course_id,
|
|
344
|
-
course_name: f.course_name,
|
|
345
|
-
})),
|
|
346
|
-
summary,
|
|
347
|
-
};
|
|
348
|
-
console.log(JSON.stringify(output));
|
|
349
|
-
}
|
|
350
|
-
finally {
|
|
351
|
-
await closeBrowserSafely(browser, browserContext);
|
|
352
|
-
}
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
console.log(JSON.stringify(output));
|
|
353
244
|
});
|
|
354
245
|
materialsCmd
|
|
355
246
|
.command("complete")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quizzes.d.ts","sourceRoot":"","sources":["../../../src/src/commands/quizzes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"quizzes.d.ts","sourceRoot":"","sources":["../../../src/src/commands/quizzes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyEpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoQ7D"}
|