@mo7yw4ng/openape 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/esm/deno.js +1 -1
- package/esm/src/commands/announcements.d.ts.map +1 -1
- package/esm/src/commands/announcements.js +16 -17
- package/esm/src/commands/assignments.d.ts +3 -0
- package/esm/src/commands/assignments.d.ts.map +1 -0
- package/esm/src/commands/assignments.js +230 -0
- package/esm/src/commands/auth.d.ts.map +1 -1
- package/esm/src/commands/auth.js +45 -15
- package/esm/src/commands/calendar.d.ts.map +1 -1
- package/esm/src/commands/calendar.js +20 -21
- package/esm/src/commands/courses.js +6 -6
- package/esm/src/commands/forums.d.ts.map +1 -1
- package/esm/src/commands/forums.js +128 -36
- package/esm/src/commands/grades.js +3 -3
- package/esm/src/commands/materials.d.ts.map +1 -1
- package/esm/src/commands/materials.js +115 -224
- package/esm/src/commands/quizzes.d.ts.map +1 -1
- package/esm/src/commands/quizzes.js +179 -68
- package/esm/src/commands/skills.d.ts.map +1 -1
- package/esm/src/commands/skills.js +4 -8
- package/esm/src/commands/upload.d.ts +3 -0
- package/esm/src/commands/upload.d.ts.map +1 -0
- package/esm/src/commands/upload.js +58 -0
- package/esm/src/commands/videos.d.ts.map +1 -1
- package/esm/src/commands/videos.js +10 -9
- package/esm/src/index.d.ts.map +1 -1
- package/esm/src/index.js +12 -1
- package/esm/src/lib/auth.d.ts +23 -1
- package/esm/src/lib/auth.d.ts.map +1 -1
- package/esm/src/lib/auth.js +36 -3
- package/esm/src/lib/logger.d.ts +1 -1
- package/esm/src/lib/logger.d.ts.map +1 -1
- package/esm/src/lib/logger.js +7 -4
- package/esm/src/lib/moodle.d.ts +183 -1
- package/esm/src/lib/moodle.d.ts.map +1 -1
- package/esm/src/lib/moodle.js +498 -13
- package/esm/src/lib/types.d.ts +81 -164
- package/esm/src/lib/types.d.ts.map +1 -1
- package/esm/src/lib/types.js +1 -0
- package/esm/src/lib/utils.d.ts +20 -0
- package/esm/src/lib/utils.d.ts.map +1 -1
- package/esm/src/lib/utils.js +48 -1
- package/package.json +1 -1
- package/script/deno.js +1 -1
- package/script/src/commands/announcements.d.ts.map +1 -1
- package/script/src/commands/announcements.js +15 -16
- package/script/src/commands/assignments.d.ts +3 -0
- package/script/src/commands/assignments.d.ts.map +1 -0
- package/script/src/commands/assignments.js +269 -0
- package/script/src/commands/auth.d.ts.map +1 -1
- package/script/src/commands/auth.js +44 -14
- package/script/src/commands/calendar.d.ts.map +1 -1
- package/script/src/commands/calendar.js +19 -20
- package/script/src/commands/courses.js +5 -5
- package/script/src/commands/forums.d.ts.map +1 -1
- package/script/src/commands/forums.js +128 -36
- package/script/src/commands/grades.js +3 -3
- package/script/src/commands/materials.d.ts.map +1 -1
- package/script/src/commands/materials.js +115 -224
- package/script/src/commands/quizzes.d.ts.map +1 -1
- package/script/src/commands/quizzes.js +177 -66
- package/script/src/commands/skills.d.ts.map +1 -1
- package/script/src/commands/skills.js +4 -8
- package/script/src/commands/upload.d.ts +3 -0
- package/script/src/commands/upload.d.ts.map +1 -0
- package/script/src/commands/upload.js +64 -0
- package/script/src/commands/videos.d.ts.map +1 -1
- package/script/src/commands/videos.js +10 -9
- package/script/src/index.d.ts.map +1 -1
- package/script/src/index.js +12 -1
- package/script/src/lib/auth.d.ts +23 -1
- package/script/src/lib/auth.d.ts.map +1 -1
- package/script/src/lib/auth.js +70 -3
- package/script/src/lib/logger.d.ts +1 -1
- package/script/src/lib/logger.d.ts.map +1 -1
- package/script/src/lib/logger.js +7 -4
- package/script/src/lib/moodle.d.ts +183 -1
- package/script/src/lib/moodle.d.ts.map +1 -1
- package/script/src/lib/moodle.js +511 -13
- package/script/src/lib/types.d.ts +81 -164
- package/script/src/lib/types.d.ts.map +1 -1
- package/script/src/lib/types.js +1 -0
- package/script/src/lib/utils.d.ts +20 -0
- package/script/src/lib/utils.d.ts.map +1 -1
- package/script/src/lib/utils.js +52 -0
- package/skills/openape/SKILL.md +74 -270
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getBaseDir, stripHtmlTags } from "../lib/utils.js";
|
|
2
|
-
import { getEnrolledCoursesApi, getForumsApi, getForumDiscussionsApi, getDiscussionPostsApi } from "../lib/moodle.js";
|
|
1
|
+
import { getBaseDir, stripHtmlTags, getOutputFormat, formatTimestamp } from "../lib/utils.js";
|
|
2
|
+
import { getEnrolledCoursesApi, getForumsApi, getForumDiscussionsApi, getDiscussionPostsApi, addForumDiscussionApi, addForumPostApi, deleteForumPostApi } from "../lib/moodle.js";
|
|
3
3
|
import { createLogger } from "../lib/logger.js";
|
|
4
4
|
import { loadWsToken, loadSesskey } from "../lib/token.js";
|
|
5
5
|
import path from "node:path";
|
|
@@ -7,28 +7,24 @@ import fs from "node:fs";
|
|
|
7
7
|
export function registerForumsCommand(program) {
|
|
8
8
|
const forumsCmd = program.command("forums");
|
|
9
9
|
forumsCmd.description("Forum operations");
|
|
10
|
-
function getOutputFormat(command) {
|
|
11
|
-
const opts = command.optsWithGlobals();
|
|
12
|
-
return opts.output || "json";
|
|
13
|
-
}
|
|
14
10
|
// Pure API context - no browser required (fast!)
|
|
15
11
|
async function createApiContext(options, command) {
|
|
16
12
|
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
17
13
|
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
18
14
|
const silent = outputFormat === "json" && !opts.verbose;
|
|
19
|
-
const log = createLogger(opts.verbose, silent);
|
|
15
|
+
const log = createLogger(opts.verbose, silent, outputFormat);
|
|
20
16
|
const baseDir = getBaseDir();
|
|
21
17
|
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
22
18
|
// Check if session exists
|
|
23
19
|
if (!fs.existsSync(sessionPath)) {
|
|
24
|
-
|
|
20
|
+
console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
|
|
25
21
|
log.info(`Session 預期位置: ${sessionPath}`);
|
|
26
22
|
return null;
|
|
27
23
|
}
|
|
28
24
|
// Try to load WS token
|
|
29
25
|
const wsToken = loadWsToken(sessionPath);
|
|
30
26
|
if (!wsToken) {
|
|
31
|
-
|
|
27
|
+
console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
|
|
32
28
|
return null;
|
|
33
29
|
}
|
|
34
30
|
// Try to load sesskey from cache
|
|
@@ -65,6 +61,7 @@ export function registerForumsCommand(program) {
|
|
|
65
61
|
allForums.push({
|
|
66
62
|
course_id: wsForum.courseid,
|
|
67
63
|
course_name: course.fullname,
|
|
64
|
+
intro: wsForum.intro,
|
|
68
65
|
cmid: wsForum.cmid.toString(),
|
|
69
66
|
forum_id: wsForum.id,
|
|
70
67
|
name: wsForum.name,
|
|
@@ -73,16 +70,15 @@ export function registerForumsCommand(program) {
|
|
|
73
70
|
});
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
|
-
|
|
73
|
+
console.log(JSON.stringify({
|
|
77
74
|
status: "success",
|
|
78
75
|
timestamp: new Date().toISOString(),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
console.log(JSON.stringify(result));
|
|
76
|
+
total_courses: courses.length,
|
|
77
|
+
total_forums: allForums.length,
|
|
78
|
+
}));
|
|
79
|
+
for (const forum of allForums) {
|
|
80
|
+
console.log(JSON.stringify(forum));
|
|
81
|
+
}
|
|
86
82
|
});
|
|
87
83
|
forumsCmd
|
|
88
84
|
.command("list-all")
|
|
@@ -109,6 +105,7 @@ export function registerForumsCommand(program) {
|
|
|
109
105
|
allForums.push({
|
|
110
106
|
course_id: wsForum.courseid,
|
|
111
107
|
course_name: course.fullname,
|
|
108
|
+
intro: wsForum.intro,
|
|
112
109
|
cmid: wsForum.cmid.toString(),
|
|
113
110
|
forum_id: wsForum.id,
|
|
114
111
|
name: wsForum.name,
|
|
@@ -116,16 +113,15 @@ export function registerForumsCommand(program) {
|
|
|
116
113
|
});
|
|
117
114
|
}
|
|
118
115
|
}
|
|
119
|
-
|
|
116
|
+
console.log(JSON.stringify({
|
|
120
117
|
status: "success",
|
|
121
118
|
timestamp: new Date().toISOString(),
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
console.log(JSON.stringify(result));
|
|
119
|
+
total_courses: courses.length,
|
|
120
|
+
total_forums: allForums.length,
|
|
121
|
+
}));
|
|
122
|
+
for (const forum of allForums) {
|
|
123
|
+
console.log(JSON.stringify(forum));
|
|
124
|
+
}
|
|
129
125
|
});
|
|
130
126
|
forumsCmd
|
|
131
127
|
.command("discussions")
|
|
@@ -155,27 +151,29 @@ export function registerForumsCommand(program) {
|
|
|
155
151
|
const course = courses.find(c => c.id === targetForum.courseid);
|
|
156
152
|
// Get discussions via WS API
|
|
157
153
|
const discussions = await getForumDiscussionsApi(apiContext.session, targetForum.id);
|
|
158
|
-
|
|
154
|
+
// Output NDJSON: one line per discussion entry for stream-friendly parsing
|
|
155
|
+
const meta = {
|
|
159
156
|
status: "success",
|
|
160
157
|
timestamp: new Date().toISOString(),
|
|
161
158
|
forum_id: targetForum.id,
|
|
162
159
|
forum_name: targetForum.name,
|
|
160
|
+
forum_intro: targetForum.intro,
|
|
163
161
|
course_id: course?.id,
|
|
164
162
|
course_name: course?.fullname,
|
|
165
|
-
|
|
163
|
+
total_discussions: discussions.length,
|
|
164
|
+
};
|
|
165
|
+
console.log(JSON.stringify(meta));
|
|
166
|
+
for (const d of discussions) {
|
|
167
|
+
console.log(JSON.stringify({
|
|
166
168
|
id: d.id,
|
|
167
169
|
name: d.name,
|
|
168
170
|
user_id: d.userId,
|
|
169
171
|
time_modified: d.timeModified,
|
|
170
172
|
post_count: d.postCount,
|
|
171
173
|
unread: d.unread,
|
|
172
|
-
message:
|
|
173
|
-
}))
|
|
174
|
-
|
|
175
|
-
total_discussions: discussions.length,
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
console.log(JSON.stringify(result));
|
|
174
|
+
message: stripHtmlTags(d.message || ""),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
179
177
|
});
|
|
180
178
|
forumsCmd
|
|
181
179
|
.command("posts")
|
|
@@ -200,8 +198,8 @@ export function registerForumsCommand(program) {
|
|
|
200
198
|
subject: p.subject,
|
|
201
199
|
author: p.author,
|
|
202
200
|
author_id: p.authorId,
|
|
203
|
-
created:
|
|
204
|
-
modified:
|
|
201
|
+
created: formatTimestamp(p.created),
|
|
202
|
+
modified: formatTimestamp(p.modified),
|
|
205
203
|
message: p.message,
|
|
206
204
|
unread: p.unread,
|
|
207
205
|
})),
|
|
@@ -223,4 +221,98 @@ export function registerForumsCommand(program) {
|
|
|
223
221
|
console.table(tablePosts);
|
|
224
222
|
}
|
|
225
223
|
});
|
|
224
|
+
forumsCmd
|
|
225
|
+
.command("post")
|
|
226
|
+
.description("Post a new discussion to a forum")
|
|
227
|
+
.argument("<forum-id>", "Forum ID")
|
|
228
|
+
.argument("<subject>", "Discussion subject")
|
|
229
|
+
.argument("<message>", "Discussion message")
|
|
230
|
+
.option("--subscribe", "Subscribe to the discussion", false)
|
|
231
|
+
.option("--pin", "Pin the discussion", false)
|
|
232
|
+
.action(async (forumId, subject, message, options, command) => {
|
|
233
|
+
const apiContext = await createApiContext(options, command);
|
|
234
|
+
if (!apiContext) {
|
|
235
|
+
process.exitCode = 1;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const { log, session } = apiContext;
|
|
239
|
+
// Get courses to find the forum
|
|
240
|
+
const courses = await getEnrolledCoursesApi(session, {
|
|
241
|
+
classification: "inprogress",
|
|
242
|
+
});
|
|
243
|
+
const courseIds = courses.map(c => c.id);
|
|
244
|
+
const wsForums = await getForumsApi(session, courseIds);
|
|
245
|
+
// Find forum by cmid or instance ID
|
|
246
|
+
const targetForum = wsForums.find(f => f.cmid.toString() === forumId || f.id === parseInt(forumId, 10));
|
|
247
|
+
if (!targetForum) {
|
|
248
|
+
log.error(`Forum not found: ${forumId}`);
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const course = courses.find(c => c.id === targetForum.courseid);
|
|
253
|
+
log.info(`Posting to forum: ${targetForum.name} (${course?.fullname})`);
|
|
254
|
+
const result = await addForumDiscussionApi(session, targetForum.id, subject, message);
|
|
255
|
+
if (result.success) {
|
|
256
|
+
log.success(`✓ Discussion posted successfully!`);
|
|
257
|
+
log.info(` Discussion ID: ${result.discussionId}`);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
log.error(`✗ Failed to post discussion: ${result.error}`);
|
|
261
|
+
process.exitCode = 1;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
forumsCmd
|
|
265
|
+
.command("reply")
|
|
266
|
+
.description("Reply to a discussion post")
|
|
267
|
+
.argument("<post-id>", "Parent post ID to reply to")
|
|
268
|
+
.argument("<subject>", "Reply subject")
|
|
269
|
+
.argument("<message>", "Reply message")
|
|
270
|
+
.option("--attachment-id <id>", "Draft file ID for attachment")
|
|
271
|
+
.option("--inline-attachment-id <id>", "Draft file ID for inline attachment")
|
|
272
|
+
.action(async (postId, subject, message, options, command) => {
|
|
273
|
+
const apiContext = await createApiContext(options, command);
|
|
274
|
+
if (!apiContext) {
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const { log, session } = apiContext;
|
|
279
|
+
log.info(`Replying to post: ${postId}`);
|
|
280
|
+
log.info(` Subject: ${subject}`);
|
|
281
|
+
log.info(` Message: ${message}`);
|
|
282
|
+
if (options.attachmentId) {
|
|
283
|
+
log.info(` Attachment ID: ${options.attachmentId}`);
|
|
284
|
+
}
|
|
285
|
+
const result = await addForumPostApi(session, parseInt(postId, 10), subject, message, {
|
|
286
|
+
attachmentId: options.attachmentId ? parseInt(options.attachmentId, 10) : undefined,
|
|
287
|
+
inlineAttachmentId: options.inlineAttachmentId ? parseInt(options.inlineAttachmentId, 10) : undefined,
|
|
288
|
+
});
|
|
289
|
+
if (result.success) {
|
|
290
|
+
log.success(`✓ Reply posted successfully!`);
|
|
291
|
+
log.info(` Post ID: ${result.postId}`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
log.error(`✗ Failed to post reply: ${result.error}`);
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
forumsCmd
|
|
299
|
+
.command("delete")
|
|
300
|
+
.description("Delete a forum post or discussion (by post ID)")
|
|
301
|
+
.argument("<post-id>", "Post ID to delete (deletes entire discussion if it's the first post)")
|
|
302
|
+
.action(async (postId, options, command) => {
|
|
303
|
+
const apiContext = await createApiContext(options, command);
|
|
304
|
+
if (!apiContext) {
|
|
305
|
+
process.exitCode = 1;
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const { log, session } = apiContext;
|
|
309
|
+
const result = await deleteForumPostApi(session, parseInt(postId, 10));
|
|
310
|
+
if (result.success) {
|
|
311
|
+
log.success(`✓ Post ${postId} deleted successfully!`);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
log.error(`✗ Failed to delete post: ${result.error}`);
|
|
315
|
+
process.exitCode = 1;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
226
318
|
}
|
|
@@ -17,19 +17,19 @@ export function registerGradesCommand(program) {
|
|
|
17
17
|
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
18
18
|
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
19
19
|
const silent = outputFormat === "json" && !opts.verbose;
|
|
20
|
-
const log = createLogger(opts.verbose, silent);
|
|
20
|
+
const log = createLogger(opts.verbose, silent, outputFormat);
|
|
21
21
|
const baseDir = getBaseDir();
|
|
22
22
|
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
23
23
|
// Check if session exists
|
|
24
24
|
if (!fs.existsSync(sessionPath)) {
|
|
25
|
-
|
|
25
|
+
console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
|
|
26
26
|
log.info(`Session 預期位置: ${sessionPath}`);
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
// Try to load WS token
|
|
30
30
|
const wsToken = loadWsToken(sessionPath);
|
|
31
31
|
if (!wsToken) {
|
|
32
|
-
|
|
32
|
+
console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
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;
|
|
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"}
|