@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
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.registerAssignmentsCommand = registerAssignmentsCommand;
40
+ const utils_js_1 = require("../lib/utils.js");
41
+ const moodle_js_1 = require("../lib/moodle.js");
42
+ const auth_js_1 = require("../lib/auth.js");
43
+ const index_js_1 = require("../index.js");
44
+ const node_path_1 = __importDefault(require("node:path"));
45
+ const promises_1 = __importDefault(require("node:fs/promises"));
46
+ function registerAssignmentsCommand(program) {
47
+ const assignmentsCmd = program.command("assignments");
48
+ assignmentsCmd.description("Assignment operations");
49
+ assignmentsCmd
50
+ .command("list")
51
+ .description("List assignments in a course")
52
+ .argument("<course-id>", "Course ID")
53
+ .option("--output <format>", "Output format: json|csv|table|silent")
54
+ .action(async (courseId, options, command) => {
55
+ const output = (0, utils_js_1.getOutputFormat)(command);
56
+ const apiContext = await (0, auth_js_1.createApiContext)(options, command);
57
+ if (!apiContext) {
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+ const apiAssignments = await (0, moodle_js_1.getAssignmentsByCoursesApi)(apiContext.session, [parseInt(courseId, 10)]);
62
+ const assignments = apiAssignments.map(a => ({
63
+ id: a.id,
64
+ courseName: courseId,
65
+ name: a.name,
66
+ url: a.url,
67
+ cmid: a.cmid,
68
+ duedate: (0, utils_js_1.formatMoodleDate)(a.duedate),
69
+ cutoffdate: (0, utils_js_1.formatMoodleDate)(a.cutoffdate),
70
+ allowSubmissionsFromDate: (0, utils_js_1.formatMoodleDate)(a.allowSubmissionsFromDate),
71
+ }));
72
+ apiContext.log.info(`\n找到 ${assignments.length} 個作業。`);
73
+ (0, index_js_1.formatAndOutput)(assignments, output, apiContext.log);
74
+ });
75
+ assignmentsCmd
76
+ .command("list-all")
77
+ .description("List all assignments across all courses")
78
+ .option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
79
+ .option("--output <format>", "Output format: json|csv|table|silent")
80
+ .action(async (options, command) => {
81
+ const output = (0, utils_js_1.getOutputFormat)(command);
82
+ const apiContext = await (0, auth_js_1.createApiContext)(options, command);
83
+ if (!apiContext) {
84
+ process.exitCode = 1;
85
+ return;
86
+ }
87
+ const classification = options.level === "all" ? undefined : "inprogress";
88
+ const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
89
+ classification,
90
+ });
91
+ // Get assignments via WS API (no browser needed!)
92
+ const courseIds = courses.map(c => c.id);
93
+ const apiAssignments = await (0, moodle_js_1.getAssignmentsByCoursesApi)(apiContext.session, courseIds);
94
+ // Build a map of courseId -> course for quick lookup
95
+ const courseMap = new Map(courses.map(c => [c.id, c]));
96
+ const allAssignments = [];
97
+ for (const a of apiAssignments) {
98
+ const course = courseMap.get(a.courseId);
99
+ if (course) {
100
+ allAssignments.push({
101
+ id: a.id,
102
+ courseName: course.fullname,
103
+ name: a.name,
104
+ url: a.url,
105
+ cmid: a.cmid,
106
+ duedate: (0, utils_js_1.formatMoodleDate)(a.duedate),
107
+ cutoffdate: (0, utils_js_1.formatMoodleDate)(a.cutoffdate),
108
+ allowSubmissionsFromDate: (0, utils_js_1.formatMoodleDate)(a.allowSubmissionsFromDate),
109
+ });
110
+ }
111
+ }
112
+ apiContext.log.info(`\n總計發現 ${allAssignments.length} 個作業。`);
113
+ (0, index_js_1.formatAndOutput)(allAssignments, output, apiContext.log);
114
+ });
115
+ // ── Submission Status ───────────────────────────────────────────────────────
116
+ assignmentsCmd
117
+ .command("status")
118
+ .description("Check assignment submission status")
119
+ .argument("<assignment-id>", "Assignment instance ID (from list-all)")
120
+ .option("--output <format>", "Output format: json|csv|table|silent")
121
+ .action(async (assignmentId, options, command) => {
122
+ const output = (0, utils_js_1.getOutputFormat)(command);
123
+ const apiContext = await (0, auth_js_1.createApiContext)(options, command);
124
+ if (!apiContext) {
125
+ process.exitCode = 1;
126
+ return;
127
+ }
128
+ const id = parseInt(assignmentId, 10);
129
+ apiContext.log.info("檢查繳交狀態...");
130
+ const status = await (0, moodle_js_1.getSubmissionStatusApi)(apiContext.session, id);
131
+ // Build status data object
132
+ const statusData = {
133
+ submitted: status.submitted,
134
+ submitted_text: status.submitted ? "已繳交" : "尚未繳交",
135
+ graded: status.graded,
136
+ graded_text: status.graded ? "已評分" : "尚未評分",
137
+ last_modified: status.lastModified ? new Date(status.lastModified * 1000).toISOString() : null,
138
+ last_modified_text: status.lastModified ? new Date(status.lastModified * 1000).toLocaleString("zh-TW") : null,
139
+ grader: status.grader,
140
+ grade: status.grade,
141
+ feedback: status.feedback,
142
+ files: status.extensions.map(f => ({
143
+ filename: f.filename,
144
+ filesize: f.filesize,
145
+ filesize_kb: (0, utils_js_1.formatFileSize)(f.filesize),
146
+ })),
147
+ };
148
+ (0, index_js_1.formatAndOutput)(statusData, output, apiContext.log);
149
+ });
150
+ // ── Submit Assignment ────────────────────────────────────────────────────────
151
+ assignmentsCmd
152
+ .command("submit")
153
+ .description("Submit an assignment (online text or file)")
154
+ .argument("<assignment-id>", "Assignment instance ID (from list-all)")
155
+ .option("--text <content>", "Online text content to submit")
156
+ .option("--file-id <id>", "Draft file ID from file upload")
157
+ .option("--file <path>", "Upload and submit a file directly")
158
+ .option("--output <format>", "Output format: json|csv|table|silent")
159
+ .action(async (assignmentId, options, command) => {
160
+ const output = (0, utils_js_1.getOutputFormat)(command);
161
+ const apiContext = await (0, auth_js_1.createApiContext)(options, command);
162
+ if (!apiContext) {
163
+ process.exitCode = 1;
164
+ return;
165
+ }
166
+ const id = parseInt(assignmentId, 10);
167
+ // Check submission status first
168
+ const status = await (0, moodle_js_1.getSubmissionStatusApi)(apiContext.session, id);
169
+ let fileUploaded;
170
+ let cancelled = false;
171
+ if (status.submitted) {
172
+ const confirm = await promptConfirm("此作業已經繳交!確定要重新繳交嗎?(y/N): ");
173
+ if (!confirm) {
174
+ cancelled = true;
175
+ }
176
+ }
177
+ if (cancelled) {
178
+ const cancelResult = {
179
+ success: false,
180
+ cancelled: true,
181
+ message: "Submission cancelled by user",
182
+ };
183
+ (0, index_js_1.formatAndOutput)(cancelResult, output, apiContext.log);
184
+ return;
185
+ }
186
+ // Validate options
187
+ if (!options.text && !options.fileId && !options.file) {
188
+ const errorResult = {
189
+ success: false,
190
+ error: "請提供 --text、--file-id 或 --file 選項。",
191
+ };
192
+ (0, index_js_1.formatAndOutput)(errorResult, output, apiContext.log);
193
+ process.exitCode = 1;
194
+ return;
195
+ }
196
+ let fileId = options.fileId ? parseInt(options.fileId, 10) : undefined;
197
+ // Upload file if --file option is provided
198
+ if (options.file) {
199
+ const resolvedPath = node_path_1.default.resolve(options.file);
200
+ // Check if file exists
201
+ try {
202
+ await promises_1.default.access(resolvedPath);
203
+ }
204
+ catch {
205
+ const errorResult = {
206
+ success: false,
207
+ error: `檔案不存在: ${options.file}`,
208
+ };
209
+ (0, index_js_1.formatAndOutput)(errorResult, output, apiContext.log);
210
+ process.exitCode = 1;
211
+ return;
212
+ }
213
+ const stats = await promises_1.default.stat(resolvedPath);
214
+ const fileSizeKB = (0, utils_js_1.formatFileSize)(stats.size);
215
+ const uploadResult = await (0, moodle_js_1.uploadFileApi)(apiContext.session, resolvedPath);
216
+ if (!uploadResult.success) {
217
+ const errorResult = {
218
+ success: false,
219
+ error: `檔案上傳失敗: ${uploadResult.error}`,
220
+ };
221
+ (0, index_js_1.formatAndOutput)(errorResult, output, apiContext.log);
222
+ process.exitCode = 1;
223
+ return;
224
+ }
225
+ fileId = uploadResult.draftId;
226
+ fileUploaded = {
227
+ filename: node_path_1.default.basename(resolvedPath),
228
+ filesize: stats.size,
229
+ filesize_kb: (0, utils_js_1.formatFileSize)(stats.size),
230
+ draft_id: fileId,
231
+ };
232
+ }
233
+ // Submit
234
+ const result = await (0, moodle_js_1.saveSubmissionApi)(apiContext.session, id, {
235
+ onlineText: options.text ? { text: options.text } : undefined,
236
+ fileId: fileId,
237
+ });
238
+ const submitResult = {
239
+ success: result.success,
240
+ assignment_id: id,
241
+ submitted: !!result.success,
242
+ online_text: !!options.text,
243
+ file_uploaded: fileUploaded,
244
+ file_id: fileId ?? null,
245
+ error: result.success ? undefined : result.error,
246
+ message: result.success ? "Assignment submitted successfully" : result.error,
247
+ };
248
+ (0, index_js_1.formatAndOutput)(submitResult, output, apiContext.log);
249
+ if (!result.success) {
250
+ process.exitCode = 1;
251
+ }
252
+ });
253
+ }
254
+ /**
255
+ * Prompt user for yes/no confirmation.
256
+ */
257
+ async function promptConfirm(prompt) {
258
+ const readline = await Promise.resolve().then(() => __importStar(require("node:readline")));
259
+ const rl = readline.createInterface({
260
+ input: process.stdin,
261
+ output: process.stdout,
262
+ });
263
+ return new Promise((resolve) => {
264
+ rl.question(prompt, (answer) => {
265
+ rl.close();
266
+ resolve(/^y/i.test(answer));
267
+ });
268
+ });
269
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoQtD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkStD"}
@@ -9,6 +9,7 @@ const playwright_core_1 = require("playwright-core");
9
9
  const logger_js_1 = require("../lib/logger.js");
10
10
  const auth_js_1 = require("../lib/auth.js");
11
11
  const token_js_1 = require("../lib/token.js");
12
+ const moodle_js_1 = require("../lib/moodle.js");
12
13
  const node_path_1 = __importDefault(require("node:path"));
13
14
  const node_fs_1 = __importDefault(require("node:fs"));
14
15
  function registerCommand(program) {
@@ -134,31 +135,41 @@ function registerCommand(program) {
134
135
  catch {
135
136
  // Ignore sesskey extraction errors
136
137
  }
137
- // Try to acquire WS token
138
+ // Acquire WS token
139
+ let wsToken;
138
140
  try {
139
- const config = {
140
- username: "",
141
- password: "",
142
- courseUrl: "",
143
- moodleBaseUrl: "https://ilearning.cycu.edu.tw",
144
- headless: false,
145
- slowMo: 0,
146
- authStatePath: sessionPath,
147
- ollamaBaseUrl: "",
148
- };
149
- const wsToken = await (0, token_js_1.acquireWsToken)(page, config, log);
141
+ wsToken = await (0, token_js_1.acquireWsToken)(page, { moodleBaseUrl: "https://ilearning.cycu.edu.tw" }, log);
150
142
  (0, token_js_1.saveWsToken)(sessionPath, wsToken);
151
143
  }
152
144
  catch {
153
145
  // WS token is optional, ignore errors
154
146
  }
155
147
  const stats = node_fs_1.default.statSync(sessionPath);
148
+ // Get user info via WS API
149
+ let user;
150
+ try {
151
+ if (wsToken) {
152
+ const siteInfo = await (0, moodle_js_1.getSiteInfoApi)({
153
+ wsToken,
154
+ moodleBaseUrl: "https://ilearning.cycu.edu.tw",
155
+ });
156
+ user = {
157
+ userid: siteInfo.userid,
158
+ username: siteInfo.username,
159
+ fullname: siteInfo.fullname,
160
+ };
161
+ }
162
+ }
163
+ catch {
164
+ // Ignore
165
+ }
156
166
  const result = {
157
167
  status: "success",
158
168
  message: "Login successful",
159
169
  session_path: sessionPath,
160
170
  session_size: stats.size,
161
- updated: true
171
+ updated: true,
172
+ ...(user ? { user } : {}),
162
173
  };
163
174
  console.log(JSON.stringify(result, null, 2));
164
175
  }
@@ -217,6 +228,25 @@ function registerCommand(program) {
217
228
  exists: false
218
229
  }
219
230
  };
231
+ // Try to get user info from WS API
232
+ try {
233
+ const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
234
+ if (wsToken) {
235
+ const session = {
236
+ wsToken,
237
+ moodleBaseUrl: "https://ilearning.cycu.edu.tw"
238
+ };
239
+ const siteInfo = await (0, moodle_js_1.getSiteInfoApi)(session);
240
+ result.user = {
241
+ userid: siteInfo.userid,
242
+ username: siteInfo.username,
243
+ fullname: siteInfo.fullname
244
+ };
245
+ }
246
+ }
247
+ catch {
248
+ // WS token might not be available or expired, skip user info
249
+ }
220
250
  console.log(JSON.stringify(result, null, 2));
221
251
  }
222
252
  catch {
@@ -233,7 +263,7 @@ function registerCommand(program) {
233
263
  status: "error",
234
264
  error: "Session not found",
235
265
  session_path: sessionPath,
236
- hint: "Run 'openape auth login' first"
266
+ hint: "Run 'openape login' first"
237
267
  };
238
268
  console.log(JSON.stringify(result, null, 2));
239
269
  }
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/src/commands/calendar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoM9D"}
1
+ {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/src/commands/calendar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmM9D"}
@@ -23,19 +23,19 @@ function registerCalendarCommand(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 {
@@ -97,29 +97,28 @@ function registerCalendarCommand(program) {
97
97
  if (options.upcoming) {
98
98
  filteredEvents = allEvents.filter(e => e.timestart > now);
99
99
  }
100
- const output = {
100
+ console.log(JSON.stringify({
101
101
  status: "success",
102
102
  timestamp: new Date().toISOString(),
103
- events: filteredEvents.map(e => ({
103
+ total_events: allEvents.length,
104
+ upcoming: allEvents.filter(e => e.timestart > now).length,
105
+ by_type: allEvents.reduce((acc, e) => {
106
+ acc[e.eventtype] = (acc[e.eventtype] || 0) + 1;
107
+ return acc;
108
+ }, {}),
109
+ }));
110
+ for (const e of filteredEvents) {
111
+ console.log(JSON.stringify({
104
112
  id: e.id,
105
113
  name: e.name,
106
114
  description: e.description,
107
115
  course_id: e.courseid,
108
116
  event_type: e.eventtype,
109
- start_time: new Date(e.timestart).toISOString(),
110
- end_time: e.timeduration ? new Date(e.timestart + e.timeduration / 1000).toISOString() : null,
117
+ start_time: (0, utils_js_1.formatTimestamp)(e.timestart),
118
+ end_time: e.timeduration ? (0, utils_js_1.formatTimestamp)(e.timestart + Math.floor(e.timeduration / 1000)) : null,
111
119
  location: e.location,
112
- })),
113
- summary: {
114
- total_events: allEvents.length,
115
- upcoming: allEvents.filter(e => e.timestart > now).length,
116
- by_type: allEvents.reduce((acc, e) => {
117
- acc[e.eventtype] = (acc[e.eventtype] || 0) + 1;
118
- return acc;
119
- }, {}),
120
- },
121
- };
122
- console.log(JSON.stringify(output));
120
+ }));
121
+ }
123
122
  });
124
123
  calendarCmd
125
124
  .command("export")
@@ -167,8 +166,8 @@ function registerCalendarCommand(program) {
167
166
  description: e.description,
168
167
  course_id: e.courseid,
169
168
  event_type: e.eventtype,
170
- start_time: new Date(e.timestart).toISOString(),
171
- end_time: e.timeduration ? new Date(e.timestart + e.timeduration / 1000).toISOString() : null,
169
+ start_time: (0, utils_js_1.formatTimestamp)(e.timestart),
170
+ end_time: e.timeduration ? (0, utils_js_1.formatTimestamp)(e.timestart + Math.floor(e.timeduration / 1000)) : null,
172
171
  location: e.location,
173
172
  })),
174
173
  summary: {
@@ -24,19 +24,19 @@ function registerCoursesCommand(program) {
24
24
  const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
25
25
  const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
26
26
  const silent = outputFormat === "json" && !opts.verbose;
27
- const log = (0, logger_js_1.createLogger)(opts.verbose, silent);
27
+ const log = (0, logger_js_1.createLogger)(opts.verbose, silent, outputFormat);
28
28
  const baseDir = (0, utils_js_1.getBaseDir)();
29
29
  const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
30
30
  // Check if session exists
31
31
  if (!node_fs_1.default.existsSync(sessionPath)) {
32
- log.error("未找到登入 session。請先執行 'openape auth login' 進行登入。");
32
+ console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
33
33
  log.info(`Session 預期位置: ${sessionPath}`);
34
34
  return null;
35
35
  }
36
36
  // Try to load WS token
37
37
  const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
38
38
  if (!wsToken) {
39
- log.error("未找到 WS token。請先執行 'openape auth login' 進行登入。");
39
+ console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
40
40
  return null;
41
41
  }
42
42
  return {
@@ -117,8 +117,8 @@ function registerCoursesCommand(program) {
117
117
  courseId: course.id,
118
118
  courseName: course.fullname,
119
119
  progress: course.progress ?? 0,
120
- startDate: course.startdate ? new Date(course.startdate * 1000).toISOString() : null,
121
- endDate: course.enddate ? new Date(course.enddate * 1000).toISOString() : null,
120
+ startDate: course.startdate ? (0, utils_js_1.formatTimestamp)(course.startdate) : null,
121
+ endDate: course.enddate ? (0, utils_js_1.formatTimestamp)(course.enddate) : null,
122
122
  };
123
123
  (0, index_js_1.formatAndOutput)(progressData, output, apiContext.log);
124
124
  });
@@ -1 +1 @@
1
- {"version":3,"file":"forums.d.ts","sourceRoot":"","sources":["../../../src/src/commands/forums.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6P5D"}
1
+ {"version":3,"file":"forums.d.ts","sourceRoot":"","sources":["../../../src/src/commands/forums.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkX5D"}