@mo7yw4ng/openape 1.0.0 → 1.0.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.
Files changed (133) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +15 -5
  3. package/esm/_dnt.polyfills.d.ts +83 -6
  4. package/esm/_dnt.polyfills.d.ts.map +1 -0
  5. package/esm/_dnt.polyfills.js +127 -1
  6. package/esm/_dnt.shims.d.ts +1 -0
  7. package/esm/_dnt.shims.d.ts.map +1 -0
  8. package/esm/deno.d.ts +2 -0
  9. package/esm/deno.d.ts.map +1 -0
  10. package/esm/deno.js +2 -1
  11. package/esm/src/commands/announcements.d.ts +3 -0
  12. package/esm/src/commands/announcements.d.ts.map +1 -0
  13. package/esm/src/commands/announcements.js +135 -0
  14. package/esm/src/commands/auth.d.ts +3 -0
  15. package/esm/src/commands/auth.d.ts.map +1 -0
  16. package/esm/src/commands/auth.js +264 -0
  17. package/esm/src/commands/calendar.d.ts +3 -0
  18. package/esm/src/commands/calendar.d.ts.map +1 -0
  19. package/esm/src/commands/calendar.js +180 -0
  20. package/esm/src/commands/courses.d.ts +3 -0
  21. package/esm/src/commands/courses.d.ts.map +1 -0
  22. package/esm/src/commands/courses.js +348 -0
  23. package/esm/src/commands/forums.d.ts +3 -0
  24. package/esm/src/commands/forums.d.ts.map +1 -0
  25. package/esm/src/commands/forums.js +231 -0
  26. package/esm/src/commands/grades.d.ts +3 -0
  27. package/esm/src/commands/grades.d.ts.map +1 -0
  28. package/esm/src/commands/grades.js +121 -0
  29. package/esm/src/commands/materials.d.ts +3 -0
  30. package/esm/src/commands/materials.d.ts.map +1 -0
  31. package/esm/src/commands/materials.js +362 -0
  32. package/esm/src/commands/quizzes.d.ts +3 -0
  33. package/esm/src/commands/quizzes.d.ts.map +1 -0
  34. package/esm/src/commands/quizzes.js +160 -0
  35. package/esm/src/commands/skills.d.ts +3 -0
  36. package/esm/src/commands/skills.d.ts.map +1 -0
  37. package/esm/src/commands/skills.js +110 -0
  38. package/esm/src/commands/videos.d.ts +3 -0
  39. package/esm/src/commands/videos.d.ts.map +1 -0
  40. package/esm/src/commands/videos.js +302 -0
  41. package/esm/src/index.d.ts +27 -0
  42. package/esm/src/index.d.ts.map +1 -0
  43. package/esm/src/index.js +149 -0
  44. package/esm/src/lib/auth.d.ts +25 -0
  45. package/esm/src/lib/auth.d.ts.map +1 -0
  46. package/esm/src/lib/auth.js +194 -0
  47. package/esm/src/lib/config.d.ts +6 -0
  48. package/esm/src/lib/config.d.ts.map +1 -0
  49. package/esm/src/lib/config.js +36 -0
  50. package/esm/src/lib/logger.d.ts +3 -0
  51. package/esm/src/lib/logger.d.ts.map +1 -0
  52. package/esm/src/lib/logger.js +24 -0
  53. package/esm/src/lib/moodle.d.ts +205 -0
  54. package/esm/src/lib/moodle.d.ts.map +1 -0
  55. package/esm/src/lib/moodle.js +690 -0
  56. package/esm/src/lib/session.d.ts +8 -0
  57. package/esm/src/lib/session.d.ts.map +1 -0
  58. package/esm/src/lib/session.js +42 -0
  59. package/esm/src/lib/token.d.ts +38 -0
  60. package/esm/src/lib/token.d.ts.map +1 -0
  61. package/esm/src/lib/token.js +178 -0
  62. package/esm/src/lib/types.d.ts +271 -0
  63. package/esm/src/lib/types.d.ts.map +1 -0
  64. package/esm/src/lib/types.js +1 -0
  65. package/esm/src/lib/utils.d.ts +17 -0
  66. package/esm/src/lib/utils.d.ts.map +1 -0
  67. package/esm/src/lib/utils.js +51 -0
  68. package/package.json +7 -3
  69. package/script/_dnt.polyfills.d.ts +83 -6
  70. package/script/_dnt.polyfills.d.ts.map +1 -0
  71. package/script/_dnt.polyfills.js +128 -0
  72. package/script/_dnt.shims.d.ts +1 -0
  73. package/script/_dnt.shims.d.ts.map +1 -0
  74. package/script/deno.d.ts +2 -0
  75. package/script/deno.d.ts.map +1 -0
  76. package/script/deno.js +2 -1
  77. package/script/src/commands/announcements.d.ts +1 -0
  78. package/script/src/commands/announcements.d.ts.map +1 -0
  79. package/script/src/commands/announcements.js +75 -222
  80. package/script/src/commands/auth.d.ts +1 -0
  81. package/script/src/commands/auth.d.ts.map +1 -0
  82. package/script/src/commands/auth.js +49 -17
  83. package/script/src/commands/calendar.d.ts +1 -0
  84. package/script/src/commands/calendar.d.ts.map +1 -0
  85. package/script/src/commands/calendar.js +112 -301
  86. package/script/src/commands/courses.d.ts +1 -0
  87. package/script/src/commands/courses.d.ts.map +1 -0
  88. package/script/src/commands/courses.js +43 -173
  89. package/script/src/commands/forums.d.ts +1 -0
  90. package/script/src/commands/forums.d.ts.map +1 -0
  91. package/script/src/commands/forums.js +145 -311
  92. package/script/src/commands/grades.d.ts +1 -0
  93. package/script/src/commands/grades.d.ts.map +1 -0
  94. package/script/src/commands/grades.js +62 -194
  95. package/script/src/commands/materials.d.ts +1 -0
  96. package/script/src/commands/materials.d.ts.map +1 -0
  97. package/script/src/commands/materials.js +111 -166
  98. package/script/src/commands/quizzes.d.ts +1 -0
  99. package/script/src/commands/quizzes.d.ts.map +1 -0
  100. package/script/src/commands/quizzes.js +40 -102
  101. package/script/src/commands/skills.d.ts +1 -0
  102. package/script/src/commands/skills.d.ts.map +1 -0
  103. package/script/src/commands/skills.js +17 -18
  104. package/script/src/commands/videos.d.ts +1 -0
  105. package/script/src/commands/videos.d.ts.map +1 -0
  106. package/script/src/commands/videos.js +26 -52
  107. package/script/src/index.d.ts +1 -0
  108. package/script/src/index.d.ts.map +1 -0
  109. package/script/src/index.js +4 -4
  110. package/script/src/lib/auth.d.ts +1 -0
  111. package/script/src/lib/auth.d.ts.map +1 -0
  112. package/script/src/lib/auth.js +9 -10
  113. package/script/src/lib/config.d.ts +1 -0
  114. package/script/src/lib/config.d.ts.map +1 -0
  115. package/script/src/lib/config.js +6 -7
  116. package/script/src/lib/logger.d.ts +1 -0
  117. package/script/src/lib/logger.d.ts.map +1 -0
  118. package/script/src/lib/logger.js +1 -2
  119. package/script/src/lib/moodle.d.ts +25 -54
  120. package/script/src/lib/moodle.d.ts.map +1 -0
  121. package/script/src/lib/moodle.js +103 -324
  122. package/script/src/lib/session.d.ts +1 -0
  123. package/script/src/lib/session.d.ts.map +1 -0
  124. package/script/src/lib/session.js +3 -29
  125. package/script/src/lib/token.d.ts +16 -5
  126. package/script/src/lib/token.d.ts.map +1 -0
  127. package/script/src/lib/token.js +71 -36
  128. package/script/src/lib/types.d.ts +10 -0
  129. package/script/src/lib/types.d.ts.map +1 -0
  130. package/script/src/lib/utils.d.ts +12 -0
  131. package/script/src/lib/utils.d.ts.map +1 -0
  132. package/script/src/lib/utils.js +57 -11
  133. package/skills/openape/SKILL.md +331 -328
@@ -3,17 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.registerForumsCommand = void 0;
6
+ exports.registerForumsCommand = registerForumsCommand;
7
7
  const utils_js_1 = require("../lib/utils.js");
8
8
  const moodle_js_1 = require("../lib/moodle.js");
9
9
  const logger_js_1 = require("../lib/logger.js");
10
- 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
- const index_js_1 = require("../index.js");
14
10
  const token_js_1 = require("../lib/token.js");
15
- const path_1 = __importDefault(require("path"));
16
- const fs_1 = __importDefault(require("fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const node_fs_1 = __importDefault(require("node:fs"));
17
13
  function registerForumsCommand(program) {
18
14
  const forumsCmd = program.command("forums");
19
15
  forumsCmd.description("Forum operations");
@@ -28,324 +24,169 @@ function registerForumsCommand(program) {
28
24
  const silent = outputFormat === "json" && !opts.verbose;
29
25
  const log = (0, logger_js_1.createLogger)(opts.verbose, silent);
30
26
  const baseDir = (0, utils_js_1.getBaseDir)();
31
- const sessionPath = path_1.default.resolve(baseDir, ".auth", "storage-state.json");
27
+ const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
32
28
  // Check if session exists
33
- if (!fs_1.default.existsSync(sessionPath)) {
29
+ if (!node_fs_1.default.existsSync(sessionPath)) {
30
+ log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
31
+ log.info(`Session 預期位置: ${sessionPath}`);
34
32
  return null;
35
33
  }
36
34
  // Try to load WS token
37
35
  const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
38
36
  if (!wsToken) {
37
+ log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
39
38
  return null;
40
39
  }
40
+ // Try to load sesskey from cache
41
+ const sesskey = (0, token_js_1.loadSesskey)(sessionPath) || undefined;
41
42
  return {
42
43
  log,
43
44
  session: {
44
45
  wsToken,
45
46
  moodleBaseUrl: "https://ilearning.cycu.edu.tw",
47
+ sesskey,
46
48
  },
47
49
  };
48
50
  }
49
- // Helper function to create session context
50
- async function createSessionContext(options, command) {
51
- // Get global options if command is provided (for --verbose, --silent flags)
52
- const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
53
- // Auto-enable silent mode for JSON output (unless --verbose is also set)
54
- const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
55
- const silent = outputFormat === "json" && !opts.verbose;
56
- const log = (0, logger_js_1.createLogger)(opts.verbose, silent);
57
- // Determine session path
58
- const baseDir = (0, utils_js_1.getBaseDir)();
59
- const sessionPath = path_1.default.resolve(baseDir, ".auth", "storage-state.json");
60
- // Check if session exists
61
- if (!fs_1.default.existsSync(sessionPath)) {
62
- log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
63
- log.info(`Session 預期位置: ${sessionPath}`);
64
- return null;
65
- }
66
- // Create minimal config
67
- const config = {
68
- username: "",
69
- password: "",
70
- courseUrl: "",
71
- moodleBaseUrl: "https://ilearning.cycu.edu.tw",
72
- headless: !options.headed,
73
- slowMo: 0,
74
- authStatePath: sessionPath,
75
- ollamaBaseUrl: "",
76
- };
77
- log.info("啟動瀏覽器...");
78
- const { browser, context, page, wsToken } = await (0, auth_js_1.launchAuthenticated)(config, log);
79
- try {
80
- const session = await (0, session_js_1.extractSessionInfo)(page, config, log, wsToken);
81
- return { log, page, session, browser, context };
82
- }
83
- catch (err) {
84
- await context.close();
85
- await browser.close();
86
- throw err;
87
- }
88
- }
89
51
  forumsCmd
90
52
  .command("list")
91
53
  .description("List forums from in-progress courses")
92
- .option("--unread-only", "Show only forums with unread discussions")
93
- .option("--fetch-instance", "Fetch forum instance IDs (slower)")
94
54
  .option("--output <format>", "Output format: json|csv|table|silent")
95
55
  .action(async (options, command) => {
96
- const output = getOutputFormat(command);
97
- // Try pure WS API mode (no browser, fast!)
98
56
  const apiContext = await createApiContext(options, command);
99
- if (apiContext) {
100
- try {
101
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
102
- classification: "inprogress",
103
- });
104
- // Get forums via WS API (no browser needed!)
105
- const courseIds = courses.map(c => c.id);
106
- const wsForums = await (0, moodle_js_1.getForumsApi)(apiContext.session, courseIds);
107
- const allForums = [];
108
- for (const wsForum of wsForums) {
109
- const course = courses.find(c => c.id === wsForum.courseid);
110
- if (course) {
111
- allForums.push({
112
- course_id: wsForum.courseid,
113
- course_name: course.fullname,
114
- cmid: wsForum.cmid.toString(),
115
- forum_id: wsForum.id,
116
- name: wsForum.name,
117
- url: `https://ilearning.cycu.edu.tw/mod/forum/view.php?id=${wsForum.cmid}`,
118
- });
119
- }
120
- }
121
- const result = {
122
- status: "success",
123
- timestamp: new Date().toISOString(),
124
- forums: allForums,
125
- summary: {
126
- total_courses: courses.length,
127
- total_forums: allForums.length,
128
- },
129
- };
130
- console.log(JSON.stringify(result));
131
- return;
132
- }
133
- catch (e) {
134
- // API failed, fall through to browser mode
135
- const msg = e instanceof Error ? e.message : String(e);
136
- console.error(`// API mode failed: ${msg}, trying browser mode...`);
137
- }
138
- }
139
- // Fallback to browser mode
140
- const context = await createSessionContext(options, command);
141
- if (!context) {
57
+ if (!apiContext) {
142
58
  process.exitCode = 1;
143
59
  return;
144
60
  }
145
- const { log, page, session, browser, context: browserContext } = context;
146
- try {
147
- const courses = await (0, moodle_js_1.getEnrolledCourses)(page, session, log, {
148
- classification: "inprogress",
149
- });
150
- const allForums = [];
151
- for (const course of courses) {
152
- const forums = await (0, moodle_js_1.getForumsInCourse)(page, session, course.id, log);
153
- for (const forum of forums) {
154
- let instance = forum.forumId;
155
- // Fetch instance ID if requested
156
- if (options.fetchInstance) {
157
- log.info(` 正在取得 forum ${forum.cmid} 的 instance ID...`);
158
- instance = await (0, moodle_js_1.getForumIdFromPage)(page, parseInt(forum.cmid, 10), session) ?? 0;
159
- }
160
- allForums.push({
161
- course_id: course.id,
162
- course_name: course.fullname,
163
- cmid: forum.cmid,
164
- forum_id: instance,
165
- name: forum.name,
166
- url: forum.url,
167
- });
168
- }
61
+ const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
62
+ classification: "inprogress",
63
+ });
64
+ // Get forums via WS API (no browser needed!)
65
+ const courseIds = courses.map(c => c.id);
66
+ const wsForums = await (0, moodle_js_1.getForumsApi)(apiContext.session, courseIds);
67
+ const allForums = [];
68
+ for (const wsForum of wsForums) {
69
+ const course = courses.find(c => c.id === wsForum.courseid);
70
+ if (course) {
71
+ allForums.push({
72
+ course_id: wsForum.courseid,
73
+ course_name: course.fullname,
74
+ cmid: wsForum.cmid.toString(),
75
+ forum_id: wsForum.id,
76
+ name: wsForum.name,
77
+ // url: `https://ilearning.cycu.edu.tw/mod/forum/view.php?id=${wsForum.cmid}`,
78
+ });
169
79
  }
170
- const output = {
171
- status: "success",
172
- timestamp: new Date().toISOString(),
173
- forums: allForums.map(f => ({
174
- course_id: f.course_id,
175
- course_name: f.course_name,
176
- cmid: f.cmid,
177
- forum_id: f.forum_id,
178
- name: f.name,
179
- url: f.url,
180
- })),
181
- summary: {
182
- total_courses: courses.length,
183
- total_forums: allForums.length,
184
- },
185
- };
186
- console.log(JSON.stringify(output));
187
- }
188
- finally {
189
- await (0, auth_js_2.closeBrowserSafely)(browser, browserContext);
190
80
  }
81
+ const result = {
82
+ status: "success",
83
+ timestamp: new Date().toISOString(),
84
+ forums: allForums,
85
+ summary: {
86
+ total_courses: courses.length,
87
+ total_forums: allForums.length,
88
+ },
89
+ };
90
+ console.log(JSON.stringify(result));
191
91
  });
192
92
  forumsCmd
193
93
  .command("list-all")
194
94
  .description("List all forums across all courses")
195
95
  .option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
196
- .option("--unread-only", "Show only forums with unread discussions")
197
- .option("--fetch-instance", "Fetch forum instance IDs (slower)")
198
96
  .option("--output <format>", "Output format: json|csv|table|silent")
199
97
  .action(async (options, command) => {
200
- // Try pure WS API mode (no browser, fast!)
201
98
  const apiContext = await createApiContext(options, command);
202
- if (apiContext) {
203
- try {
204
- const classification = options.level === "all" ? undefined : "inprogress";
205
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
206
- classification,
207
- });
208
- // Get forums via WS API (no browser needed!)
209
- const courseIds = courses.map(c => c.id);
210
- const wsForums = await (0, moodle_js_1.getForumsApi)(apiContext.session, courseIds);
211
- const allForums = [];
212
- for (const wsForum of wsForums) {
213
- const course = courses.find(c => c.id === wsForum.courseid);
214
- if (course) {
215
- allForums.push({
216
- course_id: wsForum.courseid,
217
- course_name: course.fullname,
218
- cmid: wsForum.cmid.toString(),
219
- forum_id: wsForum.id,
220
- name: wsForum.name,
221
- url: `https://ilearning.cycu.edu.tw/mod/forum/view.php?id=${wsForum.cmid}`,
222
- });
223
- }
224
- }
225
- const result = {
226
- status: "success",
227
- timestamp: new Date().toISOString(),
228
- forums: allForums,
229
- summary: {
230
- total_courses: courses.length,
231
- total_forums: allForums.length,
232
- },
233
- };
234
- console.log(JSON.stringify(result));
235
- return;
236
- }
237
- catch (e) {
238
- // API failed, fall through to browser mode
239
- const msg = e instanceof Error ? e.message : String(e);
240
- console.error(`// API mode failed: ${msg}, trying browser mode...`);
241
- }
242
- }
243
- // Fallback to browser mode
244
- const context = await createSessionContext(options, command);
245
- if (!context) {
99
+ if (!apiContext) {
246
100
  process.exitCode = 1;
247
101
  return;
248
102
  }
249
- const { log, page, session, browser, context: browserContext } = context;
250
- try {
251
- const classification = options.level === "all" ? undefined : "inprogress";
252
- const courses = await (0, moodle_js_1.getEnrolledCourses)(page, session, log, { classification });
253
- const allForums = [];
254
- for (const course of courses) {
255
- const forums = await (0, moodle_js_1.getForumsInCourse)(page, session, course.id, log);
256
- for (const forum of forums) {
257
- let instance = forum.forumId;
258
- // Fetch instance ID if requested
259
- if (options.fetchInstance) {
260
- log.info(` 正在取得 forum ${forum.cmid} 的 instance ID...`);
261
- instance = await (0, moodle_js_1.getForumIdFromPage)(page, parseInt(forum.cmid, 10), session) ?? 0;
262
- }
263
- allForums.push({
264
- course_id: course.id,
265
- course_name: course.fullname,
266
- cmid: forum.cmid,
267
- forum_id: instance,
268
- name: forum.name,
269
- url: forum.url,
270
- });
271
- }
103
+ const classification = options.level === "all" ? undefined : "inprogress";
104
+ const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
105
+ classification,
106
+ });
107
+ // Get forums via WS API (no browser needed!)
108
+ const courseIds = courses.map(c => c.id);
109
+ const wsForums = await (0, moodle_js_1.getForumsApi)(apiContext.session, courseIds);
110
+ const allForums = [];
111
+ for (const wsForum of wsForums) {
112
+ const course = courses.find(c => c.id === wsForum.courseid);
113
+ if (course) {
114
+ allForums.push({
115
+ course_id: wsForum.courseid,
116
+ course_name: course.fullname,
117
+ cmid: wsForum.cmid.toString(),
118
+ forum_id: wsForum.id,
119
+ name: wsForum.name,
120
+ url: `https://ilearning.cycu.edu.tw/mod/forum/view.php?id=${wsForum.cmid}`,
121
+ });
272
122
  }
273
- const output = {
274
- status: "success",
275
- timestamp: new Date().toISOString(),
276
- forums: allForums.map(f => ({
277
- course_id: f.course_id,
278
- course_name: f.course_name,
279
- cmid: f.cmid,
280
- forum_id: f.forum_id,
281
- name: f.name,
282
- url: f.url,
283
- })),
284
- summary: {
285
- total_courses: courses.length,
286
- total_forums: allForums.length,
287
- },
288
- };
289
- console.log(JSON.stringify(output));
290
- }
291
- finally {
292
- await (0, auth_js_2.closeBrowserSafely)(browser, browserContext);
293
123
  }
124
+ const result = {
125
+ status: "success",
126
+ timestamp: new Date().toISOString(),
127
+ forums: allForums,
128
+ summary: {
129
+ total_courses: courses.length,
130
+ total_forums: allForums.length,
131
+ },
132
+ };
133
+ console.log(JSON.stringify(result));
294
134
  });
295
135
  forumsCmd
296
136
  .command("discussions")
297
- .description("List discussions in a forum (use cmid or instance ID)")
298
- .argument("<forum-id>", "Forum cmid or instance ID")
299
- .option("--unread-only", "Show only unread discussions")
137
+ .description("List discussions in a forum (use forum ID)")
138
+ .argument("<forum-id>", "Forum ID")
300
139
  .option("--output <format>", "Output format: json|csv|table|silent")
140
+ .option("--msg", "Include message content in response")
301
141
  .action(async (forumId, options, command) => {
302
- const context = await createSessionContext({ verbose: false }, command);
303
- if (!context) {
142
+ const apiContext = await createApiContext(options, command);
143
+ if (!apiContext) {
304
144
  process.exitCode = 1;
305
145
  return;
306
146
  }
307
- const { log, page, session, browser, context: browserContext } = context;
308
- try {
309
- // Find forum by cmid or instance ID
310
- const courses = await (0, moodle_js_1.getEnrolledCourses)(page, session, log);
311
- let targetForum = null;
312
- for (const course of courses) {
313
- const forums = await (0, moodle_js_1.getForumsInCourse)(page, session, course.id, log);
314
- const forum = forums.find(f => f.cmid === forumId || f.forumId === parseInt(forumId, 10));
315
- if (forum) {
316
- targetForum = { forumId: forum.forumId, forumName: forum.name };
317
- break;
318
- }
319
- }
320
- if (!targetForum) {
321
- console.log(JSON.stringify({ status: "error", error: "Forum not found" }));
322
- process.exitCode = 1;
323
- return;
324
- }
325
- // Use WS API to get discussions
326
- const discussions = await (0, moodle_js_1.getForumDiscussions)(page, session, targetForum.forumId);
327
- const output = {
328
- status: "success",
329
- timestamp: new Date().toISOString(),
330
- forum_id: targetForum.forumId,
331
- forum_name: targetForum.forumName,
332
- discussions: discussions.map(d => ({
147
+ // Get courses via WS API
148
+ const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
149
+ classification: "inprogress",
150
+ });
151
+ // Get forums via WS API
152
+ const courseIds = courses.map(c => c.id);
153
+ const wsForums = await (0, moodle_js_1.getForumsApi)(apiContext.session, courseIds);
154
+ // Find forum by cmid or instance ID
155
+ const targetForum = wsForums.find(f => f.cmid.toString() === forumId || f.id === parseInt(forumId, 10));
156
+ if (!targetForum) {
157
+ console.log(JSON.stringify({ status: "error", error: "Forum not found" }));
158
+ process.exitCode = 1;
159
+ return;
160
+ }
161
+ const course = courses.find(c => c.id === targetForum.courseid);
162
+ // Get discussions via WS API
163
+ const discussions = await (0, moodle_js_1.getForumDiscussionsApi)(apiContext.session, targetForum.id);
164
+ const result = {
165
+ status: "success",
166
+ timestamp: new Date().toISOString(),
167
+ forum_id: targetForum.id,
168
+ forum_name: targetForum.name,
169
+ course_id: course?.id,
170
+ course_name: course?.fullname,
171
+ discussions: discussions.map(d => {
172
+ const discussion = {
333
173
  id: d.id,
334
174
  name: d.name,
335
175
  user_id: d.userId,
336
176
  time_modified: d.timeModified,
337
177
  post_count: d.postCount,
338
178
  unread: d.unread,
339
- })),
340
- summary: {
341
- total_discussions: discussions.length,
342
- },
343
- };
344
- console.log(JSON.stringify(output));
345
- }
346
- finally {
347
- await (0, auth_js_2.closeBrowserSafely)(browser, browserContext);
348
- }
179
+ };
180
+ if (options.msg) {
181
+ discussion.message = (0, utils_js_1.stripHtmlTags)(d.message || "");
182
+ }
183
+ return discussion;
184
+ }),
185
+ summary: {
186
+ total_discussions: discussions.length,
187
+ },
188
+ };
189
+ console.log(JSON.stringify(result));
349
190
  });
350
191
  forumsCmd
351
192
  .command("posts")
@@ -353,51 +194,44 @@ function registerForumsCommand(program) {
353
194
  .argument("<discussion-id>", "Discussion ID")
354
195
  .option("--output <format>", "Output format: json|csv|table|silent")
355
196
  .action(async (discussionId, options, command) => {
356
- const context = await createSessionContext(options, command);
357
- if (!context) {
197
+ const output = getOutputFormat(command);
198
+ const apiContext = await createApiContext(options, command);
199
+ if (!apiContext) {
358
200
  process.exitCode = 1;
359
201
  return;
360
202
  }
361
- const { log, page, session, browser, context: browserContext } = context;
362
- const output = getOutputFormat(command);
363
- try {
364
- const posts = await (0, moodle_js_1.getDiscussionPosts)(page, session, parseInt(discussionId, 10));
365
- if (output === "json") {
366
- const result = {
367
- status: "success",
368
- timestamp: new Date().toISOString(),
369
- discussion_id: discussionId,
370
- posts: posts.map(p => ({
371
- id: p.id,
372
- subject: p.subject,
373
- author: p.author,
374
- author_id: p.authorId,
375
- created: new Date(p.created * 1000).toISOString(),
376
- modified: new Date(p.modified * 1000).toISOString(),
377
- message: p.message,
378
- unread: p.unread,
379
- })),
380
- summary: {
381
- total_posts: posts.length,
382
- },
383
- };
384
- console.log(JSON.stringify(result));
385
- }
386
- else if (output === "table") {
387
- console.log(`Discussion ${discussionId} - ${posts.length} posts`);
388
- console.log("Use --output json to see full post content");
389
- const tablePosts = posts.map(p => ({
203
+ const posts = await (0, moodle_js_1.getDiscussionPostsApi)(apiContext.session, parseInt(discussionId, 10));
204
+ if (output === "json") {
205
+ const result = {
206
+ status: "success",
207
+ timestamp: new Date().toISOString(),
208
+ discussion_id: discussionId,
209
+ posts: posts.map(p => ({
390
210
  id: p.id,
391
- subject: p.subject.substring(0, 50) + (p.subject.length > 50 ? "..." : ""),
211
+ subject: p.subject,
392
212
  author: p.author,
393
- created: new Date(p.created * 1000).toLocaleString(),
394
- }));
395
- (0, index_js_1.formatAndOutput)(tablePosts, "table", log);
396
- }
397
- }
398
- finally {
399
- await (0, auth_js_2.closeBrowserSafely)(browser, browserContext);
213
+ author_id: p.authorId,
214
+ created: new Date(p.created * 1000).toISOString(),
215
+ modified: new Date(p.modified * 1000).toISOString(),
216
+ message: p.message,
217
+ unread: p.unread,
218
+ })),
219
+ summary: {
220
+ total_posts: posts.length,
221
+ },
222
+ };
223
+ console.log(JSON.stringify(result));
224
+ }
225
+ else if (output === "table") {
226
+ console.log(`Discussion ${discussionId} - ${posts.length} posts`);
227
+ console.log("Use --output json to see full post content");
228
+ const tablePosts = posts.map(p => ({
229
+ id: p.id,
230
+ subject: p.subject.substring(0, 50) + (p.subject.length > 50 ? "..." : ""),
231
+ author: p.author,
232
+ created: new Date(p.created * 1000).toLocaleString(),
233
+ }));
234
+ console.table(tablePosts);
400
235
  }
401
236
  });
402
237
  }
403
- exports.registerForumsCommand = registerForumsCommand;
@@ -1,2 +1,3 @@
1
1
  import { Command } from "commander";
2
2
  export declare function registerGradesCommand(program: Command): void;
3
+ //# sourceMappingURL=grades.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grades.d.ts","sourceRoot":"","sources":["../../../src/src/commands/grades.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsI5D"}