@mo7yw4ng/openape 1.0.6 → 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 -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,133 +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 auth_js_1 = require("../lib/auth.js");
10
- const index_js_1 = require("../index.js");
11
- const node_fs_1 = __importDefault(require("node:fs"));
12
- function registerCalendarCommand(program) {
13
- const calendarCmd = program.command("calendar");
14
- calendarCmd.description("Calendar operations");
15
- calendarCmd
16
- .command("events")
17
- .description("List calendar events")
18
- .option("--upcoming", "Show only upcoming events")
19
- .option("--days <n>", "Number of days ahead to look", "30")
20
- .option("--course <id>", "Filter by course ID")
21
- .option("--output <format>", "Output format: json|csv|table|silent")
22
- .action(async (options, command) => {
23
- const output = (0, utils_js_1.getOutputFormat)(command);
24
- const days = parseInt(options.days, 10);
25
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
26
- if (!apiContext) {
27
- process.exitCode = 1;
28
- return;
29
- }
30
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
31
- const now = Math.floor(Date.now() / 1000);
32
- const endTime = now + (days * 24 * 60 * 60);
33
- let allEvents = [];
34
- if (options.course) {
35
- const courseId = parseInt(options.course, 10);
36
- const events = await (0, moodle_js_1.getCalendarEventsApi)(apiContext.session, {
37
- startTime: now,
38
- endTime: endTime,
39
- });
40
- allEvents = events.filter(e => e.courseid === courseId);
41
- }
42
- else {
43
- const results = await Promise.allSettled(courses.map(course => (0, moodle_js_1.getCalendarEventsApi)(apiContext.session, {
44
- courseId: course.id,
45
- startTime: now,
46
- endTime: endTime,
47
- })));
48
- for (const result of results) {
49
- if (result.status === "fulfilled")
50
- allEvents.push(...result.value);
51
- }
52
- }
53
- allEvents.sort((a, b) => a.timestart - b.timestart);
54
- let filteredEvents = allEvents;
55
- if (options.upcoming) {
56
- filteredEvents = allEvents.filter(e => e.timestart > now);
57
- }
58
- const items = filteredEvents.map(e => ({
59
- id: e.id,
60
- name: e.name,
61
- description: e.description,
62
- course_id: e.courseid,
63
- event_type: e.eventtype,
64
- start_time: (0, utils_js_1.formatTimestamp)(e.timestart),
65
- end_time: e.timeduration ? (0, utils_js_1.formatTimestamp)(e.timestart + Math.floor(e.timeduration / 1000)) : null,
66
- location: e.location,
67
- }));
68
- (0, index_js_1.formatAndOutput)(items, output, apiContext.log, {
69
- status: "success",
70
- timestamp: new Date().toISOString(),
71
- total_events: allEvents.length,
72
- upcoming: allEvents.filter(e => e.timestart > now).length,
73
- by_type: allEvents.reduce((acc, e) => {
74
- acc[e.eventtype] = (acc[e.eventtype] || 0) + 1;
75
- return acc;
76
- }, {}),
77
- });
78
- });
79
- calendarCmd
80
- .command("export")
81
- .description("Export calendar events to file")
82
- .option("--output <path>", "Output file path", "./calendar.json")
83
- .option("--days <n>", "Number of days ahead to include", "30")
84
- .action(async (options, command) => {
85
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
86
- if (!apiContext) {
87
- process.exitCode = 1;
88
- return;
89
- }
90
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
91
- const now = Math.floor(Date.now() / 1000);
92
- const days = parseInt(options.days, 10);
93
- const endTime = now + (days * 24 * 60 * 60);
94
- const allEvents = [];
95
- const results = await Promise.allSettled(courses.map(course => (0, moodle_js_1.getCalendarEventsApi)(apiContext.session, {
96
- courseId: course.id,
97
- startTime: now,
98
- endTime: endTime,
99
- })));
100
- for (const result of results) {
101
- if (result.status === "fulfilled")
102
- allEvents.push(...result.value);
103
- }
104
- allEvents.sort((a, b) => a.timestart - b.timestart);
105
- const exportData = {
106
- exported_at: new Date().toISOString(),
107
- time_range: {
108
- start: new Date(now * 1000).toISOString(),
109
- end: new Date(endTime * 1000).toISOString(),
110
- days: days,
111
- },
112
- events: allEvents.map(e => ({
113
- id: e.id,
114
- name: e.name,
115
- description: e.description,
116
- course_id: e.courseid,
117
- event_type: e.eventtype,
118
- start_time: (0, utils_js_1.formatTimestamp)(e.timestart),
119
- end_time: e.timeduration ? (0, utils_js_1.formatTimestamp)(e.timestart + Math.floor(e.timeduration / 1000)) : null,
120
- location: e.location,
121
- })),
122
- summary: {
123
- total_events: allEvents.length,
124
- by_type: allEvents.reduce((acc, e) => {
125
- acc[e.eventtype] = (acc[e.eventtype] || 0) + 1;
126
- return acc;
127
- }, {}),
128
- },
129
- };
130
- node_fs_1.default.writeFileSync(options.output, JSON.stringify(exportData));
131
- apiContext.log.success(`Exported ${allEvents.length} events to ${options.output}`);
132
- });
133
- }
@@ -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;AAMpC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkW7D"}
@@ -1,315 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerCoursesCommand = registerCoursesCommand;
4
- const utils_js_1 = require("../lib/utils.js");
5
- const moodle_js_1 = require("../lib/moodle.js");
6
- const auth_js_1 = require("../lib/auth.js");
7
- const index_js_1 = require("../index.js");
8
- function registerCoursesCommand(program) {
9
- const coursesCmd = program.command("courses");
10
- coursesCmd.description("Course operations");
11
- coursesCmd
12
- .command("list")
13
- .description("List enrolled courses")
14
- .option("--incomplete-only", "Show only incomplete courses")
15
- .option("--output <format>", "Output format: json|csv|table|silent")
16
- .option("--level <type>", "Course level: in_progress (default) | past | future | all", "in_progress")
17
- .action(async (options, command) => {
18
- const output = (0, utils_js_1.getOutputFormat)(command);
19
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
20
- if (!apiContext) {
21
- process.exitCode = 1;
22
- return;
23
- }
24
- // Map level to classification
25
- const classification = options.level === "all" ? undefined :
26
- options.level === "past" ? "past" :
27
- options.level === "future" ? "future" : "inprogress";
28
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
29
- classification,
30
- });
31
- let filteredCourses = courses;
32
- if (options.incompleteOnly) {
33
- filteredCourses = courses.filter(c => (c.progress ?? 0) < 100);
34
- }
35
- (0, index_js_1.formatAndOutput)(filteredCourses, output, apiContext.log);
36
- });
37
- coursesCmd
38
- .command("info")
39
- .description("Show detailed course information")
40
- .argument("<course-id>", "Course ID")
41
- .option("--output <format>", "Output format: json|csv|table|silent")
42
- .action(async (courseId, options, command) => {
43
- const output = (0, utils_js_1.getOutputFormat)(command);
44
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
45
- if (!apiContext) {
46
- process.exitCode = 1;
47
- return;
48
- }
49
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
50
- const course = courses.find(c => c.id === parseInt(courseId, 10));
51
- if (!course) {
52
- apiContext.log.error(`Course not found: ${courseId}`);
53
- process.exitCode = 1;
54
- return;
55
- }
56
- (0, index_js_1.formatAndOutput)(course, output, apiContext.log);
57
- });
58
- coursesCmd
59
- .command("progress")
60
- .description("Show course progress")
61
- .argument("<course-id>", "Course ID")
62
- .option("--output <format>", "Output format: json|csv|table|silent")
63
- .action(async (courseId, options, command) => {
64
- const output = (0, utils_js_1.getOutputFormat)(command);
65
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
66
- if (!apiContext) {
67
- process.exitCode = 1;
68
- return;
69
- }
70
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
71
- const course = courses.find(c => c.id === parseInt(courseId, 10));
72
- if (!course) {
73
- apiContext.log.error(`Course not found: ${courseId}`);
74
- process.exitCode = 1;
75
- return;
76
- }
77
- const progressData = {
78
- courseId: course.id,
79
- courseName: course.fullname,
80
- progress: course.progress ?? 0,
81
- startDate: course.startdate ? (0, utils_js_1.formatTimestamp)(course.startdate) : null,
82
- endDate: course.enddate ? (0, utils_js_1.formatTimestamp)(course.enddate) : null,
83
- };
84
- (0, index_js_1.formatAndOutput)(progressData, output, apiContext.log);
85
- });
86
- // Helper function to fetch syllabus from CMAP using GWT-RPC API
87
- async function fetchSyllabus(shortname) {
88
- try {
89
- const parts = shortname.split("_");
90
- if (parts.length < 2) {
91
- return { error: "Invalid course shortname format" };
92
- }
93
- const [yearTerm, opCode] = parts;
94
- // Build GWT-RPC request body
95
- // Format: 7|0|8|<base_url>|<permutation>|<service>|<method>|<param_types>|<params>...|1|2|3|4|3|5|5|5|6|7|8|
96
- 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|`;
97
- const response = await fetch("https://cmap.cycu.edu.tw:8443/Syllabus/syllabus/syllabusClientService", {
98
- method: "POST",
99
- headers: {
100
- "X-GWT-Permutation": "339796D6E7B561A6465F5E9B5F4943FA",
101
- "Accept": "text/x-gwt-rpc, */*; q=0.01",
102
- "Content-Type": "text/x-gwt-rpc; charset=UTF-8",
103
- },
104
- body: gwtBody,
105
- });
106
- if (!response.ok) {
107
- return { error: `HTTP ${response.status}`, url: "https://cmap.cycu.edu.tw:8443/Syllabus/syllabus/syllabusClientService" };
108
- }
109
- const rawText = await response.text();
110
- // GWT-RPC response format: //OK[...data...]
111
- if (!rawText.startsWith("//OK")) {
112
- return { error: "Invalid GWT-RPC response", rawResponse: rawText.slice(0, 200) };
113
- }
114
- // Extract the JSON array part from the GWT response
115
- // Response format: //OK[data1,data2,...]
116
- const content = rawText.slice(4); // Remove "//OK"
117
- // Parse the GWT string table - GWT uses a special format where strings are escaped
118
- // Format: ["string1","string2",...] or [123,"string2",...]
119
- const stringTable = [];
120
- // Simple parser for GWT string table
121
- let current = "";
122
- let inString = false;
123
- let escaped = false;
124
- for (let i = 0; i < content.length; i++) {
125
- const char = content[i];
126
- if (escaped) {
127
- // Handle escape sequences
128
- switch (char) {
129
- case 'n':
130
- current += '\n';
131
- break;
132
- case 'r':
133
- current += '\r';
134
- break;
135
- case 't':
136
- current += '\t';
137
- break;
138
- case '"':
139
- current += '"';
140
- break;
141
- case '\\':
142
- current += '\\';
143
- break;
144
- case '0':
145
- current += '\0';
146
- break;
147
- default:
148
- // Unknown escape, just append the char
149
- current += char;
150
- }
151
- escaped = false;
152
- continue;
153
- }
154
- if (char === "\\") {
155
- escaped = true;
156
- continue;
157
- }
158
- if (char === '"') {
159
- inString = !inString;
160
- if (!inString && current.length > 0) {
161
- stringTable.push(current);
162
- current = "";
163
- }
164
- continue;
165
- }
166
- if (inString) {
167
- current += char;
168
- }
169
- }
170
- // Parse schedule from string table
171
- // Strategy: Find week numbers (1-18), extract title (previous field) and date
172
- const schedule = [];
173
- const datePattern = /^\d{4}-\d{2}-\d{2}$/;
174
- // Track processed indices to avoid duplicates
175
- const processedIndices = new Set();
176
- for (let i = 0; i < stringTable.length; i++) {
177
- const s = stringTable[i];
178
- // Look for week numbers (1-18)
179
- if (/^[1-9]$|^1[0-8]$/.test(s) && !processedIndices.has(i)) {
180
- const week = s;
181
- let date = "";
182
- let title = "";
183
- // Previous field is the title
184
- if (i - 1 >= 0 && !processedIndices.has(i - 1)) {
185
- title = stringTable[i - 1];
186
- }
187
- // For week 1 & 2, find date before the week number
188
- // For week 18, also look before (last week has no "next week")
189
- // For other weeks (3-17), next field is next week's date
190
- if (week === "1" || week === "2" || week === "18") {
191
- // Look backwards for date pattern (search further back for week 18)
192
- const maxLookback = week === "18" ? 15 : 6;
193
- for (let j = i - 1; j >= Math.max(0, i - maxLookback); j--) {
194
- if (datePattern.test(stringTable[j]) && !processedIndices.has(j)) {
195
- date = stringTable[j];
196
- processedIndices.add(j);
197
- break;
198
- }
199
- }
200
- }
201
- else {
202
- // Week 3-17: look for next week number, then get date before it
203
- for (let j = i + 1; j < Math.min(i + 10, stringTable.length); j++) {
204
- if (/^[1-9]$|^1[0-8]$/.test(stringTable[j]) && !processedIndices.has(j)) {
205
- // Found next week, look before it for date
206
- for (let k = j - 1; k >= Math.max(0, j - 6); k--) {
207
- if (datePattern.test(stringTable[k]) && !processedIndices.has(k)) {
208
- date = stringTable[k];
209
- processedIndices.add(k);
210
- break;
211
- }
212
- }
213
- break;
214
- }
215
- }
216
- }
217
- // Clean up title
218
- title = title.trim()
219
- .replace(/[\r\n]+/g, ' ')
220
- .replace(/,+$/, '')
221
- .trim()
222
- .slice(0, 200);
223
- // Only add if we have title (date is optional, will be inferred if missing)
224
- if (title.length > 1) {
225
- // If no date found, try to infer from the last added date
226
- if (date.length === 0 && schedule.length > 0) {
227
- const lastEntry = schedule[schedule.length - 1];
228
- const lastDate = new Date(lastEntry.date);
229
- const nextDate = new Date(lastDate.getTime() + 7 * 24 * 60 * 60 * 1000);
230
- const year = nextDate.getFullYear();
231
- const month = String(nextDate.getMonth() + 1).padStart(2, "0");
232
- const day = String(nextDate.getDate()).padStart(2, "0");
233
- date = `${year}-${month}-${day}`;
234
- }
235
- schedule.push({
236
- week,
237
- date,
238
- title,
239
- });
240
- }
241
- processedIndices.add(i);
242
- }
243
- }
244
- // Sort by date to maintain order
245
- schedule.sort((a, b) => a.date.localeCompare(b.date));
246
- // Extract course info from string table
247
- const result = {
248
- yearTerm,
249
- opCode,
250
- url: `https://cmap.cycu.edu.tw:8443/Syllabus/CoursePreview.html?yearTerm=${yearTerm}&opCode=${opCode}&locale=zh_TW`,
251
- schedule,
252
- };
253
- // Try to find instructor (look for common patterns)
254
- for (let i = 0; i < stringTable.length; i++) {
255
- const s = stringTable[i];
256
- if (s.includes("教授") || s.includes("老師") || s.includes("教師") || s.includes("Instructor")) {
257
- result.instructor = s;
258
- break;
259
- }
260
- }
261
- return result;
262
- }
263
- catch (e) {
264
- return { error: e instanceof Error ? e.message : String(e) };
265
- }
266
- }
267
- coursesCmd
268
- .command("syllabus")
269
- .description("Show course syllabus (from CMAP)")
270
- .argument("<course-id>", "Course ID")
271
- .option("--output <format>", "Output format: json|csv|table|silent")
272
- .action(async (courseId, options, command) => {
273
- const output = (0, utils_js_1.getOutputFormat)(command);
274
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
275
- if (!apiContext) {
276
- process.exitCode = 1;
277
- return;
278
- }
279
- try {
280
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session);
281
- const course = courses.find(c => c.id === parseInt(courseId, 10));
282
- if (!course) {
283
- apiContext.log.error(`Course not found: ${courseId}`);
284
- process.exitCode = 1;
285
- return;
286
- }
287
- // Fetch syllabus from CMAP
288
- const syllabus = await fetchSyllabus(course.shortname);
289
- if (!syllabus) {
290
- apiContext.log.warn(`Syllabus not found for course: ${course.shortname}`);
291
- // Return course info at least
292
- (0, index_js_1.formatAndOutput)({
293
- courseId: course.id,
294
- shortname: course.shortname,
295
- fullname: course.fullname,
296
- note: "Syllabus not available from CMAP",
297
- }, output, apiContext.log);
298
- return;
299
- }
300
- // Combine course info with syllabus
301
- const result = {
302
- courseId: course.id,
303
- shortname: course.shortname,
304
- fullname: course.fullname,
305
- ...syllabus,
306
- };
307
- (0, index_js_1.formatAndOutput)(result, output, apiContext.log);
308
- }
309
- catch (e) {
310
- const msg = e instanceof Error ? e.message : String(e);
311
- apiContext.log.error(`Error fetching syllabus: ${msg}`);
312
- process.exitCode = 1;
313
- }
314
- });
315
- }
@@ -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;AAiBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+O5D"}
@@ -1,193 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerForumsCommand = registerForumsCommand;
4
- const utils_js_1 = require("../lib/utils.js");
5
- const moodle_js_1 = require("../lib/moodle.js");
6
- const auth_js_1 = require("../lib/auth.js");
7
- const index_js_1 = require("../index.js");
8
- function registerForumsCommand(program) {
9
- const forumsCmd = program.command("forums");
10
- forumsCmd.description("Forum operations");
11
- async function listForums(classification) {
12
- const apiContext = await (0, auth_js_1.createApiContext)({});
13
- if (!apiContext) {
14
- process.exitCode = 1;
15
- return;
16
- }
17
- const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, {
18
- classification,
19
- });
20
- const courseIds = courses.map(c => c.id);
21
- const wsForums = await (0, moodle_js_1.getForumsApi)(apiContext.session, courseIds);
22
- const courseMap = new Map(courses.map(c => [c.id, c]));
23
- const allForums = [];
24
- for (const wsForum of wsForums) {
25
- const course = courseMap.get(wsForum.courseid);
26
- if (course) {
27
- allForums.push({
28
- course_id: wsForum.courseid,
29
- course_name: course.fullname,
30
- intro: wsForum.intro,
31
- cmid: wsForum.cmid.toString(),
32
- forum_id: wsForum.id,
33
- name: wsForum.name,
34
- timemodified: wsForum.timemodified,
35
- });
36
- }
37
- }
38
- (0, index_js_1.formatAndOutput)(allForums, "json", apiContext.log, { status: "success", timestamp: new Date().toISOString(), total_courses: courses.length, total_forums: allForums.length });
39
- }
40
- forumsCmd
41
- .command("list")
42
- .description("List forums from in-progress courses")
43
- .action(() => listForums("inprogress"));
44
- forumsCmd
45
- .command("list-all")
46
- .description("List all forums across all courses")
47
- .option("--level <type>", "Course level: in_progress (default) | all", "in_progress")
48
- .action(async (options) => {
49
- const classification = options.level === "all" ? undefined : "inprogress";
50
- await listForums(classification);
51
- });
52
- forumsCmd
53
- .command("discussions")
54
- .description("List discussions in a forum (use forum ID)")
55
- .argument("<forum-id>", "Forum ID")
56
- .option("--output <format>", "Output format: json|csv|table|silent")
57
- .action(async (forumId, options, command) => {
58
- const output = (0, utils_js_1.getOutputFormat)(command);
59
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
60
- if (!apiContext) {
61
- process.exitCode = 1;
62
- return;
63
- }
64
- const resolved = await (0, moodle_js_1.resolveForumId)(apiContext.session, forumId);
65
- if (!resolved) {
66
- apiContext.log.error("Forum not found");
67
- process.exitCode = 1;
68
- return;
69
- }
70
- const discussions = await (0, moodle_js_1.getForumDiscussionsApi)(apiContext.session, resolved.forumId);
71
- const items = discussions.map(d => ({
72
- id: d.id,
73
- name: d.name,
74
- user_id: d.userId,
75
- time_modified: d.timeModified,
76
- post_count: d.postCount,
77
- unread: d.unread,
78
- message: (0, utils_js_1.stripHtmlTags)(d.message || ""),
79
- }));
80
- (0, index_js_1.formatAndOutput)(items, output, apiContext.log, { status: "success", timestamp: new Date().toISOString(), forum_id: resolved.forumId, forum_name: resolved.name ?? null, course_id: resolved.courseid ?? null, total_discussions: discussions.length });
81
- });
82
- forumsCmd
83
- .command("posts")
84
- .description("Show posts in a discussion")
85
- .argument("<discussion-id>", "Discussion ID")
86
- .option("--output <format>", "Output format: json|csv|table|silent")
87
- .action(async (discussionId, options, command) => {
88
- const output = (0, utils_js_1.getOutputFormat)(command);
89
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
90
- if (!apiContext) {
91
- process.exitCode = 1;
92
- return;
93
- }
94
- const posts = await (0, moodle_js_1.getDiscussionPostsApi)(apiContext.session, parseInt(discussionId, 10));
95
- const items = posts.map(p => ({
96
- id: p.id,
97
- subject: p.subject,
98
- author: p.author,
99
- author_id: p.authorId,
100
- created: (0, utils_js_1.formatTimestamp)(p.created),
101
- modified: (0, utils_js_1.formatTimestamp)(p.modified),
102
- message: p.message,
103
- unread: p.unread,
104
- }));
105
- (0, index_js_1.formatAndOutput)(items, output, apiContext.log, { status: "success", timestamp: new Date().toISOString(), discussion_id: discussionId, total_posts: posts.length });
106
- });
107
- forumsCmd
108
- .command("post")
109
- .description("Post a new discussion to a forum")
110
- .argument("<forum-id>", "Forum ID")
111
- .argument("<subject>", "Discussion subject")
112
- .argument("<message>", "Discussion message")
113
- .option("--subscribe", "Subscribe to the discussion", false)
114
- .option("--pin", "Pin the discussion", false)
115
- .action(async (forumId, subject, message, options, command) => {
116
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
117
- if (!apiContext) {
118
- process.exitCode = 1;
119
- return;
120
- }
121
- const { log, session } = apiContext;
122
- const resolved = await (0, moodle_js_1.resolveForumId)(session, forumId);
123
- if (!resolved) {
124
- log.error(`Forum not found: ${forumId}`);
125
- process.exitCode = 1;
126
- return;
127
- }
128
- log.info(`Posting to forum: ${resolved.name ?? forumId}`);
129
- const result = await (0, moodle_js_1.addForumDiscussionApi)(session, resolved.forumId, subject, message);
130
- if (result.success) {
131
- log.success(`✓ Discussion posted successfully!`);
132
- log.info(` Discussion ID: ${result.discussionId}`);
133
- }
134
- else {
135
- log.error(`✗ Failed to post discussion: ${result.error}`);
136
- process.exitCode = 1;
137
- }
138
- });
139
- forumsCmd
140
- .command("reply")
141
- .description("Reply to a discussion post")
142
- .argument("<post-id>", "Parent post ID to reply to")
143
- .argument("<subject>", "Reply subject")
144
- .argument("<message>", "Reply message")
145
- .option("--attachment-id <id>", "Draft file ID for attachment")
146
- .option("--inline-attachment-id <id>", "Draft file ID for inline attachment")
147
- .action(async (postId, subject, message, options, command) => {
148
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
149
- if (!apiContext) {
150
- process.exitCode = 1;
151
- return;
152
- }
153
- const { log, session } = apiContext;
154
- log.info(`Replying to post: ${postId}`);
155
- log.info(` Subject: ${subject}`);
156
- log.info(` Message: ${message}`);
157
- if (options.attachmentId) {
158
- log.info(` Attachment ID: ${options.attachmentId}`);
159
- }
160
- const result = await (0, moodle_js_1.addForumPostApi)(session, parseInt(postId, 10), subject, message, {
161
- attachmentId: options.attachmentId ? parseInt(options.attachmentId, 10) : undefined,
162
- inlineAttachmentId: options.inlineAttachmentId ? parseInt(options.inlineAttachmentId, 10) : undefined,
163
- });
164
- if (result.success) {
165
- log.success(`✓ Reply posted successfully!`);
166
- log.info(` Post ID: ${result.postId}`);
167
- }
168
- else {
169
- log.error(`✗ Failed to post reply: ${result.error}`);
170
- process.exitCode = 1;
171
- }
172
- });
173
- forumsCmd
174
- .command("delete")
175
- .description("Delete a forum post or discussion (by post ID)")
176
- .argument("<post-id>", "Post ID to delete (deletes entire discussion if it's the first post)")
177
- .action(async (postId, options, command) => {
178
- const apiContext = await (0, auth_js_1.createApiContext)(options, command);
179
- if (!apiContext) {
180
- process.exitCode = 1;
181
- return;
182
- }
183
- const { log, session } = apiContext;
184
- const result = await (0, moodle_js_1.deleteForumPostApi)(session, parseInt(postId, 10));
185
- if (result.success) {
186
- log.success(`✓ Post ${postId} deleted successfully!`);
187
- }
188
- else {
189
- log.error(`✗ Failed to delete post: ${result.error}`);
190
- process.exitCode = 1;
191
- }
192
- });
193
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerGradesCommand(program: Command): void;
3
- //# sourceMappingURL=grades.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"grades.d.ts","sourceRoot":"","sources":["../../../src/src/commands/grades.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+F5D"}