@mo7yw4ng/openape 1.0.6 → 2.0.4

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 (152) hide show
  1. package/bin/openape +29 -0
  2. package/bin/openape.js +32 -0
  3. package/package.json +24 -28
  4. package/LICENSE +0 -21
  5. package/README.md +0 -138
  6. package/esm/_dnt.polyfills.d.ts +0 -101
  7. package/esm/_dnt.polyfills.d.ts.map +0 -1
  8. package/esm/_dnt.polyfills.js +0 -127
  9. package/esm/_dnt.shims.d.ts +0 -6
  10. package/esm/_dnt.shims.d.ts.map +0 -1
  11. package/esm/_dnt.shims.js +0 -61
  12. package/esm/deno.d.ts +0 -25
  13. package/esm/deno.d.ts.map +0 -1
  14. package/esm/deno.js +0 -23
  15. package/esm/package.json +0 -3
  16. package/esm/src/commands/announcements.d.ts +0 -3
  17. package/esm/src/commands/announcements.d.ts.map +0 -1
  18. package/esm/src/commands/announcements.js +0 -71
  19. package/esm/src/commands/assignments.d.ts +0 -3
  20. package/esm/src/commands/assignments.d.ts.map +0 -1
  21. package/esm/src/commands/assignments.js +0 -229
  22. package/esm/src/commands/auth.d.ts +0 -3
  23. package/esm/src/commands/auth.d.ts.map +0 -1
  24. package/esm/src/commands/auth.js +0 -290
  25. package/esm/src/commands/calendar.d.ts +0 -3
  26. package/esm/src/commands/calendar.d.ts.map +0 -1
  27. package/esm/src/commands/calendar.js +0 -127
  28. package/esm/src/commands/courses.d.ts +0 -3
  29. package/esm/src/commands/courses.d.ts.map +0 -1
  30. package/esm/src/commands/courses.js +0 -312
  31. package/esm/src/commands/forums.d.ts +0 -3
  32. package/esm/src/commands/forums.d.ts.map +0 -1
  33. package/esm/src/commands/forums.js +0 -190
  34. package/esm/src/commands/grades.d.ts +0 -3
  35. package/esm/src/commands/grades.d.ts.map +0 -1
  36. package/esm/src/commands/grades.js +0 -84
  37. package/esm/src/commands/materials.d.ts +0 -3
  38. package/esm/src/commands/materials.d.ts.map +0 -1
  39. package/esm/src/commands/materials.js +0 -402
  40. package/esm/src/commands/quizzes.d.ts +0 -3
  41. package/esm/src/commands/quizzes.d.ts.map +0 -1
  42. package/esm/src/commands/quizzes.js +0 -236
  43. package/esm/src/commands/skills.d.ts +0 -3
  44. package/esm/src/commands/skills.d.ts.map +0 -1
  45. package/esm/src/commands/skills.js +0 -106
  46. package/esm/src/commands/upload.d.ts +0 -3
  47. package/esm/src/commands/upload.d.ts.map +0 -1
  48. package/esm/src/commands/upload.js +0 -55
  49. package/esm/src/commands/videos.d.ts +0 -3
  50. package/esm/src/commands/videos.d.ts.map +0 -1
  51. package/esm/src/commands/videos.js +0 -266
  52. package/esm/src/index.d.ts +0 -28
  53. package/esm/src/index.d.ts.map +0 -1
  54. package/esm/src/index.js +0 -164
  55. package/esm/src/lib/auth.d.ts +0 -66
  56. package/esm/src/lib/auth.d.ts.map +0 -1
  57. package/esm/src/lib/auth.js +0 -286
  58. package/esm/src/lib/config.d.ts +0 -6
  59. package/esm/src/lib/config.d.ts.map +0 -1
  60. package/esm/src/lib/config.js +0 -36
  61. package/esm/src/lib/logger.d.ts +0 -3
  62. package/esm/src/lib/logger.d.ts.map +0 -1
  63. package/esm/src/lib/logger.js +0 -26
  64. package/esm/src/lib/moodle.d.ts +0 -447
  65. package/esm/src/lib/moodle.d.ts.map +0 -1
  66. package/esm/src/lib/moodle.js +0 -1353
  67. package/esm/src/lib/session.d.ts +0 -8
  68. package/esm/src/lib/session.d.ts.map +0 -1
  69. package/esm/src/lib/session.js +0 -42
  70. package/esm/src/lib/token.d.ts +0 -38
  71. package/esm/src/lib/token.d.ts.map +0 -1
  72. package/esm/src/lib/token.js +0 -178
  73. package/esm/src/lib/types.d.ts +0 -189
  74. package/esm/src/lib/types.d.ts.map +0 -1
  75. package/esm/src/lib/types.js +0 -2
  76. package/esm/src/lib/utils.d.ts +0 -52
  77. package/esm/src/lib/utils.d.ts.map +0 -1
  78. package/esm/src/lib/utils.js +0 -122
  79. package/script/_dnt.polyfills.d.ts +0 -101
  80. package/script/_dnt.polyfills.d.ts.map +0 -1
  81. package/script/_dnt.polyfills.js +0 -130
  82. package/script/_dnt.shims.d.ts +0 -6
  83. package/script/_dnt.shims.d.ts.map +0 -1
  84. package/script/_dnt.shims.js +0 -65
  85. package/script/deno.d.ts +0 -25
  86. package/script/deno.d.ts.map +0 -1
  87. package/script/deno.js +0 -25
  88. package/script/package.json +0 -3
  89. package/script/src/commands/announcements.d.ts +0 -3
  90. package/script/src/commands/announcements.d.ts.map +0 -1
  91. package/script/src/commands/announcements.js +0 -74
  92. package/script/src/commands/assignments.d.ts +0 -3
  93. package/script/src/commands/assignments.d.ts.map +0 -1
  94. package/script/src/commands/assignments.js +0 -268
  95. package/script/src/commands/auth.d.ts +0 -3
  96. package/script/src/commands/auth.d.ts.map +0 -1
  97. package/script/src/commands/auth.js +0 -296
  98. package/script/src/commands/calendar.d.ts +0 -3
  99. package/script/src/commands/calendar.d.ts.map +0 -1
  100. package/script/src/commands/calendar.js +0 -133
  101. package/script/src/commands/courses.d.ts +0 -3
  102. package/script/src/commands/courses.d.ts.map +0 -1
  103. package/script/src/commands/courses.js +0 -315
  104. package/script/src/commands/forums.d.ts +0 -3
  105. package/script/src/commands/forums.d.ts.map +0 -1
  106. package/script/src/commands/forums.js +0 -193
  107. package/script/src/commands/grades.d.ts +0 -3
  108. package/script/src/commands/grades.d.ts.map +0 -1
  109. package/script/src/commands/grades.js +0 -87
  110. package/script/src/commands/materials.d.ts +0 -3
  111. package/script/src/commands/materials.d.ts.map +0 -1
  112. package/script/src/commands/materials.js +0 -408
  113. package/script/src/commands/quizzes.d.ts +0 -3
  114. package/script/src/commands/quizzes.d.ts.map +0 -1
  115. package/script/src/commands/quizzes.js +0 -239
  116. package/script/src/commands/skills.d.ts +0 -3
  117. package/script/src/commands/skills.d.ts.map +0 -1
  118. package/script/src/commands/skills.js +0 -112
  119. package/script/src/commands/upload.d.ts +0 -3
  120. package/script/src/commands/upload.d.ts.map +0 -1
  121. package/script/src/commands/upload.js +0 -61
  122. package/script/src/commands/videos.d.ts +0 -3
  123. package/script/src/commands/videos.d.ts.map +0 -1
  124. package/script/src/commands/videos.js +0 -272
  125. package/script/src/index.d.ts +0 -28
  126. package/script/src/index.d.ts.map +0 -1
  127. package/script/src/index.js +0 -171
  128. package/script/src/lib/auth.d.ts +0 -66
  129. package/script/src/lib/auth.d.ts.map +0 -1
  130. package/script/src/lib/auth.js +0 -296
  131. package/script/src/lib/config.d.ts +0 -6
  132. package/script/src/lib/config.d.ts.map +0 -1
  133. package/script/src/lib/config.js +0 -42
  134. package/script/src/lib/logger.d.ts +0 -3
  135. package/script/src/lib/logger.d.ts.map +0 -1
  136. package/script/src/lib/logger.js +0 -29
  137. package/script/src/lib/moodle.d.ts +0 -447
  138. package/script/src/lib/moodle.d.ts.map +0 -1
  139. package/script/src/lib/moodle.js +0 -1425
  140. package/script/src/lib/session.d.ts +0 -8
  141. package/script/src/lib/session.d.ts.map +0 -1
  142. package/script/src/lib/session.js +0 -45
  143. package/script/src/lib/token.d.ts +0 -38
  144. package/script/src/lib/token.d.ts.map +0 -1
  145. package/script/src/lib/token.js +0 -189
  146. package/script/src/lib/types.d.ts +0 -189
  147. package/script/src/lib/types.d.ts.map +0 -1
  148. package/script/src/lib/types.js +0 -3
  149. package/script/src/lib/utils.d.ts +0 -52
  150. package/script/src/lib/utils.d.ts.map +0 -1
  151. package/script/src/lib/utils.js +0 -167
  152. package/skills/openape/SKILL.md +0 -115
@@ -1,268 +0,0 @@
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
- let stats;
201
- try {
202
- stats = await promises_1.default.stat(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 fileSizeKB = (0, utils_js_1.formatFileSize)(stats.size);
214
- const uploadResult = await (0, moodle_js_1.uploadFileApi)(apiContext.session, resolvedPath);
215
- if (!uploadResult.success) {
216
- const errorResult = {
217
- success: false,
218
- error: `檔案上傳失敗: ${uploadResult.error}`,
219
- };
220
- (0, index_js_1.formatAndOutput)(errorResult, output, apiContext.log);
221
- process.exitCode = 1;
222
- return;
223
- }
224
- fileId = uploadResult.draftId;
225
- fileUploaded = {
226
- filename: node_path_1.default.basename(resolvedPath),
227
- filesize: stats.size,
228
- filesize_kb: (0, utils_js_1.formatFileSize)(stats.size),
229
- draft_id: fileId,
230
- };
231
- }
232
- // Submit
233
- const result = await (0, moodle_js_1.saveSubmissionApi)(apiContext.session, id, {
234
- onlineText: options.text ? { text: options.text } : undefined,
235
- fileId: fileId,
236
- });
237
- const submitResult = {
238
- success: result.success,
239
- assignment_id: id,
240
- submitted: !!result.success,
241
- online_text: !!options.text,
242
- file_uploaded: fileUploaded,
243
- file_id: fileId ?? null,
244
- error: result.success ? undefined : result.error,
245
- message: result.success ? "Assignment submitted successfully" : result.error,
246
- };
247
- (0, index_js_1.formatAndOutput)(submitResult, output, apiContext.log);
248
- if (!result.success) {
249
- process.exitCode = 1;
250
- }
251
- });
252
- }
253
- /**
254
- * Prompt user for yes/no confirmation.
255
- */
256
- async function promptConfirm(prompt) {
257
- const readline = await Promise.resolve().then(() => __importStar(require("node:readline")));
258
- const rl = readline.createInterface({
259
- input: process.stdin,
260
- output: process.stdout,
261
- });
262
- return new Promise((resolve) => {
263
- rl.question(prompt, (answer) => {
264
- rl.close();
265
- resolve(/^y/i.test(answer));
266
- });
267
- });
268
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCommand(program: Command): void;
3
- //# sourceMappingURL=auth.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,296 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.registerCommand = registerCommand;
7
- const utils_js_1 = require("../lib/utils.js");
8
- const playwright_core_1 = require("playwright-core");
9
- const logger_js_1 = require("../lib/logger.js");
10
- const auth_js_1 = require("../lib/auth.js");
11
- const token_js_1 = require("../lib/token.js");
12
- const moodle_js_1 = require("../lib/moodle.js");
13
- const node_path_1 = __importDefault(require("node:path"));
14
- const node_fs_1 = __importDefault(require("node:fs"));
15
- function registerCommand(program) {
16
- program
17
- .command("login")
18
- .description("Login to iLearning manually and save session")
19
- .action(async (options) => {
20
- const log = (0, logger_js_1.createLogger)(false);
21
- // Determine session storage path
22
- const baseDir = (0, utils_js_1.getBaseDir)();
23
- const sessionDir = node_path_1.default.resolve(baseDir, ".auth");
24
- const sessionPath = node_path_1.default.resolve(sessionDir, "storage-state.json");
25
- // Ensure session directory exists
26
- if (!node_fs_1.default.existsSync(sessionDir)) {
27
- node_fs_1.default.mkdirSync(sessionDir, { recursive: true });
28
- }
29
- const edgePath = (0, auth_js_1.findEdgePath)();
30
- const browser = await playwright_core_1.chromium.launch({
31
- executablePath: edgePath,
32
- headless: false,
33
- slowMo: 0,
34
- });
35
- let context;
36
- let page;
37
- if (node_fs_1.default.existsSync(sessionPath)) {
38
- log.info(`找到已有 session: ${sessionPath}`);
39
- log.info("正在驗證 session...");
40
- try {
41
- context = await browser.newContext({ storageState: sessionPath });
42
- page = await context.newPage();
43
- await page.goto("https://ilearning.cycu.edu.tw/my/", {
44
- waitUntil: "domcontentloaded",
45
- timeout: 15000,
46
- });
47
- const url = page.url();
48
- if (url.includes("login") || url.includes("microsoftonline")) {
49
- log.warn("Session 已過期,請重新登入。");
50
- await context.close();
51
- context = await browser.newContext();
52
- page = await context.newPage();
53
- await page.goto("https://ilearning.cycu.edu.tw/login/index.php", {
54
- waitUntil: "domcontentloaded",
55
- });
56
- }
57
- else {
58
- // Session is still valid, close browser and exit
59
- try {
60
- if (context)
61
- await context.close().catch(() => { });
62
- }
63
- catch { }
64
- try {
65
- await browser.close().catch(() => { });
66
- }
67
- catch { }
68
- // Wait a bit for browser to fully close
69
- await new Promise(resolve => setTimeout(resolve, 500));
70
- const result = {
71
- status: "success",
72
- message: "Session still valid",
73
- session_path: sessionPath,
74
- updated: false
75
- };
76
- console.log(JSON.stringify(result));
77
- return;
78
- }
79
- }
80
- catch {
81
- log.warn("無法恢復 session,請重新登入。");
82
- // context might not have been initialized if the error occurred during newContext
83
- if (context) {
84
- await context.close();
85
- }
86
- context = await browser.newContext();
87
- page = await context.newPage();
88
- await page.goto("https://ilearning.cycu.edu.tw/login/index.php", {
89
- waitUntil: "domcontentloaded",
90
- });
91
- }
92
- }
93
- else {
94
- log.info("首次登入,請在瀏覽器中完成登入流程。");
95
- context = await browser.newContext();
96
- page = await context.newPage();
97
- await page.goto("https://ilearning.cycu.edu.tw/login/index.php", {
98
- waitUntil: "domcontentloaded",
99
- });
100
- }
101
- log.info("\n請在瀏覽器中完成登入,登入成功後將自動儲存 session...\n");
102
- try {
103
- const startTime = Date.now();
104
- const timeout = 300000;
105
- let loggedIn = false;
106
- while (Date.now() - startTime < timeout) {
107
- await page.waitForTimeout(1000);
108
- const currentUrl = page.url();
109
- if (currentUrl.includes("ilearning.cycu.edu.tw") &&
110
- !currentUrl.includes("login") &&
111
- !currentUrl.includes("microsoftonline")) {
112
- await page.waitForTimeout(2000);
113
- const finalUrl = page.url();
114
- if (finalUrl.includes("ilearning.cycu.edu.tw") &&
115
- !finalUrl.includes("login") &&
116
- !finalUrl.includes("microsoftonline")) {
117
- loggedIn = true;
118
- break;
119
- }
120
- }
121
- }
122
- if (loggedIn) {
123
- await context.storageState({ path: sessionPath });
124
- // Extract and save sesskey for faster API calls
125
- try {
126
- // Navigate to a page with M.cfg first
127
- await page.goto("https://ilearning.cycu.edu.tw/my/", { waitUntil: "domcontentloaded" });
128
- // Use Function constructor to avoid dnt transforming globalThis
129
- const sesskey = await page.evaluate(() => self.M?.cfg?.sesskey ?? null);
130
- if (sesskey) {
131
- (0, token_js_1.saveSesskey)(sessionPath, sesskey);
132
- log.debug(`Saved sesskey: ${sesskey}`);
133
- }
134
- }
135
- catch {
136
- // Ignore sesskey extraction errors
137
- }
138
- // Acquire WS token
139
- let wsToken;
140
- try {
141
- wsToken = await (0, token_js_1.acquireWsToken)(page, { moodleBaseUrl: "https://ilearning.cycu.edu.tw" }, log);
142
- (0, token_js_1.saveWsToken)(sessionPath, wsToken);
143
- }
144
- catch {
145
- // WS token is optional, ignore errors
146
- }
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
- }
166
- const result = {
167
- status: "success",
168
- message: "Login successful",
169
- session_path: sessionPath,
170
- session_size: stats.size,
171
- updated: true,
172
- ...(user ? { user } : {}),
173
- };
174
- console.log(JSON.stringify(result, null, 2));
175
- }
176
- else {
177
- throw new Error("TimeoutError");
178
- }
179
- }
180
- catch (err) {
181
- const errorResult = {
182
- status: "error",
183
- error: err instanceof Error ? err.message : String(err),
184
- session_path: sessionPath
185
- };
186
- console.log(JSON.stringify(errorResult));
187
- }
188
- finally {
189
- // Safely close browser with error handling
190
- try {
191
- if (context)
192
- await context.close().catch(() => { });
193
- }
194
- catch { }
195
- try {
196
- await browser.close().catch(() => { });
197
- }
198
- catch { }
199
- // Wait for browser process to fully terminate
200
- await new Promise(resolve => setTimeout(resolve, 500));
201
- }
202
- });
203
- program
204
- .command("status")
205
- .description("Check session status")
206
- .option("--session <path>", "Session file path", ".auth/storage-state.json")
207
- .action(async (options) => {
208
- const baseDir = (0, utils_js_1.getBaseDir)();
209
- const sessionPath = node_path_1.default.resolve(baseDir, options.session);
210
- if (node_fs_1.default.existsSync(sessionPath)) {
211
- const stats = node_fs_1.default.statSync(sessionPath);
212
- // Try to read and validate the session
213
- try {
214
- const content = node_fs_1.default.readFileSync(sessionPath, "utf8");
215
- const state = JSON.parse(content);
216
- const cookies = state.cookies || [];
217
- const moodleSession = cookies.find((c) => c.name === "MoodleSession");
218
- const result = {
219
- status: "success",
220
- session_path: sessionPath,
221
- exists: true,
222
- modified: new Date(stats.mtime).toISOString(),
223
- size: stats.size,
224
- moodle_session: moodleSession ? {
225
- exists: true,
226
- expires: new Date(moodleSession.expires * 1000).toISOString()
227
- } : {
228
- exists: false
229
- }
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
- }
250
- console.log(JSON.stringify(result, null, 2));
251
- }
252
- catch {
253
- const result = {
254
- status: "error",
255
- error: "Session file is corrupted",
256
- session_path: sessionPath
257
- };
258
- console.log(JSON.stringify(result, null, 2));
259
- }
260
- }
261
- else {
262
- const result = {
263
- status: "error",
264
- error: "Session not found",
265
- session_path: sessionPath,
266
- hint: "Run 'openape login' first"
267
- };
268
- console.log(JSON.stringify(result, null, 2));
269
- }
270
- });
271
- program
272
- .command("logout")
273
- .description("Remove saved session")
274
- .option("--session <path>", "Session file path", ".auth/storage-state.json")
275
- .action(async (options) => {
276
- const baseDir = (0, utils_js_1.getBaseDir)();
277
- const sessionPath = node_path_1.default.resolve(baseDir, options.session);
278
- if (node_fs_1.default.existsSync(sessionPath)) {
279
- node_fs_1.default.unlinkSync(sessionPath);
280
- const result = {
281
- status: "success",
282
- message: "Session removed",
283
- session_path: sessionPath
284
- };
285
- console.log(JSON.stringify(result, null, 2));
286
- }
287
- else {
288
- const result = {
289
- status: "error",
290
- error: "Session not found",
291
- session_path: sessionPath
292
- };
293
- console.log(JSON.stringify(result, null, 2));
294
- }
295
- });
296
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCalendarCommand(program: Command): void;
3
- //# sourceMappingURL=calendar.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"calendar.d.ts","sourceRoot":"","sources":["../../../src/src/commands/calendar.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqJ9D"}