@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.
Files changed (152) hide show
  1. package/bin/openape +29 -0
  2. package/bin/openape.js +29 -0
  3. package/package.json +22 -28
  4. package/LICENSE +0 -21
  5. package/README.md +0 -135
  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 -134
  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 -230
  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 -179
  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 -348
  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 -318
  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 -121
  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 -413
  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 -271
  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 -58
  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 -336
  52. package/esm/src/index.d.ts +0 -27
  53. package/esm/src/index.d.ts.map +0 -1
  54. package/esm/src/index.js +0 -160
  55. package/esm/src/lib/auth.d.ts +0 -47
  56. package/esm/src/lib/auth.d.ts.map +0 -1
  57. package/esm/src/lib/auth.js +0 -227
  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 -27
  64. package/esm/src/lib/moodle.d.ts +0 -433
  65. package/esm/src/lib/moodle.d.ts.map +0 -1
  66. package/esm/src/lib/moodle.js +0 -1318
  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 -57
  77. package/esm/src/lib/utils.d.ts.map +0 -1
  78. package/esm/src/lib/utils.js +0 -129
  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 -140
  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 -269
  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 -185
  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 -354
  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 -324
  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 -127
  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 -419
  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 -277
  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 -64
  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 -342
  125. package/script/src/index.d.ts +0 -27
  126. package/script/src/index.d.ts.map +0 -1
  127. package/script/src/index.js +0 -167
  128. package/script/src/lib/auth.d.ts +0 -47
  129. package/script/src/lib/auth.d.ts.map +0 -1
  130. package/script/src/lib/auth.js +0 -269
  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 -30
  137. package/script/src/lib/moodle.d.ts +0 -433
  138. package/script/src/lib/moodle.d.ts.map +0 -1
  139. package/script/src/lib/moodle.js +0 -1389
  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 -57
  150. package/script/src/lib/utils.d.ts.map +0 -1
  151. package/script/src/lib/utils.js +0 -175
  152. package/skills/openape/SKILL.md +0 -115
@@ -1,185 +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.registerCalendarCommand = registerCalendarCommand;
7
- const utils_js_1 = require("../lib/utils.js");
8
- const moodle_js_1 = require("../lib/moodle.js");
9
- const logger_js_1 = require("../lib/logger.js");
10
- const token_js_1 = require("../lib/token.js");
11
- const node_path_1 = __importDefault(require("node:path"));
12
- const node_fs_1 = __importDefault(require("node:fs"));
13
- function registerCalendarCommand(program) {
14
- const calendarCmd = program.command("calendar");
15
- calendarCmd.description("Calendar operations");
16
- // Helper to get output format from global or local options
17
- function getOutputFormat(command) {
18
- const opts = command.optsWithGlobals();
19
- return opts.output || "json";
20
- }
21
- // Pure API context - no browser required (fast!)
22
- async function createApiContext(options, command) {
23
- const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
24
- const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
25
- const silent = outputFormat === "json" && !opts.verbose;
26
- const log = (0, logger_js_1.createLogger)(opts.verbose, silent, outputFormat);
27
- const baseDir = (0, utils_js_1.getBaseDir)();
28
- const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
29
- // Check if session exists
30
- if (!node_fs_1.default.existsSync(sessionPath)) {
31
- console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
32
- log.info(`Session 預期位置: ${sessionPath}`);
33
- return null;
34
- }
35
- // Try to load WS token
36
- const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
37
- if (!wsToken) {
38
- console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
39
- return null;
40
- }
41
- return {
42
- log,
43
- session: {
44
- wsToken,
45
- moodleBaseUrl: "https://ilearning.cycu.edu.tw",
46
- },
47
- };
48
- }
49
- calendarCmd
50
- .command("events")
51
- .description("List calendar events")
52
- .option("--upcoming", "Show only upcoming events")
53
- .option("--days <n>", "Number of days ahead to look", "30")
54
- .option("--course <id>", "Filter by course ID")
55
- .option("--output <format>", "Output format: json|csv|table|silent")
56
- .action(async (options, command) => {
57
- const days = parseInt(options.days, 10);
58
- const apiContext = await createApiContext(options, command);
59
- if (!apiContext) {
60
- process.exitCode = 1;
61
- return;
62
- }
63
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
64
- // Calculate time range
65
- const now = Math.floor(Date.now() / 1000);
66
- const endTime = now + (days * 24 * 60 * 60);
67
- let allEvents = [];
68
- if (options.course) {
69
- // Get events for specific course
70
- const courseId = parseInt(options.course, 10);
71
- const events = await (0, moodle_js_1.getCalendarEventsApi)(apiContext.session, {
72
- startTime: now,
73
- endTime: endTime,
74
- });
75
- allEvents = events.filter(e => e.courseid === courseId);
76
- }
77
- else {
78
- // Get events for all courses
79
- for (const course of courses) {
80
- try {
81
- const events = await (0, moodle_js_1.getCalendarEventsApi)(apiContext.session, {
82
- courseId: course.id,
83
- startTime: now,
84
- endTime: endTime,
85
- });
86
- allEvents.push(...events);
87
- }
88
- catch (err) {
89
- apiContext.log.debug(`Failed to fetch calendar events for ${course.fullname}: ${err}`);
90
- }
91
- }
92
- }
93
- // Sort by start time
94
- allEvents.sort((a, b) => a.timestart - b.timestart);
95
- // Filter upcoming only if requested
96
- let filteredEvents = allEvents;
97
- if (options.upcoming) {
98
- filteredEvents = allEvents.filter(e => e.timestart > now);
99
- }
100
- console.log(JSON.stringify({
101
- status: "success",
102
- timestamp: new Date().toISOString(),
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({
112
- id: e.id,
113
- name: e.name,
114
- description: e.description,
115
- course_id: e.courseid,
116
- event_type: e.eventtype,
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,
119
- location: e.location,
120
- }));
121
- }
122
- });
123
- calendarCmd
124
- .command("export")
125
- .description("Export calendar events to file")
126
- .option("--output <path>", "Output file path", "./calendar.json")
127
- .option("--days <n>", "Number of days ahead to include", "30")
128
- .action(async (options, command) => {
129
- const apiContext = await createApiContext(options, command);
130
- if (!apiContext) {
131
- process.exitCode = 1;
132
- return;
133
- }
134
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
135
- // Calculate time range
136
- const now = Math.floor(Date.now() / 1000);
137
- const days = parseInt(options.days, 10);
138
- const endTime = now + (days * 24 * 60 * 60);
139
- const allEvents = [];
140
- for (const course of courses) {
141
- try {
142
- const events = await (0, moodle_js_1.getCalendarEventsApi)(apiContext.session, {
143
- courseId: course.id,
144
- startTime: now,
145
- endTime: endTime,
146
- });
147
- allEvents.push(...events);
148
- }
149
- catch (err) {
150
- apiContext.log.debug(`Failed to fetch calendar events for ${course.fullname}: ${err}`);
151
- }
152
- }
153
- // Sort by start time
154
- allEvents.sort((a, b) => a.timestart - b.timestart);
155
- // Export data
156
- const exportData = {
157
- exported_at: new Date().toISOString(),
158
- time_range: {
159
- start: new Date(now * 1000).toISOString(),
160
- end: new Date(endTime * 1000).toISOString(),
161
- days: days,
162
- },
163
- events: allEvents.map(e => ({
164
- id: e.id,
165
- name: e.name,
166
- description: e.description,
167
- course_id: e.courseid,
168
- event_type: e.eventtype,
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,
171
- location: e.location,
172
- })),
173
- summary: {
174
- total_events: allEvents.length,
175
- by_type: allEvents.reduce((acc, e) => {
176
- acc[e.eventtype] = (acc[e.eventtype] || 0) + 1;
177
- return acc;
178
- }, {}),
179
- },
180
- };
181
- // Write to file
182
- node_fs_1.default.writeFileSync(options.output, JSON.stringify(exportData));
183
- apiContext.log.success(`Exported ${allEvents.length} events to ${options.output}`);
184
- });
185
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerCoursesCommand(program: Command): void;
3
- //# sourceMappingURL=courses.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"courses.d.ts","sourceRoot":"","sources":["../../../src/src/commands/courses.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4Y7D"}
@@ -1,354 +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.registerCoursesCommand = registerCoursesCommand;
7
- const utils_js_1 = require("../lib/utils.js");
8
- const moodle_js_1 = require("../lib/moodle.js");
9
- const logger_js_1 = require("../lib/logger.js");
10
- const token_js_1 = require("../lib/token.js");
11
- const index_js_1 = require("../index.js");
12
- const node_path_1 = __importDefault(require("node:path"));
13
- const node_fs_1 = __importDefault(require("node:fs"));
14
- function registerCoursesCommand(program) {
15
- const coursesCmd = program.command("courses");
16
- coursesCmd.description("Course operations");
17
- // Helper to get output format from global or local options
18
- function getOutputFormat(command) {
19
- const opts = command.optsWithGlobals();
20
- return opts.output || "json";
21
- }
22
- // Pure API context - no browser required (fast!)
23
- async function createApiContext(options, command) {
24
- const opts = command?.optsWithGlobals ? command.optsWithGlobals() : options;
25
- const outputFormat = getOutputFormat(command || { optsWithGlobals: () => ({ output: "json" }) });
26
- const silent = outputFormat === "json" && !opts.verbose;
27
- const log = (0, logger_js_1.createLogger)(opts.verbose, silent, outputFormat);
28
- const baseDir = (0, utils_js_1.getBaseDir)();
29
- const sessionPath = node_path_1.default.resolve(baseDir, ".auth", "storage-state.json");
30
- // Check if session exists
31
- if (!node_fs_1.default.existsSync(sessionPath)) {
32
- console.error("未找到登入 session。請先執行 'openape login' 進行登入。");
33
- log.info(`Session 預期位置: ${sessionPath}`);
34
- return null;
35
- }
36
- // Try to load WS token
37
- const wsToken = (0, token_js_1.loadWsToken)(sessionPath);
38
- if (!wsToken) {
39
- console.error("未找到 WS token。請先執行 'openape login' 進行登入。");
40
- return null;
41
- }
42
- return {
43
- log,
44
- session: {
45
- wsToken,
46
- moodleBaseUrl: "https://ilearning.cycu.edu.tw",
47
- },
48
- };
49
- }
50
- coursesCmd
51
- .command("list")
52
- .description("List enrolled courses")
53
- .option("--incomplete-only", "Show only incomplete courses")
54
- .option("--output <format>", "Output format: json|csv|table|silent")
55
- .option("--level <type>", "Course level: in_progress (default) | past | future | all", "in_progress")
56
- .action(async (options, command) => {
57
- const output = getOutputFormat(command);
58
- const apiContext = await createApiContext(options, command);
59
- if (!apiContext) {
60
- process.exitCode = 1;
61
- return;
62
- }
63
- // Map level to classification
64
- const classification = options.level === "all" ? undefined :
65
- options.level === "past" ? "past" :
66
- options.level === "future" ? "future" : "inprogress";
67
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
68
- classification,
69
- });
70
- let filteredCourses = courses;
71
- if (options.incompleteOnly) {
72
- filteredCourses = courses.filter(c => (c.progress ?? 0) < 100);
73
- }
74
- (0, index_js_1.formatAndOutput)(filteredCourses, output, apiContext.log);
75
- });
76
- coursesCmd
77
- .command("info")
78
- .description("Show detailed course information")
79
- .argument("<course-id>", "Course ID")
80
- .option("--output <format>", "Output format: json|csv|table|silent")
81
- .action(async (courseId, options, command) => {
82
- const output = getOutputFormat(command);
83
- const apiContext = await createApiContext(options, command);
84
- if (!apiContext) {
85
- process.exitCode = 1;
86
- return;
87
- }
88
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
89
- const course = courses.find(c => c.id === parseInt(courseId, 10));
90
- if (!course) {
91
- apiContext.log.error(`Course not found: ${courseId}`);
92
- process.exitCode = 1;
93
- return;
94
- }
95
- (0, index_js_1.formatAndOutput)(course, output, apiContext.log);
96
- });
97
- coursesCmd
98
- .command("progress")
99
- .description("Show course progress")
100
- .argument("<course-id>", "Course ID")
101
- .option("--output <format>", "Output format: json|csv|table|silent")
102
- .action(async (courseId, options, command) => {
103
- const output = getOutputFormat(command);
104
- const apiContext = await createApiContext(options, command);
105
- if (!apiContext) {
106
- process.exitCode = 1;
107
- return;
108
- }
109
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
110
- const course = courses.find(c => c.id === parseInt(courseId, 10));
111
- if (!course) {
112
- apiContext.log.error(`Course not found: ${courseId}`);
113
- process.exitCode = 1;
114
- return;
115
- }
116
- const progressData = {
117
- courseId: course.id,
118
- courseName: course.fullname,
119
- progress: course.progress ?? 0,
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
- };
123
- (0, index_js_1.formatAndOutput)(progressData, output, apiContext.log);
124
- });
125
- // Helper function to fetch syllabus from CMAP using GWT-RPC API
126
- async function fetchSyllabus(shortname) {
127
- try {
128
- const parts = shortname.split("_");
129
- if (parts.length < 2) {
130
- return { error: "Invalid course shortname format" };
131
- }
132
- const [yearTerm, opCode] = parts;
133
- // Build GWT-RPC request body
134
- // Format: 7|0|8|<base_url>|<permutation>|<service>|<method>|<param_types>|<params>...|1|2|3|4|3|5|5|5|6|7|8|
135
- const gwtBody = `7|0|8|https://cmap.cycu.edu.tw:8443/Syllabus/syllabus/|339796D6E7B561A6465F5E9B5F4943FA|com.sanfong.syllabus.shared.SyllabusClientService|findClassTargetByYearAndOpCode|java.lang.String/2004016611|${yearTerm}|${opCode}|zh_TW|1|2|3|4|3|5|5|5|6|7|8|`;
136
- const response = await fetch("https://cmap.cycu.edu.tw:8443/Syllabus/syllabus/syllabusClientService", {
137
- method: "POST",
138
- headers: {
139
- "X-GWT-Permutation": "339796D6E7B561A6465F5E9B5F4943FA",
140
- "Accept": "text/x-gwt-rpc, */*; q=0.01",
141
- "Content-Type": "text/x-gwt-rpc; charset=UTF-8",
142
- },
143
- body: gwtBody,
144
- });
145
- if (!response.ok) {
146
- return { error: `HTTP ${response.status}`, url: "https://cmap.cycu.edu.tw:8443/Syllabus/syllabus/syllabusClientService" };
147
- }
148
- const rawText = await response.text();
149
- // GWT-RPC response format: //OK[...data...]
150
- if (!rawText.startsWith("//OK")) {
151
- return { error: "Invalid GWT-RPC response", rawResponse: rawText.slice(0, 200) };
152
- }
153
- // Extract the JSON array part from the GWT response
154
- // Response format: //OK[data1,data2,...]
155
- const content = rawText.slice(4); // Remove "//OK"
156
- // Parse the GWT string table - GWT uses a special format where strings are escaped
157
- // Format: ["string1","string2",...] or [123,"string2",...]
158
- const stringTable = [];
159
- // Simple parser for GWT string table
160
- let current = "";
161
- let inString = false;
162
- let escaped = false;
163
- for (let i = 0; i < content.length; i++) {
164
- const char = content[i];
165
- if (escaped) {
166
- // Handle escape sequences
167
- switch (char) {
168
- case 'n':
169
- current += '\n';
170
- break;
171
- case 'r':
172
- current += '\r';
173
- break;
174
- case 't':
175
- current += '\t';
176
- break;
177
- case '"':
178
- current += '"';
179
- break;
180
- case '\\':
181
- current += '\\';
182
- break;
183
- case '0':
184
- current += '\0';
185
- break;
186
- default:
187
- // Unknown escape, just append the char
188
- current += char;
189
- }
190
- escaped = false;
191
- continue;
192
- }
193
- if (char === "\\") {
194
- escaped = true;
195
- continue;
196
- }
197
- if (char === '"') {
198
- inString = !inString;
199
- if (!inString && current.length > 0) {
200
- stringTable.push(current);
201
- current = "";
202
- }
203
- continue;
204
- }
205
- if (inString) {
206
- current += char;
207
- }
208
- }
209
- // Parse schedule from string table
210
- // Strategy: Find week numbers (1-18), extract title (previous field) and date
211
- const schedule = [];
212
- const datePattern = /^\d{4}-\d{2}-\d{2}$/;
213
- // Track processed indices to avoid duplicates
214
- const processedIndices = new Set();
215
- for (let i = 0; i < stringTable.length; i++) {
216
- const s = stringTable[i];
217
- // Look for week numbers (1-18)
218
- if (/^[1-9]$|^1[0-8]$/.test(s) && !processedIndices.has(i)) {
219
- const week = s;
220
- let date = "";
221
- let title = "";
222
- // Previous field is the title
223
- if (i - 1 >= 0 && !processedIndices.has(i - 1)) {
224
- title = stringTable[i - 1];
225
- }
226
- // For week 1 & 2, find date before the week number
227
- // For week 18, also look before (last week has no "next week")
228
- // For other weeks (3-17), next field is next week's date
229
- if (week === "1" || week === "2" || week === "18") {
230
- // Look backwards for date pattern (search further back for week 18)
231
- const maxLookback = week === "18" ? 15 : 6;
232
- for (let j = i - 1; j >= Math.max(0, i - maxLookback); j--) {
233
- if (datePattern.test(stringTable[j]) && !processedIndices.has(j)) {
234
- date = stringTable[j];
235
- processedIndices.add(j);
236
- break;
237
- }
238
- }
239
- }
240
- else {
241
- // Week 3-17: look for next week number, then get date before it
242
- for (let j = i + 1; j < Math.min(i + 10, stringTable.length); j++) {
243
- if (/^[1-9]$|^1[0-8]$/.test(stringTable[j]) && !processedIndices.has(j)) {
244
- // Found next week, look before it for date
245
- for (let k = j - 1; k >= Math.max(0, j - 6); k--) {
246
- if (datePattern.test(stringTable[k]) && !processedIndices.has(k)) {
247
- date = stringTable[k];
248
- processedIndices.add(k);
249
- break;
250
- }
251
- }
252
- break;
253
- }
254
- }
255
- }
256
- // Clean up title
257
- title = title.trim()
258
- .replace(/[\r\n]+/g, ' ')
259
- .replace(/,+$/, '')
260
- .trim()
261
- .slice(0, 200);
262
- // Only add if we have title (date is optional, will be inferred if missing)
263
- if (title.length > 1) {
264
- // If no date found, try to infer from the last added date
265
- if (date.length === 0 && schedule.length > 0) {
266
- const lastEntry = schedule[schedule.length - 1];
267
- const lastDate = new Date(lastEntry.date);
268
- const nextDate = new Date(lastDate.getTime() + 7 * 24 * 60 * 60 * 1000);
269
- const year = nextDate.getFullYear();
270
- const month = String(nextDate.getMonth() + 1).padStart(2, "0");
271
- const day = String(nextDate.getDate()).padStart(2, "0");
272
- date = `${year}-${month}-${day}`;
273
- }
274
- schedule.push({
275
- week,
276
- date,
277
- title,
278
- });
279
- }
280
- processedIndices.add(i);
281
- }
282
- }
283
- // Sort by date to maintain order
284
- schedule.sort((a, b) => a.date.localeCompare(b.date));
285
- // Extract course info from string table
286
- const result = {
287
- yearTerm,
288
- opCode,
289
- url: `https://cmap.cycu.edu.tw:8443/Syllabus/CoursePreview.html?yearTerm=${yearTerm}&opCode=${opCode}&locale=zh_TW`,
290
- schedule,
291
- };
292
- // Try to find instructor (look for common patterns)
293
- for (let i = 0; i < stringTable.length; i++) {
294
- const s = stringTable[i];
295
- if (s.includes("教授") || s.includes("老師") || s.includes("教師") || s.includes("Instructor")) {
296
- result.instructor = s;
297
- break;
298
- }
299
- }
300
- return result;
301
- }
302
- catch (e) {
303
- return { error: e instanceof Error ? e.message : String(e) };
304
- }
305
- }
306
- coursesCmd
307
- .command("syllabus")
308
- .description("Show course syllabus (from CMAP)")
309
- .argument("<course-id>", "Course ID")
310
- .option("--output <format>", "Output format: json|csv|table|silent")
311
- .action(async (courseId, options, command) => {
312
- const output = getOutputFormat(command);
313
- const apiContext = await createApiContext(options, command);
314
- if (!apiContext) {
315
- process.exitCode = 1;
316
- return;
317
- }
318
- try {
319
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
320
- const course = courses.find(c => c.id === parseInt(courseId, 10));
321
- if (!course) {
322
- apiContext.log.error(`Course not found: ${courseId}`);
323
- process.exitCode = 1;
324
- return;
325
- }
326
- // Fetch syllabus from CMAP
327
- const syllabus = await fetchSyllabus(course.shortname);
328
- if (!syllabus) {
329
- apiContext.log.warn(`Syllabus not found for course: ${course.shortname}`);
330
- // Return course info at least
331
- (0, index_js_1.formatAndOutput)({
332
- courseId: course.id,
333
- shortname: course.shortname,
334
- fullname: course.fullname,
335
- note: "Syllabus not available from CMAP",
336
- }, output, apiContext.log);
337
- return;
338
- }
339
- // Combine course info with syllabus
340
- const result = {
341
- courseId: course.id,
342
- shortname: course.shortname,
343
- fullname: course.fullname,
344
- ...syllabus,
345
- };
346
- (0, index_js_1.formatAndOutput)(result, output, apiContext.log);
347
- }
348
- catch (e) {
349
- const msg = e instanceof Error ? e.message : String(e);
350
- apiContext.log.error(`Error fetching syllabus: ${msg}`);
351
- process.exitCode = 1;
352
- }
353
- });
354
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerForumsCommand(program: Command): void;
3
- //# sourceMappingURL=forums.d.ts.map
@@ -1 +0,0 @@
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"}