@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
@@ -13,28 +13,24 @@ const node_fs_1 = __importDefault(require("node:fs"));
13
13
  function registerForumsCommand(program) {
14
14
  const forumsCmd = program.command("forums");
15
15
  forumsCmd.description("Forum operations");
16
- function getOutputFormat(command) {
17
- const opts = command.optsWithGlobals();
18
- return opts.output || "json";
19
- }
20
16
  // Pure API context - no browser required (fast!)
21
17
  async function createApiContext(options, command) {
22
18
  const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
23
- const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
19
+ const outputFormat = (0, utils_js_1.getOutputFormat)(command || { optsWithGlobals: () => ({ output: "json" }) });
24
20
  const silent = outputFormat === "json" && !opts.verbose;
25
- const log = (0, logger_js_1.createLogger)(opts.verbose, silent);
21
+ const log = (0, logger_js_1.createLogger)(opts.verbose, silent, outputFormat);
26
22
  const baseDir = (0, utils_js_1.getBaseDir)();
27
23
  const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
28
24
  // Check if session exists
29
25
  if (!node_fs_1.default.existsSync(sessionPath)) {
30
- log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
26
+ console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
31
27
  log.info(`Session 預期位置: ${sessionPath}`);
32
28
  return null;
33
29
  }
34
30
  // Try to load WS token
35
31
  const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
36
32
  if (!wsToken) {
37
- log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
33
+ console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
38
34
  return null;
39
35
  }
40
36
  // Try to load sesskey from cache
@@ -71,6 +67,7 @@ function registerForumsCommand(program) {
71
67
  allForums.push({
72
68
  course_id: wsForum.courseid,
73
69
  course_name: course.fullname,
70
+ intro: wsForum.intro,
74
71
  cmid: wsForum.cmid.toString(),
75
72
  forum_id: wsForum.id,
76
73
  name: wsForum.name,
@@ -79,16 +76,15 @@ function registerForumsCommand(program) {
79
76
  });
80
77
  }
81
78
  }
82
- const result = {
79
+ console.log(JSON.stringify({
83
80
  status: "success",
84
81
  timestamp: new Date().toISOString(),
85
- forums: allForums,
86
- summary: {
87
- total_courses: courses.length,
88
- total_forums: allForums.length,
89
- },
90
- };
91
- console.log(JSON.stringify(result));
82
+ total_courses: courses.length,
83
+ total_forums: allForums.length,
84
+ }));
85
+ for (const forum of allForums) {
86
+ console.log(JSON.stringify(forum));
87
+ }
92
88
  });
93
89
  forumsCmd
94
90
  .command("list-all")
@@ -115,6 +111,7 @@ function registerForumsCommand(program) {
115
111
  allForums.push({
116
112
  course_id: wsForum.courseid,
117
113
  course_name: course.fullname,
114
+ intro: wsForum.intro,
118
115
  cmid: wsForum.cmid.toString(),
119
116
  forum_id: wsForum.id,
120
117
  name: wsForum.name,
@@ -122,16 +119,15 @@ function registerForumsCommand(program) {
122
119
  });
123
120
  }
124
121
  }
125
- const result = {
122
+ console.log(JSON.stringify({
126
123
  status: "success",
127
124
  timestamp: new Date().toISOString(),
128
- forums: allForums,
129
- summary: {
130
- total_courses: courses.length,
131
- total_forums: allForums.length,
132
- },
133
- };
134
- console.log(JSON.stringify(result));
125
+ total_courses: courses.length,
126
+ total_forums: allForums.length,
127
+ }));
128
+ for (const forum of allForums) {
129
+ console.log(JSON.stringify(forum));
130
+ }
135
131
  });
136
132
  forumsCmd
137
133
  .command("discussions")
@@ -161,27 +157,29 @@ function registerForumsCommand(program) {
161
157
  const course = courses.find(c => c.id === targetForum.courseid);
162
158
  // Get discussions via WS API
163
159
  const discussions = await (0, moodle_js_1.getForumDiscussionsApi)(apiContext.session, targetForum.id);
164
- const result = {
160
+ // Output NDJSON: one line per discussion entry for stream-friendly parsing
161
+ const meta = {
165
162
  status: "success",
166
163
  timestamp: new Date().toISOString(),
167
164
  forum_id: targetForum.id,
168
165
  forum_name: targetForum.name,
166
+ forum_intro: targetForum.intro,
169
167
  course_id: course?.id,
170
168
  course_name: course?.fullname,
171
- discussions: discussions.map(d => ({
169
+ total_discussions: discussions.length,
170
+ };
171
+ console.log(JSON.stringify(meta));
172
+ for (const d of discussions) {
173
+ console.log(JSON.stringify({
172
174
  id: d.id,
173
175
  name: d.name,
174
176
  user_id: d.userId,
175
177
  time_modified: d.timeModified,
176
178
  post_count: d.postCount,
177
179
  unread: d.unread,
178
- message: ((0, utils_js_1.stripHtmlTags)(d.message || "")).substring(0, 250) + "...",
179
- })),
180
- summary: {
181
- total_discussions: discussions.length,
182
- },
183
- };
184
- console.log(JSON.stringify(result));
180
+ message: (0, utils_js_1.stripHtmlTags)(d.message || ""),
181
+ }));
182
+ }
185
183
  });
186
184
  forumsCmd
187
185
  .command("posts")
@@ -189,7 +187,7 @@ function registerForumsCommand(program) {
189
187
  .argument("<discussion-id>", "Discussion ID")
190
188
  .option("--output <format>", "Output format: json|csv|table|silent")
191
189
  .action(async (discussionId, options, command) => {
192
- const output = getOutputFormat(command);
190
+ const output = (0, utils_js_1.getOutputFormat)(command);
193
191
  const apiContext = await createApiContext(options, command);
194
192
  if (!apiContext) {
195
193
  process.exitCode = 1;
@@ -206,8 +204,8 @@ function registerForumsCommand(program) {
206
204
  subject: p.subject,
207
205
  author: p.author,
208
206
  author_id: p.authorId,
209
- created: new Date(p.created * 1000).toISOString(),
210
- modified: new Date(p.modified * 1000).toISOString(),
207
+ created: (0, utils_js_1.formatTimestamp)(p.created),
208
+ modified: (0, utils_js_1.formatTimestamp)(p.modified),
211
209
  message: p.message,
212
210
  unread: p.unread,
213
211
  })),
@@ -229,4 +227,98 @@ function registerForumsCommand(program) {
229
227
  console.table(tablePosts);
230
228
  }
231
229
  });
230
+ forumsCmd
231
+ .command("post")
232
+ .description("Post a new discussion to a forum")
233
+ .argument("<forum-id>", "Forum ID")
234
+ .argument("<subject>", "Discussion subject")
235
+ .argument("<message>", "Discussion message")
236
+ .option("--subscribe", "Subscribe to the discussion", false)
237
+ .option("--pin", "Pin the discussion", false)
238
+ .action(async (forumId, subject, message, options, command) => {
239
+ const apiContext = await createApiContext(options, command);
240
+ if (!apiContext) {
241
+ process.exitCode = 1;
242
+ return;
243
+ }
244
+ const { log, session } = apiContext;
245
+ // Get courses to find the forum
246
+ const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(session, {
247
+ classification: "inprogress",
248
+ });
249
+ const courseIds = courses.map(c => c.id);
250
+ const wsForums = await (0, moodle_js_1.getForumsApi)(session, courseIds);
251
+ // Find forum by cmid or instance ID
252
+ const targetForum = wsForums.find(f => f.cmid.toString() === forumId || f.id === parseInt(forumId, 10));
253
+ if (!targetForum) {
254
+ log.error(`Forum not found: ${forumId}`);
255
+ process.exitCode = 1;
256
+ return;
257
+ }
258
+ const course = courses.find(c => c.id === targetForum.courseid);
259
+ log.info(`Posting to forum: ${targetForum.name} (${course?.fullname})`);
260
+ const result = await (0, moodle_js_1.addForumDiscussionApi)(session, targetForum.id, subject, message);
261
+ if (result.success) {
262
+ log.success(`✓ Discussion posted successfully!`);
263
+ log.info(` Discussion ID: ${result.discussionId}`);
264
+ }
265
+ else {
266
+ log.error(`✗ Failed to post discussion: ${result.error}`);
267
+ process.exitCode = 1;
268
+ }
269
+ });
270
+ forumsCmd
271
+ .command("reply")
272
+ .description("Reply to a discussion post")
273
+ .argument("<post-id>", "Parent post ID to reply to")
274
+ .argument("<subject>", "Reply subject")
275
+ .argument("<message>", "Reply message")
276
+ .option("--attachment-id <id>", "Draft file ID for attachment")
277
+ .option("--inline-attachment-id <id>", "Draft file ID for inline attachment")
278
+ .action(async (postId, subject, message, options, command) => {
279
+ const apiContext = await createApiContext(options, command);
280
+ if (!apiContext) {
281
+ process.exitCode = 1;
282
+ return;
283
+ }
284
+ const { log, session } = apiContext;
285
+ log.info(`Replying to post: ${postId}`);
286
+ log.info(` Subject: ${subject}`);
287
+ log.info(` Message: ${message}`);
288
+ if (options.attachmentId) {
289
+ log.info(` Attachment ID: ${options.attachmentId}`);
290
+ }
291
+ const result = await (0, moodle_js_1.addForumPostApi)(session, parseInt(postId, 10), subject, message, {
292
+ attachmentId: options.attachmentId ? parseInt(options.attachmentId, 10) : undefined,
293
+ inlineAttachmentId: options.inlineAttachmentId ? parseInt(options.inlineAttachmentId, 10) : undefined,
294
+ });
295
+ if (result.success) {
296
+ log.success(`✓ Reply posted successfully!`);
297
+ log.info(` Post ID: ${result.postId}`);
298
+ }
299
+ else {
300
+ log.error(`✗ Failed to post reply: ${result.error}`);
301
+ process.exitCode = 1;
302
+ }
303
+ });
304
+ forumsCmd
305
+ .command("delete")
306
+ .description("Delete a forum post or discussion (by post ID)")
307
+ .argument("<post-id>", "Post ID to delete (deletes entire discussion if it's the first post)")
308
+ .action(async (postId, options, command) => {
309
+ const apiContext = await createApiContext(options, command);
310
+ if (!apiContext) {
311
+ process.exitCode = 1;
312
+ return;
313
+ }
314
+ const { log, session } = apiContext;
315
+ const result = await (0, moodle_js_1.deleteForumPostApi)(session, parseInt(postId, 10));
316
+ if (result.success) {
317
+ log.success(`✓ Post ${postId} deleted successfully!`);
318
+ }
319
+ else {
320
+ log.error(`✗ Failed to delete post: ${result.error}`);
321
+ process.exitCode = 1;
322
+ }
323
+ });
232
324
  }
@@ -23,19 +23,19 @@ function registerGradesCommand(program) {
23
23
  const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
24
24
  const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
25
25
  const silent = outputFormat === "json" && !opts.verbose;
26
- const log = (0, logger_js_1.createLogger)(opts.verbose, silent);
26
+ const log = (0, logger_js_1.createLogger)(opts.verbose, silent, outputFormat);
27
27
  const baseDir = (0, utils_js_1.getBaseDir)();
28
28
  const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
29
29
  // Check if session exists
30
30
  if (!node_fs_1.default.existsSync(sessionPath)) {
31
- log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
31
+ console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
32
32
  log.info(`Session 預期位置: ${sessionPath}`);
33
33
  return null;
34
34
  }
35
35
  // Try to load WS token
36
36
  const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
37
37
  if (!wsToken) {
38
- log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
38
+ console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
39
39
  return null;
40
40
  }
41
41
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../../../src/src/commands/materials.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqlB/D"}
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,CAid/D"}