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