@mo7yw4ng/openape 1.0.6 → 2.0.3

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 (152) hide show
  1. package/bin/openape +29 -0
  2. package/bin/openape.js +29 -0
  3. package/package.json +22 -28
  4. package/LICENSE +0 -21
  5. package/README.md +0 -138
  6. package/esm/_dnt.polyfills.d.ts +0 -101
  7. package/esm/_dnt.polyfills.d.ts.map +0 -1
  8. package/esm/_dnt.polyfills.js +0 -127
  9. package/esm/_dnt.shims.d.ts +0 -6
  10. package/esm/_dnt.shims.d.ts.map +0 -1
  11. package/esm/_dnt.shims.js +0 -61
  12. package/esm/deno.d.ts +0 -25
  13. package/esm/deno.d.ts.map +0 -1
  14. package/esm/deno.js +0 -23
  15. package/esm/package.json +0 -3
  16. package/esm/src/commands/announcements.d.ts +0 -3
  17. package/esm/src/commands/announcements.d.ts.map +0 -1
  18. package/esm/src/commands/announcements.js +0 -71
  19. package/esm/src/commands/assignments.d.ts +0 -3
  20. package/esm/src/commands/assignments.d.ts.map +0 -1
  21. package/esm/src/commands/assignments.js +0 -229
  22. package/esm/src/commands/auth.d.ts +0 -3
  23. package/esm/src/commands/auth.d.ts.map +0 -1
  24. package/esm/src/commands/auth.js +0 -290
  25. package/esm/src/commands/calendar.d.ts +0 -3
  26. package/esm/src/commands/calendar.d.ts.map +0 -1
  27. package/esm/src/commands/calendar.js +0 -127
  28. package/esm/src/commands/courses.d.ts +0 -3
  29. package/esm/src/commands/courses.d.ts.map +0 -1
  30. package/esm/src/commands/courses.js +0 -312
  31. package/esm/src/commands/forums.d.ts +0 -3
  32. package/esm/src/commands/forums.d.ts.map +0 -1
  33. package/esm/src/commands/forums.js +0 -190
  34. package/esm/src/commands/grades.d.ts +0 -3
  35. package/esm/src/commands/grades.d.ts.map +0 -1
  36. package/esm/src/commands/grades.js +0 -84
  37. package/esm/src/commands/materials.d.ts +0 -3
  38. package/esm/src/commands/materials.d.ts.map +0 -1
  39. package/esm/src/commands/materials.js +0 -402
  40. package/esm/src/commands/quizzes.d.ts +0 -3
  41. package/esm/src/commands/quizzes.d.ts.map +0 -1
  42. package/esm/src/commands/quizzes.js +0 -236
  43. package/esm/src/commands/skills.d.ts +0 -3
  44. package/esm/src/commands/skills.d.ts.map +0 -1
  45. package/esm/src/commands/skills.js +0 -106
  46. package/esm/src/commands/upload.d.ts +0 -3
  47. package/esm/src/commands/upload.d.ts.map +0 -1
  48. package/esm/src/commands/upload.js +0 -55
  49. package/esm/src/commands/videos.d.ts +0 -3
  50. package/esm/src/commands/videos.d.ts.map +0 -1
  51. package/esm/src/commands/videos.js +0 -266
  52. package/esm/src/index.d.ts +0 -28
  53. package/esm/src/index.d.ts.map +0 -1
  54. package/esm/src/index.js +0 -164
  55. package/esm/src/lib/auth.d.ts +0 -66
  56. package/esm/src/lib/auth.d.ts.map +0 -1
  57. package/esm/src/lib/auth.js +0 -286
  58. package/esm/src/lib/config.d.ts +0 -6
  59. package/esm/src/lib/config.d.ts.map +0 -1
  60. package/esm/src/lib/config.js +0 -36
  61. package/esm/src/lib/logger.d.ts +0 -3
  62. package/esm/src/lib/logger.d.ts.map +0 -1
  63. package/esm/src/lib/logger.js +0 -26
  64. package/esm/src/lib/moodle.d.ts +0 -447
  65. package/esm/src/lib/moodle.d.ts.map +0 -1
  66. package/esm/src/lib/moodle.js +0 -1353
  67. package/esm/src/lib/session.d.ts +0 -8
  68. package/esm/src/lib/session.d.ts.map +0 -1
  69. package/esm/src/lib/session.js +0 -42
  70. package/esm/src/lib/token.d.ts +0 -38
  71. package/esm/src/lib/token.d.ts.map +0 -1
  72. package/esm/src/lib/token.js +0 -178
  73. package/esm/src/lib/types.d.ts +0 -189
  74. package/esm/src/lib/types.d.ts.map +0 -1
  75. package/esm/src/lib/types.js +0 -2
  76. package/esm/src/lib/utils.d.ts +0 -52
  77. package/esm/src/lib/utils.d.ts.map +0 -1
  78. package/esm/src/lib/utils.js +0 -122
  79. package/script/_dnt.polyfills.d.ts +0 -101
  80. package/script/_dnt.polyfills.d.ts.map +0 -1
  81. package/script/_dnt.polyfills.js +0 -130
  82. package/script/_dnt.shims.d.ts +0 -6
  83. package/script/_dnt.shims.d.ts.map +0 -1
  84. package/script/_dnt.shims.js +0 -65
  85. package/script/deno.d.ts +0 -25
  86. package/script/deno.d.ts.map +0 -1
  87. package/script/deno.js +0 -25
  88. package/script/package.json +0 -3
  89. package/script/src/commands/announcements.d.ts +0 -3
  90. package/script/src/commands/announcements.d.ts.map +0 -1
  91. package/script/src/commands/announcements.js +0 -74
  92. package/script/src/commands/assignments.d.ts +0 -3
  93. package/script/src/commands/assignments.d.ts.map +0 -1
  94. package/script/src/commands/assignments.js +0 -268
  95. package/script/src/commands/auth.d.ts +0 -3
  96. package/script/src/commands/auth.d.ts.map +0 -1
  97. package/script/src/commands/auth.js +0 -296
  98. package/script/src/commands/calendar.d.ts +0 -3
  99. package/script/src/commands/calendar.d.ts.map +0 -1
  100. package/script/src/commands/calendar.js +0 -133
  101. package/script/src/commands/courses.d.ts +0 -3
  102. package/script/src/commands/courses.d.ts.map +0 -1
  103. package/script/src/commands/courses.js +0 -315
  104. package/script/src/commands/forums.d.ts +0 -3
  105. package/script/src/commands/forums.d.ts.map +0 -1
  106. package/script/src/commands/forums.js +0 -193
  107. package/script/src/commands/grades.d.ts +0 -3
  108. package/script/src/commands/grades.d.ts.map +0 -1
  109. package/script/src/commands/grades.js +0 -87
  110. package/script/src/commands/materials.d.ts +0 -3
  111. package/script/src/commands/materials.d.ts.map +0 -1
  112. package/script/src/commands/materials.js +0 -408
  113. package/script/src/commands/quizzes.d.ts +0 -3
  114. package/script/src/commands/quizzes.d.ts.map +0 -1
  115. package/script/src/commands/quizzes.js +0 -239
  116. package/script/src/commands/skills.d.ts +0 -3
  117. package/script/src/commands/skills.d.ts.map +0 -1
  118. package/script/src/commands/skills.js +0 -112
  119. package/script/src/commands/upload.d.ts +0 -3
  120. package/script/src/commands/upload.d.ts.map +0 -1
  121. package/script/src/commands/upload.js +0 -61
  122. package/script/src/commands/videos.d.ts +0 -3
  123. package/script/src/commands/videos.d.ts.map +0 -1
  124. package/script/src/commands/videos.js +0 -272
  125. package/script/src/index.d.ts +0 -28
  126. package/script/src/index.d.ts.map +0 -1
  127. package/script/src/index.js +0 -171
  128. package/script/src/lib/auth.d.ts +0 -66
  129. package/script/src/lib/auth.d.ts.map +0 -1
  130. package/script/src/lib/auth.js +0 -296
  131. package/script/src/lib/config.d.ts +0 -6
  132. package/script/src/lib/config.d.ts.map +0 -1
  133. package/script/src/lib/config.js +0 -42
  134. package/script/src/lib/logger.d.ts +0 -3
  135. package/script/src/lib/logger.d.ts.map +0 -1
  136. package/script/src/lib/logger.js +0 -29
  137. package/script/src/lib/moodle.d.ts +0 -447
  138. package/script/src/lib/moodle.d.ts.map +0 -1
  139. package/script/src/lib/moodle.js +0 -1425
  140. package/script/src/lib/session.d.ts +0 -8
  141. package/script/src/lib/session.d.ts.map +0 -1
  142. package/script/src/lib/session.js +0 -45
  143. package/script/src/lib/token.d.ts +0 -38
  144. package/script/src/lib/token.d.ts.map +0 -1
  145. package/script/src/lib/token.js +0 -189
  146. package/script/src/lib/types.d.ts +0 -189
  147. package/script/src/lib/types.d.ts.map +0 -1
  148. package/script/src/lib/types.js +0 -3
  149. package/script/src/lib/utils.d.ts +0 -52
  150. package/script/src/lib/utils.d.ts.map +0 -1
  151. package/script/src/lib/utils.js +0 -167
  152. package/skills/openape/SKILL.md +0 -115
@@ -1,84 +0,0 @@
1
- import { getEnrolledCoursesApi, getCourseGradesApi } from "../lib/moodle.js";
2
- import { createApiContext } from "../lib/auth.js";
3
- import { formatAndOutput } from "../index.js";
4
- import { getOutputFormat } from "../lib/utils.js";
5
- export function registerGradesCommand(program) {
6
- const gradesCmd = program.command("grades");
7
- gradesCmd.description("Grade operations");
8
- gradesCmd
9
- .command("summary")
10
- .description("Show grade summary across all courses")
11
- .option("--output <format>", "Output format: json|csv|table|silent")
12
- .action(async (options, command) => {
13
- const output = getOutputFormat(command);
14
- const apiContext = await createApiContext(options, command);
15
- if (!apiContext) {
16
- process.exitCode = 1;
17
- return;
18
- }
19
- const courses = await getEnrolledCoursesApi(apiContext.session);
20
- const gradeResults = await Promise.allSettled(courses.map(course => getCourseGradesApi(apiContext.session, course.id)
21
- .then(grades => ({ course, grades }))));
22
- const gradeSummaries = [];
23
- for (const result of gradeResults) {
24
- if (result.status !== "fulfilled")
25
- continue;
26
- const { course, grades } = result.value;
27
- gradeSummaries.push({
28
- courseId: course.id,
29
- courseName: course.fullname,
30
- grade: grades.grade,
31
- gradeFormatted: grades.gradeFormatted,
32
- rank: grades.rank,
33
- totalUsers: grades.totalUsers,
34
- });
35
- }
36
- const gradedCourses = gradeSummaries.filter(g => g.grade !== undefined && g.grade !== null && g.grade !== "-");
37
- const averageRank = gradeSummaries
38
- .filter(g => g.rank !== undefined && g.rank !== null)
39
- .reduce((sum, g) => sum + (g.rank || 0), 0) /
40
- (gradeSummaries.filter(g => g.rank !== undefined && g.rank !== null).length || 1);
41
- apiContext.log.info(`Total: ${courses.length} courses, ${gradedCourses.length} graded, avg rank: ${averageRank.toFixed(1)}`);
42
- formatAndOutput(gradeSummaries, output, apiContext.log);
43
- });
44
- gradesCmd
45
- .command("course")
46
- .description("Show detailed grades for a specific course")
47
- .argument("<course-id>", "Course ID")
48
- .option("--output <format>", "Output format: json|csv|table|silent")
49
- .action(async (courseId, options, command) => {
50
- const output = getOutputFormat(command);
51
- const apiContext = await createApiContext(options, command);
52
- if (!apiContext) {
53
- process.exitCode = 1;
54
- return;
55
- }
56
- const courses = await getEnrolledCoursesApi(apiContext.session);
57
- const course = courses.find(c => c.id === parseInt(courseId, 10));
58
- if (!course) {
59
- apiContext.log.error(`Course not found: ${courseId}`);
60
- process.exitCode = 1;
61
- return;
62
- }
63
- const grades = await getCourseGradesApi(apiContext.session, course.id);
64
- const gradeData = {
65
- courseId: grades.courseId,
66
- courseName: grades.courseName,
67
- grade: grades.grade,
68
- gradeFormatted: grades.gradeFormatted,
69
- rank: grades.rank,
70
- totalUsers: grades.totalUsers,
71
- items: grades.items?.map(item => ({
72
- name: item.name,
73
- grade: item.grade,
74
- gradeFormatted: item.gradeFormatted,
75
- range: item.range,
76
- percentage: item.percentage,
77
- weight: item.weight,
78
- feedback: item.feedback,
79
- graded: item.graded,
80
- })),
81
- };
82
- formatAndOutput(gradeData, output, apiContext.log);
83
- });
84
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerMaterialsCommand(program: Command): void;
3
- //# sourceMappingURL=materials.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../../src/src/commands/materials.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwd/D"}
@@ -1,402 +0,0 @@
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";
4
- import { formatAndOutput } from "../index.js";
5
- import path from "node:path";
6
- import fs from "node:fs";
7
- export function registerMaterialsCommand(program) {
8
- const materialsCmd = program.command("materials");
9
- materialsCmd.description("Material/resource operations");
10
- // Helper to download a single resource via HTTP (no browser needed)
11
- async function downloadResourceHttp(resource, outputDir, log, token) {
12
- try {
13
- // Only download resource type (skip url)
14
- if (resource.modType !== "resource") {
15
- log.debug(` Skipping ${resource.modType}: ${resource.name}`);
16
- return null;
17
- }
18
- const courseDir = path.join(outputDir, sanitizeFilename(resource.course_name));
19
- await fs.promises.mkdir(courseDir, { recursive: true });
20
- // Build filename
21
- let filename = sanitizeFilename(resource.name);
22
- if (resource.mimetype && !path.extname(filename)) {
23
- const extMap = {
24
- "application/pdf": ".pdf",
25
- "application/vnd.ms-powerpoint": ".ppt",
26
- "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
27
- "application/msword": ".doc",
28
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
29
- "application/vnd.ms-excel": ".xls",
30
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
31
- "application/zip": ".zip",
32
- "image/jpeg": ".jpg",
33
- "image/png": ".png",
34
- };
35
- if (extMap[resource.mimetype]) {
36
- filename += extMap[resource.mimetype];
37
- }
38
- }
39
- const outputPath = path.join(courseDir, filename);
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 };
60
- }
61
- catch (err) {
62
- log.warn(` Failed to download ${resource.name}: ${err instanceof Error ? err.message : String(err)}`);
63
- return null;
64
- }
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
- }
88
- materialsCmd
89
- .command("list-all")
90
- .description("List all materials/resources across all courses")
91
- .option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
92
- .option("--output <format>", "Output format: json|csv|table|silent")
93
- .action(async (options, command) => {
94
- const output = getOutputFormat(command);
95
- const apiContext = await createApiContext(options, command);
96
- if (!apiContext) {
97
- process.exitCode = 1;
98
- return;
99
- }
100
- const classification = options.level === "all" ? undefined : "inprogress";
101
- const courses = await getEnrolledCoursesApi(apiContext.session, {
102
- classification,
103
- });
104
- const courseIds = courses.map(c => c.id);
105
- const apiResources = await getResourcesByCoursesApi(apiContext.session, courseIds);
106
- const courseMap = new Map(courses.map(c => [c.id, c]));
107
- const allMaterials = [];
108
- for (const resource of apiResources) {
109
- const course = courseMap.get(resource.courseId);
110
- if (course) {
111
- allMaterials.push({
112
- course_id: resource.courseId,
113
- course_name: course.fullname,
114
- cmid: resource.cmid,
115
- name: resource.name,
116
- url: resource.url,
117
- modType: resource.modType,
118
- mimetype: resource.mimetype,
119
- filesize: resource.filesize,
120
- modified: resource.modified,
121
- });
122
- }
123
- }
124
- const items = allMaterials.map(m => ({
125
- course_id: m.course_id,
126
- course_name: m.course_name,
127
- id: m.cmid,
128
- name: m.name,
129
- type: m.modType,
130
- mimetype: m.mimetype,
131
- filesize: m.filesize,
132
- modified: m.modified ? new Date(m.modified * 1000).toISOString() : null,
133
- url: m.url,
134
- }));
135
- formatAndOutput(items, output, apiContext.log, {
136
- status: "success",
137
- timestamp: new Date().toISOString(),
138
- total_courses: courses.length,
139
- total_materials: allMaterials.length,
140
- by_type: allMaterials.reduce((acc, m) => {
141
- acc[m.modType] = (acc[m.modType] || 0) + 1;
142
- return acc;
143
- }, {}),
144
- });
145
- });
146
- materialsCmd
147
- .command("download")
148
- .description("Download all materials from a specific course")
149
- .argument("<course-id>", "Course ID")
150
- .option("--output-dir <path>", "Output directory", "./downloads")
151
- .action(async (courseId, options, command) => {
152
- const apiContext = await createApiContext(options, command);
153
- if (!apiContext) {
154
- process.exitCode = 1;
155
- return;
156
- }
157
- const { log, session } = apiContext;
158
- const courses = await getEnrolledCoursesApi(session);
159
- const course = courses.find((c) => c.id === parseInt(courseId, 10));
160
- if (!course) {
161
- log.error(`Course not found: ${courseId}`);
162
- process.exitCode = 1;
163
- return;
164
- }
165
- const apiResources = await getResourcesByCoursesApi(session, [course.id]);
166
- const materials = buildMaterialsList(courses, apiResources);
167
- log.info(`Found ${materials.length} materials in course: ${course.fullname}`);
168
- const downloadedFiles = [];
169
- for (const material of materials) {
170
- const result = await downloadResourceHttp(material, options.outputDir, log, session.wsToken);
171
- if (result) {
172
- downloadedFiles.push(result);
173
- }
174
- }
175
- const items = downloadedFiles.map(f => ({
176
- filename: f.filename,
177
- path: f.path,
178
- size: f.size,
179
- course_id: f.course_id,
180
- course_name: f.course_name,
181
- }));
182
- formatAndOutput(items, "json", log, {
183
- status: "success",
184
- timestamp: new Date().toISOString(),
185
- total_materials: materials.length,
186
- downloaded: downloadedFiles.length,
187
- skipped: materials.length - downloadedFiles.length,
188
- total_size: downloadedFiles.reduce((sum, f) => sum + f.size, 0),
189
- });
190
- });
191
- materialsCmd
192
- .command("download-all")
193
- .description("Download all materials from all courses")
194
- .option("--output-dir <path>", "Output directory", "./downloads")
195
- .option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
196
- .action(async (options, command) => {
197
- const apiContext = await createApiContext(options, command);
198
- if (!apiContext) {
199
- process.exitCode = 1;
200
- return;
201
- }
202
- const { log, session } = apiContext;
203
- const classification = options.level === "all" ? undefined : "inprogress";
204
- const courses = await getEnrolledCoursesApi(session, { classification });
205
- log.info(`Scanning ${courses.length} courses for materials...`);
206
- const courseIds = courses.map((c) => c.id);
207
- const apiResources = await getResourcesByCoursesApi(session, courseIds);
208
- const allMaterials = buildMaterialsList(courses, apiResources);
209
- log.info(`Found ${allMaterials.length} materials across ${courses.length} courses`);
210
- const downloadedFiles = [];
211
- for (const material of allMaterials) {
212
- const result = await downloadResourceHttp(material, options.outputDir, log, session.wsToken);
213
- if (result) {
214
- downloadedFiles.push(result);
215
- }
216
- }
217
- const items = downloadedFiles.map(f => ({
218
- filename: f.filename,
219
- path: f.path,
220
- size: f.size,
221
- course_id: f.course_id,
222
- course_name: f.course_name,
223
- }));
224
- formatAndOutput(items, "json", log, {
225
- status: "success",
226
- timestamp: new Date().toISOString(),
227
- total_courses: courses.length,
228
- total_materials: allMaterials.length,
229
- downloaded: downloadedFiles.length,
230
- skipped: allMaterials.length - downloadedFiles.length,
231
- total_size: downloadedFiles.reduce((sum, f) => sum + f.size, 0),
232
- });
233
- });
234
- materialsCmd
235
- .command("complete")
236
- .description("Mark all incomplete resources (non-video) as complete in a course")
237
- .argument("<course-id>", "Course ID")
238
- .option("--dry-run", "Show what would be marked complete without doing it")
239
- .option("--output <format>", "Output format: json|csv|table|silent")
240
- .action(async (courseId, options, command) => {
241
- const output = getOutputFormat(command);
242
- const apiContext = await createApiContext(options, command);
243
- if (!apiContext) {
244
- process.exitCode = 1;
245
- return;
246
- }
247
- try {
248
- const { log, session } = apiContext;
249
- // Get user ID
250
- const siteInfo = await getSiteInfoApi(session);
251
- // Get completion status for all activities in the course
252
- const completionData = await moodleApiCall(session, "core_completion_get_activities_completion_status", { courseid: parseInt(courseId, 10), userid: siteInfo.userid });
253
- if (!completionData?.statuses) {
254
- log.info("No activities found in this course.");
255
- return;
256
- }
257
- // Filter for resources (non-video) that have completion enabled but are not complete
258
- const incompleteResources = completionData.statuses.filter((status) => {
259
- // Only resources, not supervideo
260
- if (status.modname === "supervideo")
261
- return false;
262
- // Must have completion enabled
263
- if (!status.hascompletion)
264
- return false;
265
- // Must be incomplete
266
- if (status.isoverallcomplete)
267
- return false;
268
- return true;
269
- });
270
- if (incompleteResources.length === 0) {
271
- log.info("All resources are already complete (or no resources with completion tracking).");
272
- return;
273
- }
274
- log.info(`Found ${incompleteResources.length} incomplete resources to complete:`);
275
- for (const resource of incompleteResources) {
276
- log.info(` - ${resource.name} (cmid: ${resource.cmid})`);
277
- }
278
- if (options.dryRun) {
279
- log.info("\n[Dry run] No changes made.");
280
- return;
281
- }
282
- // Mark each resource as complete
283
- const results = [];
284
- for (const resource of incompleteResources) {
285
- const success = await updateActivityCompletionStatusManually(session, resource.cmid, true);
286
- results.push({
287
- cmid: resource.cmid,
288
- name: resource.name,
289
- success,
290
- });
291
- if (success) {
292
- log.success(` ✓ Completed: ${resource.name}`);
293
- }
294
- else {
295
- log.error(` ✗ Failed: ${resource.name}`);
296
- }
297
- }
298
- const completed = results.filter(r => r.success).length;
299
- const failed = results.filter(r => !r.success).length;
300
- log.info(`\n執行結果: ${completed} 成功, ${failed} 失敗`);
301
- if (output !== "silent") {
302
- formatAndOutput(results, output, log);
303
- }
304
- }
305
- catch (e) {
306
- apiContext.log.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
307
- process.exitCode = 1;
308
- }
309
- });
310
- materialsCmd
311
- .command("complete-all")
312
- .description("Mark all incomplete resources (non-video) as complete across all in-progress courses")
313
- .option("--dry-run", "Show what would be marked complete without doing it")
314
- .option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
315
- .option("--output <format>", "Output format: json|csv|table|silent")
316
- .action(async (options, command) => {
317
- const output = getOutputFormat(command);
318
- const apiContext = await createApiContext(options, command);
319
- if (!apiContext) {
320
- process.exitCode = 1;
321
- return;
322
- }
323
- try {
324
- const { log, session } = apiContext;
325
- // Get user ID
326
- const siteInfo = await getSiteInfoApi(session);
327
- // Get all courses
328
- const classification = options.level === "all" ? undefined : "inprogress";
329
- const courses = await getEnrolledCoursesApi(session, { classification });
330
- log.info(`Scanning ${courses.length} courses for incomplete resources...`);
331
- const allResults = [];
332
- let totalIncomplete = 0;
333
- for (const course of courses) {
334
- try {
335
- // Get completion status for all activities in the course
336
- const completionData = await moodleApiCall(session, "core_completion_get_activities_completion_status", { courseid: course.id, userid: siteInfo.userid });
337
- if (!completionData?.statuses)
338
- continue;
339
- // Filter for resources (non-video) that have completion enabled but are not complete
340
- const incompleteResources = completionData.statuses.filter((status) => {
341
- if (status.modname === "supervideo")
342
- return false;
343
- if (!status.hascompletion)
344
- return false;
345
- if (status.isoverallcomplete)
346
- return false;
347
- return true;
348
- });
349
- if (incompleteResources.length > 0) {
350
- log.info(`\n${course.fullname}: ${incompleteResources.length} incomplete resources`);
351
- totalIncomplete += incompleteResources.length;
352
- if (options.dryRun) {
353
- for (const resource of incompleteResources) {
354
- log.info(` - ${resource.name} (cmid: ${resource.cmid})`);
355
- }
356
- }
357
- else {
358
- for (const resource of incompleteResources) {
359
- const success = await updateActivityCompletionStatusManually(session, resource.cmid, true);
360
- allResults.push({
361
- courseId: course.id,
362
- courseName: course.fullname,
363
- cmid: resource.cmid,
364
- name: resource.name,
365
- success,
366
- });
367
- if (success) {
368
- log.success(` ✓ Completed: ${resource.name}`);
369
- }
370
- else {
371
- log.error(` ✗ Failed: ${resource.name}`);
372
- }
373
- }
374
- }
375
- }
376
- }
377
- catch (e) {
378
- log.warn(`Failed to process course ${course.fullname}: ${e}`);
379
- }
380
- }
381
- if (totalIncomplete === 0) {
382
- log.info("\nAll resources are already complete (or no resources with completion tracking).");
383
- return;
384
- }
385
- if (options.dryRun) {
386
- log.info(`\n[Dry run] Found ${totalIncomplete} incomplete resources across ${courses.length} courses.`);
387
- log.info("Run without --dry-run to mark them as complete.");
388
- return;
389
- }
390
- const completed = allResults.filter(r => r.success).length;
391
- const failed = allResults.filter(r => !r.success).length;
392
- log.info(`\n執行結果: ${completed} 成功, ${failed} 失敗`);
393
- if (output !== "silent") {
394
- formatAndOutput(allResults, output, log);
395
- }
396
- }
397
- catch (e) {
398
- apiContext.log.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
399
- process.exitCode = 1;
400
- }
401
- });
402
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerQuizzesCommand(program: Command): void;
3
- //# sourceMappingURL=quizzes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"quizzes.d.ts","sourceRoot":"","sources":["../../../src/src/commands/quizzes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsEpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2N7D"}