@mo7yw4ng/openape 1.0.5 → 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.
- package/bin/openape +29 -0
- package/bin/openape.js +29 -0
- package/package.json +22 -28
- package/LICENSE +0 -21
- package/README.md +0 -135
- package/esm/_dnt.polyfills.d.ts +0 -101
- package/esm/_dnt.polyfills.d.ts.map +0 -1
- package/esm/_dnt.polyfills.js +0 -127
- package/esm/_dnt.shims.d.ts +0 -6
- package/esm/_dnt.shims.d.ts.map +0 -1
- package/esm/_dnt.shims.js +0 -61
- package/esm/deno.d.ts +0 -25
- package/esm/deno.d.ts.map +0 -1
- package/esm/deno.js +0 -23
- package/esm/package.json +0 -3
- package/esm/src/commands/announcements.d.ts +0 -3
- package/esm/src/commands/announcements.d.ts.map +0 -1
- package/esm/src/commands/announcements.js +0 -134
- package/esm/src/commands/assignments.d.ts +0 -3
- package/esm/src/commands/assignments.d.ts.map +0 -1
- package/esm/src/commands/assignments.js +0 -230
- package/esm/src/commands/auth.d.ts +0 -3
- package/esm/src/commands/auth.d.ts.map +0 -1
- package/esm/src/commands/auth.js +0 -290
- package/esm/src/commands/calendar.d.ts +0 -3
- package/esm/src/commands/calendar.d.ts.map +0 -1
- package/esm/src/commands/calendar.js +0 -179
- package/esm/src/commands/courses.d.ts +0 -3
- package/esm/src/commands/courses.d.ts.map +0 -1
- package/esm/src/commands/courses.js +0 -348
- package/esm/src/commands/forums.d.ts +0 -3
- package/esm/src/commands/forums.d.ts.map +0 -1
- package/esm/src/commands/forums.js +0 -318
- package/esm/src/commands/grades.d.ts +0 -3
- package/esm/src/commands/grades.d.ts.map +0 -1
- package/esm/src/commands/grades.js +0 -121
- package/esm/src/commands/materials.d.ts +0 -3
- package/esm/src/commands/materials.d.ts.map +0 -1
- package/esm/src/commands/materials.js +0 -413
- package/esm/src/commands/quizzes.d.ts +0 -3
- package/esm/src/commands/quizzes.d.ts.map +0 -1
- package/esm/src/commands/quizzes.js +0 -271
- package/esm/src/commands/skills.d.ts +0 -3
- package/esm/src/commands/skills.d.ts.map +0 -1
- package/esm/src/commands/skills.js +0 -106
- package/esm/src/commands/upload.d.ts +0 -3
- package/esm/src/commands/upload.d.ts.map +0 -1
- package/esm/src/commands/upload.js +0 -58
- package/esm/src/commands/videos.d.ts +0 -3
- package/esm/src/commands/videos.d.ts.map +0 -1
- package/esm/src/commands/videos.js +0 -336
- package/esm/src/index.d.ts +0 -27
- package/esm/src/index.d.ts.map +0 -1
- package/esm/src/index.js +0 -160
- package/esm/src/lib/auth.d.ts +0 -47
- package/esm/src/lib/auth.d.ts.map +0 -1
- package/esm/src/lib/auth.js +0 -227
- package/esm/src/lib/config.d.ts +0 -6
- package/esm/src/lib/config.d.ts.map +0 -1
- package/esm/src/lib/config.js +0 -36
- package/esm/src/lib/logger.d.ts +0 -3
- package/esm/src/lib/logger.d.ts.map +0 -1
- package/esm/src/lib/logger.js +0 -27
- package/esm/src/lib/moodle.d.ts +0 -433
- package/esm/src/lib/moodle.d.ts.map +0 -1
- package/esm/src/lib/moodle.js +0 -1318
- package/esm/src/lib/session.d.ts +0 -8
- package/esm/src/lib/session.d.ts.map +0 -1
- package/esm/src/lib/session.js +0 -42
- package/esm/src/lib/token.d.ts +0 -38
- package/esm/src/lib/token.d.ts.map +0 -1
- package/esm/src/lib/token.js +0 -178
- package/esm/src/lib/types.d.ts +0 -189
- package/esm/src/lib/types.d.ts.map +0 -1
- package/esm/src/lib/types.js +0 -2
- package/esm/src/lib/utils.d.ts +0 -57
- package/esm/src/lib/utils.d.ts.map +0 -1
- package/esm/src/lib/utils.js +0 -129
- package/script/_dnt.polyfills.d.ts +0 -101
- package/script/_dnt.polyfills.d.ts.map +0 -1
- package/script/_dnt.polyfills.js +0 -130
- package/script/_dnt.shims.d.ts +0 -6
- package/script/_dnt.shims.d.ts.map +0 -1
- package/script/_dnt.shims.js +0 -65
- package/script/deno.d.ts +0 -25
- package/script/deno.d.ts.map +0 -1
- package/script/deno.js +0 -25
- package/script/package.json +0 -3
- package/script/src/commands/announcements.d.ts +0 -3
- package/script/src/commands/announcements.d.ts.map +0 -1
- package/script/src/commands/announcements.js +0 -140
- package/script/src/commands/assignments.d.ts +0 -3
- package/script/src/commands/assignments.d.ts.map +0 -1
- package/script/src/commands/assignments.js +0 -269
- package/script/src/commands/auth.d.ts +0 -3
- package/script/src/commands/auth.d.ts.map +0 -1
- package/script/src/commands/auth.js +0 -296
- package/script/src/commands/calendar.d.ts +0 -3
- package/script/src/commands/calendar.d.ts.map +0 -1
- package/script/src/commands/calendar.js +0 -185
- package/script/src/commands/courses.d.ts +0 -3
- package/script/src/commands/courses.d.ts.map +0 -1
- package/script/src/commands/courses.js +0 -354
- package/script/src/commands/forums.d.ts +0 -3
- package/script/src/commands/forums.d.ts.map +0 -1
- package/script/src/commands/forums.js +0 -324
- package/script/src/commands/grades.d.ts +0 -3
- package/script/src/commands/grades.d.ts.map +0 -1
- package/script/src/commands/grades.js +0 -127
- package/script/src/commands/materials.d.ts +0 -3
- package/script/src/commands/materials.d.ts.map +0 -1
- package/script/src/commands/materials.js +0 -419
- package/script/src/commands/quizzes.d.ts +0 -3
- package/script/src/commands/quizzes.d.ts.map +0 -1
- package/script/src/commands/quizzes.js +0 -277
- package/script/src/commands/skills.d.ts +0 -3
- package/script/src/commands/skills.d.ts.map +0 -1
- package/script/src/commands/skills.js +0 -112
- package/script/src/commands/upload.d.ts +0 -3
- package/script/src/commands/upload.d.ts.map +0 -1
- package/script/src/commands/upload.js +0 -64
- package/script/src/commands/videos.d.ts +0 -3
- package/script/src/commands/videos.d.ts.map +0 -1
- package/script/src/commands/videos.js +0 -342
- package/script/src/index.d.ts +0 -27
- package/script/src/index.d.ts.map +0 -1
- package/script/src/index.js +0 -167
- package/script/src/lib/auth.d.ts +0 -47
- package/script/src/lib/auth.d.ts.map +0 -1
- package/script/src/lib/auth.js +0 -269
- package/script/src/lib/config.d.ts +0 -6
- package/script/src/lib/config.d.ts.map +0 -1
- package/script/src/lib/config.js +0 -42
- package/script/src/lib/logger.d.ts +0 -3
- package/script/src/lib/logger.d.ts.map +0 -1
- package/script/src/lib/logger.js +0 -30
- package/script/src/lib/moodle.d.ts +0 -433
- package/script/src/lib/moodle.d.ts.map +0 -1
- package/script/src/lib/moodle.js +0 -1389
- package/script/src/lib/session.d.ts +0 -8
- package/script/src/lib/session.d.ts.map +0 -1
- package/script/src/lib/session.js +0 -45
- package/script/src/lib/token.d.ts +0 -38
- package/script/src/lib/token.d.ts.map +0 -1
- package/script/src/lib/token.js +0 -189
- package/script/src/lib/types.d.ts +0 -189
- package/script/src/lib/types.d.ts.map +0 -1
- package/script/src/lib/types.js +0 -3
- package/script/src/lib/utils.d.ts +0 -57
- package/script/src/lib/utils.d.ts.map +0 -1
- package/script/src/lib/utils.js +0 -175
- package/skills/openape/SKILL.md +0 -115
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
import { getBaseDir, stripHtmlTags, getOutputFormat, formatTimestamp } from "../lib/utils.js";
|
|
2
|
-
import { getEnrolledCoursesApi, getForumsApi, getForumDiscussionsApi, getDiscussionPostsApi, addForumDiscussionApi, addForumPostApi, deleteForumPostApi } from "../lib/moodle.js";
|
|
3
|
-
import { createLogger } from "../lib/logger.js";
|
|
4
|
-
import { loadWsToken, loadSesskey } from "../lib/token.js";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import fs from "node:fs";
|
|
7
|
-
export function registerForumsCommand(program) {
|
|
8
|
-
const forumsCmd = program.command("forums");
|
|
9
|
-
forumsCmd.description("Forum operations");
|
|
10
|
-
// Pure API context - no browser required (fast!)
|
|
11
|
-
async function createApiContext(options, command) {
|
|
12
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
13
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
14
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
15
|
-
const log = createLogger(opts.verbose, silent, outputFormat);
|
|
16
|
-
const baseDir = getBaseDir();
|
|
17
|
-
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
18
|
-
// Check if session exists
|
|
19
|
-
if (!fs.existsSync(sessionPath)) {
|
|
20
|
-
console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
|
|
21
|
-
log.info(`Session 預期位置: ${sessionPath}`);
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
// Try to load WS token
|
|
25
|
-
const wsToken = loadWsToken(sessionPath);
|
|
26
|
-
if (!wsToken) {
|
|
27
|
-
console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
// Try to load sesskey from cache
|
|
31
|
-
const sesskey = loadSesskey(sessionPath) || undefined;
|
|
32
|
-
return {
|
|
33
|
-
log,
|
|
34
|
-
session: {
|
|
35
|
-
wsToken,
|
|
36
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
37
|
-
sesskey,
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
forumsCmd
|
|
42
|
-
.command("list")
|
|
43
|
-
.description("List forums from in-progress courses")
|
|
44
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
45
|
-
.action(async (options, command) => {
|
|
46
|
-
const apiContext = await createApiContext(options, command);
|
|
47
|
-
if (!apiContext) {
|
|
48
|
-
process.exitCode = 1;
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const courses = await getEnrolledCoursesApi(apiContext.session, {
|
|
52
|
-
classification: "inprogress",
|
|
53
|
-
});
|
|
54
|
-
// Get forums via WS API (no browser needed!)
|
|
55
|
-
const courseIds = courses.map(c => c.id);
|
|
56
|
-
const wsForums = await getForumsApi(apiContext.session, courseIds);
|
|
57
|
-
const allForums = [];
|
|
58
|
-
for (const wsForum of wsForums) {
|
|
59
|
-
const course = courses.find(c => c.id === wsForum.courseid);
|
|
60
|
-
if (course) {
|
|
61
|
-
allForums.push({
|
|
62
|
-
course_id: wsForum.courseid,
|
|
63
|
-
course_name: course.fullname,
|
|
64
|
-
intro: wsForum.intro,
|
|
65
|
-
cmid: wsForum.cmid.toString(),
|
|
66
|
-
forum_id: wsForum.id,
|
|
67
|
-
name: wsForum.name,
|
|
68
|
-
timemodified: wsForum.timemodified,
|
|
69
|
-
// url: `https://ilearning.cycu.edu.tw/mod/forum/view.php?id=${wsForum.cmid}`,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
console.log(JSON.stringify({
|
|
74
|
-
status: "success",
|
|
75
|
-
timestamp: new Date().toISOString(),
|
|
76
|
-
total_courses: courses.length,
|
|
77
|
-
total_forums: allForums.length,
|
|
78
|
-
}));
|
|
79
|
-
for (const forum of allForums) {
|
|
80
|
-
console.log(JSON.stringify(forum));
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
forumsCmd
|
|
84
|
-
.command("list-all")
|
|
85
|
-
.description("List all forums across all courses")
|
|
86
|
-
.option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
|
|
87
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
88
|
-
.action(async (options, command) => {
|
|
89
|
-
const apiContext = await createApiContext(options, command);
|
|
90
|
-
if (!apiContext) {
|
|
91
|
-
process.exitCode = 1;
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const classification = options.level === "all" ? undefined : "inprogress";
|
|
95
|
-
const courses = await getEnrolledCoursesApi(apiContext.session, {
|
|
96
|
-
classification,
|
|
97
|
-
});
|
|
98
|
-
// Get forums via WS API (no browser needed!)
|
|
99
|
-
const courseIds = courses.map(c => c.id);
|
|
100
|
-
const wsForums = await getForumsApi(apiContext.session, courseIds);
|
|
101
|
-
const allForums = [];
|
|
102
|
-
for (const wsForum of wsForums) {
|
|
103
|
-
const course = courses.find(c => c.id === wsForum.courseid);
|
|
104
|
-
if (course) {
|
|
105
|
-
allForums.push({
|
|
106
|
-
course_id: wsForum.courseid,
|
|
107
|
-
course_name: course.fullname,
|
|
108
|
-
intro: wsForum.intro,
|
|
109
|
-
cmid: wsForum.cmid.toString(),
|
|
110
|
-
forum_id: wsForum.id,
|
|
111
|
-
name: wsForum.name,
|
|
112
|
-
timemodified: wsForum.timemodified,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
console.log(JSON.stringify({
|
|
117
|
-
status: "success",
|
|
118
|
-
timestamp: new Date().toISOString(),
|
|
119
|
-
total_courses: courses.length,
|
|
120
|
-
total_forums: allForums.length,
|
|
121
|
-
}));
|
|
122
|
-
for (const forum of allForums) {
|
|
123
|
-
console.log(JSON.stringify(forum));
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
forumsCmd
|
|
127
|
-
.command("discussions")
|
|
128
|
-
.description("List discussions in a forum (use forum ID)")
|
|
129
|
-
.argument("<forum-id>", "Forum ID")
|
|
130
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
131
|
-
.action(async (forumId, options, command) => {
|
|
132
|
-
const apiContext = await createApiContext(options, command);
|
|
133
|
-
if (!apiContext) {
|
|
134
|
-
process.exitCode = 1;
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
// Get courses via WS API
|
|
138
|
-
const courses = await getEnrolledCoursesApi(apiContext.session, {
|
|
139
|
-
classification: "inprogress",
|
|
140
|
-
});
|
|
141
|
-
// Get forums via WS API
|
|
142
|
-
const courseIds = courses.map(c => c.id);
|
|
143
|
-
const wsForums = await getForumsApi(apiContext.session, courseIds);
|
|
144
|
-
// Find forum by cmid or instance ID
|
|
145
|
-
const targetForum = wsForums.find(f => f.cmid.toString() === forumId || f.id === parseInt(forumId, 10));
|
|
146
|
-
if (!targetForum) {
|
|
147
|
-
console.log(JSON.stringify({ status: "error", error: "Forum not found" }));
|
|
148
|
-
process.exitCode = 1;
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const course = courses.find(c => c.id === targetForum.courseid);
|
|
152
|
-
// Get discussions via WS API
|
|
153
|
-
const discussions = await getForumDiscussionsApi(apiContext.session, targetForum.id);
|
|
154
|
-
// Output NDJSON: one line per discussion entry for stream-friendly parsing
|
|
155
|
-
const meta = {
|
|
156
|
-
status: "success",
|
|
157
|
-
timestamp: new Date().toISOString(),
|
|
158
|
-
forum_id: targetForum.id,
|
|
159
|
-
forum_name: targetForum.name,
|
|
160
|
-
forum_intro: targetForum.intro,
|
|
161
|
-
course_id: course?.id,
|
|
162
|
-
course_name: course?.fullname,
|
|
163
|
-
total_discussions: discussions.length,
|
|
164
|
-
};
|
|
165
|
-
console.log(JSON.stringify(meta));
|
|
166
|
-
for (const d of discussions) {
|
|
167
|
-
console.log(JSON.stringify({
|
|
168
|
-
id: d.id,
|
|
169
|
-
name: d.name,
|
|
170
|
-
user_id: d.userId,
|
|
171
|
-
time_modified: d.timeModified,
|
|
172
|
-
post_count: d.postCount,
|
|
173
|
-
unread: d.unread,
|
|
174
|
-
message: stripHtmlTags(d.message || ""),
|
|
175
|
-
}));
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
forumsCmd
|
|
179
|
-
.command("posts")
|
|
180
|
-
.description("Show posts in a discussion")
|
|
181
|
-
.argument("<discussion-id>", "Discussion ID")
|
|
182
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
183
|
-
.action(async (discussionId, options, command) => {
|
|
184
|
-
const output = getOutputFormat(command);
|
|
185
|
-
const apiContext = await createApiContext(options, command);
|
|
186
|
-
if (!apiContext) {
|
|
187
|
-
process.exitCode = 1;
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const posts = await getDiscussionPostsApi(apiContext.session, parseInt(discussionId, 10));
|
|
191
|
-
if (output === "json") {
|
|
192
|
-
const result = {
|
|
193
|
-
status: "success",
|
|
194
|
-
timestamp: new Date().toISOString(),
|
|
195
|
-
discussion_id: discussionId,
|
|
196
|
-
posts: posts.map(p => ({
|
|
197
|
-
id: p.id,
|
|
198
|
-
subject: p.subject,
|
|
199
|
-
author: p.author,
|
|
200
|
-
author_id: p.authorId,
|
|
201
|
-
created: formatTimestamp(p.created),
|
|
202
|
-
modified: formatTimestamp(p.modified),
|
|
203
|
-
message: p.message,
|
|
204
|
-
unread: p.unread,
|
|
205
|
-
})),
|
|
206
|
-
summary: {
|
|
207
|
-
total_posts: posts.length,
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
console.log(JSON.stringify(result));
|
|
211
|
-
}
|
|
212
|
-
else if (output === "table") {
|
|
213
|
-
console.log(`Discussion ${discussionId} - ${posts.length} posts`);
|
|
214
|
-
console.log("Use --output json to see full post content");
|
|
215
|
-
const tablePosts = posts.map(p => ({
|
|
216
|
-
id: p.id,
|
|
217
|
-
subject: p.subject.substring(0, 50) + (p.subject.length > 50 ? "..." : ""),
|
|
218
|
-
author: p.author,
|
|
219
|
-
created: new Date(p.created * 1000).toLocaleString(),
|
|
220
|
-
}));
|
|
221
|
-
console.table(tablePosts);
|
|
222
|
-
}
|
|
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
|
-
});
|
|
318
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { getBaseDir } from "../lib/utils.js";
|
|
2
|
-
import { getEnrolledCoursesApi, getCourseGradesApi } from "../lib/moodle.js";
|
|
3
|
-
import { createLogger } from "../lib/logger.js";
|
|
4
|
-
import { loadWsToken } from "../lib/token.js";
|
|
5
|
-
import { formatAndOutput } from "../index.js";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import fs from "node:fs";
|
|
8
|
-
export function registerGradesCommand(program) {
|
|
9
|
-
const gradesCmd = program.command("grades");
|
|
10
|
-
gradesCmd.description("Grade operations");
|
|
11
|
-
function getOutputFormat(command) {
|
|
12
|
-
const opts = command.optsWithGlobals();
|
|
13
|
-
return opts.output || "json";
|
|
14
|
-
}
|
|
15
|
-
// Pure API context - no browser required (fast!)
|
|
16
|
-
async function createApiContext(options, command) {
|
|
17
|
-
const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
|
|
18
|
-
const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
|
|
19
|
-
const silent = outputFormat === "json" && !opts.verbose;
|
|
20
|
-
const log = createLogger(opts.verbose, silent, outputFormat);
|
|
21
|
-
const baseDir = getBaseDir();
|
|
22
|
-
const sessionPath = path.resolve(baseDir, ".auth", "storage-state.json");
|
|
23
|
-
// Check if session exists
|
|
24
|
-
if (!fs.existsSync(sessionPath)) {
|
|
25
|
-
console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
|
|
26
|
-
log.info(`Session 預期位置: ${sessionPath}`);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
// Try to load WS token
|
|
30
|
-
const wsToken = loadWsToken(sessionPath);
|
|
31
|
-
if (!wsToken) {
|
|
32
|
-
console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
log,
|
|
37
|
-
session: {
|
|
38
|
-
wsToken,
|
|
39
|
-
moodleBaseUrl: "https://ilearning.cycu.edu.tw",
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
gradesCmd
|
|
44
|
-
.command("summary")
|
|
45
|
-
.description("Show grade summary across all courses")
|
|
46
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
47
|
-
.action(async (options, command) => {
|
|
48
|
-
const output = getOutputFormat(command);
|
|
49
|
-
const apiContext = await createApiContext(options, command);
|
|
50
|
-
if (!apiContext) {
|
|
51
|
-
process.exitCode = 1;
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const courses = await getEnrolledCoursesApi(apiContext.session);
|
|
55
|
-
const gradeSummaries = [];
|
|
56
|
-
for (const course of courses) {
|
|
57
|
-
const grades = await getCourseGradesApi(apiContext.session, course.id);
|
|
58
|
-
gradeSummaries.push({
|
|
59
|
-
courseId: course.id,
|
|
60
|
-
courseName: course.fullname,
|
|
61
|
-
grade: grades.grade,
|
|
62
|
-
gradeFormatted: grades.gradeFormatted,
|
|
63
|
-
rank: grades.rank,
|
|
64
|
-
totalUsers: grades.totalUsers,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
// Calculate overall statistics
|
|
68
|
-
const gradedCourses = gradeSummaries.filter(g => g.grade !== undefined && g.grade !== null && g.grade !== "-");
|
|
69
|
-
const averageRank = gradeSummaries
|
|
70
|
-
.filter(g => g.rank !== undefined && g.rank !== null)
|
|
71
|
-
.reduce((sum, g) => sum + (g.rank || 0), 0) /
|
|
72
|
-
(gradeSummaries.filter(g => g.rank !== undefined && g.rank !== null).length || 1);
|
|
73
|
-
const summaryData = {
|
|
74
|
-
total_courses: courses.length,
|
|
75
|
-
graded_courses: gradedCourses.length,
|
|
76
|
-
average_rank: averageRank.toFixed(1),
|
|
77
|
-
grades: gradeSummaries,
|
|
78
|
-
};
|
|
79
|
-
formatAndOutput(summaryData, output, apiContext.log);
|
|
80
|
-
});
|
|
81
|
-
gradesCmd
|
|
82
|
-
.command("course")
|
|
83
|
-
.description("Show detailed grades for a specific course")
|
|
84
|
-
.argument("<course-id>", "Course ID")
|
|
85
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
86
|
-
.action(async (courseId, options, command) => {
|
|
87
|
-
const output = getOutputFormat(command);
|
|
88
|
-
const apiContext = await createApiContext(options, command);
|
|
89
|
-
if (!apiContext) {
|
|
90
|
-
process.exitCode = 1;
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const courses = await getEnrolledCoursesApi(apiContext.session);
|
|
94
|
-
const course = courses.find(c => c.id === parseInt(courseId, 10));
|
|
95
|
-
if (!course) {
|
|
96
|
-
apiContext.log.error(`Course not found: ${courseId}`);
|
|
97
|
-
process.exitCode = 1;
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const grades = await getCourseGradesApi(apiContext.session, course.id);
|
|
101
|
-
const gradeData = {
|
|
102
|
-
courseId: grades.courseId,
|
|
103
|
-
courseName: grades.courseName,
|
|
104
|
-
grade: grades.grade,
|
|
105
|
-
gradeFormatted: grades.gradeFormatted,
|
|
106
|
-
rank: grades.rank,
|
|
107
|
-
totalUsers: grades.totalUsers,
|
|
108
|
-
items: grades.items?.map(item => ({
|
|
109
|
-
name: item.name,
|
|
110
|
-
grade: item.grade,
|
|
111
|
-
gradeFormatted: item.gradeFormatted,
|
|
112
|
-
range: item.range,
|
|
113
|
-
percentage: item.percentage,
|
|
114
|
-
weight: item.weight,
|
|
115
|
-
feedback: item.feedback,
|
|
116
|
-
graded: item.graded,
|
|
117
|
-
})),
|
|
118
|
-
};
|
|
119
|
-
formatAndOutput(gradeData, output, apiContext.log);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
@@ -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,CAid/D"}
|