@mo7yw4ng/openape 1.0.1 → 1.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 (132) hide show
  1. package/README.md +8 -5
  2. package/esm/_dnt.polyfills.d.ts +83 -6
  3. package/esm/_dnt.polyfills.d.ts.map +1 -0
  4. package/esm/_dnt.polyfills.js +127 -1
  5. package/esm/_dnt.shims.d.ts +1 -0
  6. package/esm/_dnt.shims.d.ts.map +1 -0
  7. package/esm/deno.d.ts +2 -0
  8. package/esm/deno.d.ts.map +1 -0
  9. package/esm/deno.js +2 -1
  10. package/esm/src/commands/announcements.d.ts +3 -0
  11. package/esm/src/commands/announcements.d.ts.map +1 -0
  12. package/esm/src/commands/announcements.js +135 -0
  13. package/esm/src/commands/auth.d.ts +3 -0
  14. package/esm/src/commands/auth.d.ts.map +1 -0
  15. package/esm/src/commands/auth.js +260 -0
  16. package/esm/src/commands/calendar.d.ts +3 -0
  17. package/esm/src/commands/calendar.d.ts.map +1 -0
  18. package/esm/src/commands/calendar.js +180 -0
  19. package/esm/src/commands/courses.d.ts +3 -0
  20. package/esm/src/commands/courses.d.ts.map +1 -0
  21. package/esm/src/commands/courses.js +348 -0
  22. package/esm/src/commands/forums.d.ts +3 -0
  23. package/esm/src/commands/forums.d.ts.map +1 -0
  24. package/esm/src/commands/forums.js +226 -0
  25. package/esm/src/commands/grades.d.ts +3 -0
  26. package/esm/src/commands/grades.d.ts.map +1 -0
  27. package/esm/src/commands/grades.js +121 -0
  28. package/esm/src/commands/materials.d.ts +3 -0
  29. package/esm/src/commands/materials.d.ts.map +1 -0
  30. package/esm/src/commands/materials.js +522 -0
  31. package/esm/src/commands/quizzes.d.ts +3 -0
  32. package/esm/src/commands/quizzes.d.ts.map +1 -0
  33. package/esm/src/commands/quizzes.js +160 -0
  34. package/esm/src/commands/skills.d.ts +3 -0
  35. package/esm/src/commands/skills.d.ts.map +1 -0
  36. package/esm/src/commands/skills.js +110 -0
  37. package/esm/src/commands/videos.d.ts +3 -0
  38. package/esm/src/commands/videos.d.ts.map +1 -0
  39. package/esm/src/commands/videos.js +335 -0
  40. package/esm/src/index.d.ts +27 -0
  41. package/esm/src/index.d.ts.map +1 -0
  42. package/esm/src/index.js +149 -0
  43. package/esm/src/lib/auth.d.ts +25 -0
  44. package/esm/src/lib/auth.d.ts.map +1 -0
  45. package/esm/src/lib/auth.js +194 -0
  46. package/esm/src/lib/config.d.ts +6 -0
  47. package/esm/src/lib/config.d.ts.map +1 -0
  48. package/esm/src/lib/config.js +36 -0
  49. package/esm/src/lib/logger.d.ts +3 -0
  50. package/esm/src/lib/logger.d.ts.map +1 -0
  51. package/esm/src/lib/logger.js +24 -0
  52. package/esm/src/lib/moodle.d.ts +251 -0
  53. package/esm/src/lib/moodle.d.ts.map +1 -0
  54. package/esm/src/lib/moodle.js +833 -0
  55. package/esm/src/lib/session.d.ts +8 -0
  56. package/esm/src/lib/session.d.ts.map +1 -0
  57. package/esm/src/lib/session.js +42 -0
  58. package/esm/src/lib/token.d.ts +38 -0
  59. package/esm/src/lib/token.d.ts.map +1 -0
  60. package/esm/src/lib/token.js +178 -0
  61. package/esm/src/lib/types.d.ts +272 -0
  62. package/esm/src/lib/types.d.ts.map +1 -0
  63. package/esm/src/lib/types.js +1 -0
  64. package/esm/src/lib/utils.d.ts +37 -0
  65. package/esm/src/lib/utils.d.ts.map +1 -0
  66. package/esm/src/lib/utils.js +82 -0
  67. package/package.json +4 -4
  68. package/script/_dnt.polyfills.d.ts +83 -6
  69. package/script/_dnt.polyfills.d.ts.map +1 -0
  70. package/script/_dnt.polyfills.js +128 -0
  71. package/script/_dnt.shims.d.ts +1 -0
  72. package/script/_dnt.shims.d.ts.map +1 -0
  73. package/script/deno.d.ts +2 -0
  74. package/script/deno.d.ts.map +1 -0
  75. package/script/deno.js +2 -1
  76. package/script/src/commands/announcements.d.ts +1 -0
  77. package/script/src/commands/announcements.d.ts.map +1 -0
  78. package/script/src/commands/announcements.js +75 -222
  79. package/script/src/commands/auth.d.ts +2 -1
  80. package/script/src/commands/auth.d.ts.map +1 -0
  81. package/script/src/commands/auth.js +52 -24
  82. package/script/src/commands/calendar.d.ts +1 -0
  83. package/script/src/commands/calendar.d.ts.map +1 -0
  84. package/script/src/commands/calendar.js +112 -301
  85. package/script/src/commands/courses.d.ts +1 -0
  86. package/script/src/commands/courses.d.ts.map +1 -0
  87. package/script/src/commands/courses.js +43 -173
  88. package/script/src/commands/forums.d.ts +1 -0
  89. package/script/src/commands/forums.d.ts.map +1 -0
  90. package/script/src/commands/forums.js +145 -316
  91. package/script/src/commands/grades.d.ts +1 -0
  92. package/script/src/commands/grades.d.ts.map +1 -0
  93. package/script/src/commands/grades.js +62 -194
  94. package/script/src/commands/materials.d.ts +1 -0
  95. package/script/src/commands/materials.d.ts.map +1 -0
  96. package/script/src/commands/materials.js +283 -178
  97. package/script/src/commands/quizzes.d.ts +1 -0
  98. package/script/src/commands/quizzes.d.ts.map +1 -0
  99. package/script/src/commands/quizzes.js +40 -102
  100. package/script/src/commands/skills.d.ts +1 -0
  101. package/script/src/commands/skills.d.ts.map +1 -0
  102. package/script/src/commands/skills.js +17 -18
  103. package/script/src/commands/videos.d.ts +1 -0
  104. package/script/src/commands/videos.d.ts.map +1 -0
  105. package/script/src/commands/videos.js +127 -120
  106. package/script/src/index.d.ts +1 -0
  107. package/script/src/index.d.ts.map +1 -0
  108. package/script/src/index.js +5 -5
  109. package/script/src/lib/auth.d.ts +1 -0
  110. package/script/src/lib/auth.d.ts.map +1 -0
  111. package/script/src/lib/auth.js +9 -10
  112. package/script/src/lib/config.d.ts +1 -0
  113. package/script/src/lib/config.d.ts.map +1 -0
  114. package/script/src/lib/config.js +6 -7
  115. package/script/src/lib/logger.d.ts +1 -0
  116. package/script/src/lib/logger.d.ts.map +1 -0
  117. package/script/src/lib/logger.js +1 -2
  118. package/script/src/lib/moodle.d.ts +72 -55
  119. package/script/src/lib/moodle.d.ts.map +1 -0
  120. package/script/src/lib/moodle.js +275 -350
  121. package/script/src/lib/session.d.ts +1 -0
  122. package/script/src/lib/session.d.ts.map +1 -0
  123. package/script/src/lib/session.js +3 -29
  124. package/script/src/lib/token.d.ts +16 -5
  125. package/script/src/lib/token.d.ts.map +1 -0
  126. package/script/src/lib/token.js +71 -36
  127. package/script/src/lib/types.d.ts +11 -0
  128. package/script/src/lib/types.d.ts.map +1 -0
  129. package/script/src/lib/utils.d.ts +32 -0
  130. package/script/src/lib/utils.d.ts.map +1 -0
  131. package/script/src/lib/utils.js +93 -13
  132. package/skills/openape/SKILL.md +6 -26
@@ -5,3 +5,4 @@ import type { AppConfig, Logger, SessionInfo } from "./types.js";
5
5
  * The sesskey is required for all AJAX calls.
6
6
  */
7
7
  export declare function extractSessionInfo(page: Page, config: AppConfig, log: Logger, wsToken?: string): Promise<SessionInfo>;
8
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEjE;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CA6CtB"}
@@ -1,30 +1,6 @@
1
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 (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.extractSessionInfo = void 0;
27
- const dntShim = __importStar(require("../../_dnt.shims.js"));
3
+ exports.extractSessionInfo = extractSessionInfo;
28
4
  /**
29
5
  * Extract Moodle sesskey from the current page.
30
6
  * The sesskey is required for all AJAX calls.
@@ -38,9 +14,8 @@ async function extractSessionInfo(page, config, log, wsToken) {
38
14
  });
39
15
  }
40
16
  // Try extracting sesskey from M.cfg (Moodle's JS config object)
41
- let sesskey = await page.evaluate(() => {
42
- return dntShim.dntGlobalThis.M?.cfg?.sesskey ?? null;
43
- });
17
+ // Use string to avoid dnt transforming globalThis/window to dntShim
18
+ let sesskey = await page.evaluate("() => self.M?.cfg?.sesskey ?? null");
44
19
  // Fallback: extract from a hidden input
45
20
  if (!sesskey) {
46
21
  sesskey = await page.evaluate(() => {
@@ -68,4 +43,3 @@ async function extractSessionInfo(page, config, log, wsToken) {
68
43
  }
69
44
  return sessionInfo;
70
45
  }
71
- exports.extractSessionInfo = extractSessionInfo;
@@ -1,16 +1,26 @@
1
1
  import type { Page } from "playwright-core";
2
2
  import type { AppConfig, Logger } from "./types.js";
3
3
  /**
4
- * Get the WS token file path from the auth state path.
5
- * E.g., .auth/storage-state.json -> .auth/ws-token.json
4
+ * Get the session metadata file path from the auth state path.
5
+ * E.g., .auth/storage-state.json -> .auth/session-meta.json
6
6
  */
7
- export declare function getWsTokenPath(authStatePath: string): string;
7
+ export declare function getSessionMetaPath(authStatePath: string): string;
8
8
  /**
9
- * Load WS token from file if it exists.
9
+ * Load sesskey from metadata if it exists and is valid.
10
+ * Sesskey is typically valid for 6 hours.
11
+ */
12
+ export declare function loadSesskey(authStatePath: string): string | null;
13
+ /**
14
+ * Save sesskey to metadata.
15
+ */
16
+ export declare function saveSesskey(authStatePath: string, sesskey: string): void;
17
+ /**
18
+ * Load WS token from metadata if it exists and is valid.
19
+ * WS token is valid for 24 hours.
10
20
  */
11
21
  export declare function loadWsToken(authStatePath: string): string | null;
12
22
  /**
13
- * Save WS token to file.
23
+ * Save WS token to metadata.
14
24
  */
15
25
  export declare function saveWsToken(authStatePath: string, token: string): void;
16
26
  /**
@@ -25,3 +35,4 @@ export declare function saveWsToken(authStatePath: string, token: string): void;
25
35
  * @throws Error if token acquisition fails
26
36
  */
27
37
  export declare function acquireWsToken(page: Page, config: AppConfig, log: Logger): Promise<string>;
38
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/src/lib/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAWpD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAGhE;AA8BD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAShE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAKxE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAShE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAKtE;AAuBD;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,CAsEjB"}
@@ -3,60 +3,96 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.acquireWsToken = exports.saveWsToken = exports.loadWsToken = exports.getWsTokenPath = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
6
+ exports.getSessionMetaPath = getSessionMetaPath;
7
+ exports.loadSesskey = loadSesskey;
8
+ exports.saveSesskey = saveSesskey;
9
+ exports.loadWsToken = loadWsToken;
10
+ exports.saveWsToken = saveWsToken;
11
+ exports.acquireWsToken = acquireWsToken;
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ const node_path_1 = __importDefault(require("node:path"));
9
14
  /**
10
- * Get the WS token file path from the auth state path.
11
- * E.g., .auth/storage-state.json -> .auth/ws-token.json
15
+ * Get the session metadata file path from the auth state path.
16
+ * E.g., .auth/storage-state.json -> .auth/session-meta.json
12
17
  */
13
- function getWsTokenPath(authStatePath) {
14
- const dir = path_1.default.dirname(authStatePath);
15
- return path_1.default.join(dir, "ws-token.json");
18
+ function getSessionMetaPath(authStatePath) {
19
+ const dir = node_path_1.default.dirname(authStatePath);
20
+ return node_path_1.default.join(dir, "session-meta.json");
16
21
  }
17
- exports.getWsTokenPath = getWsTokenPath;
18
22
  /**
19
- * Load WS token from file if it exists.
23
+ * Load session metadata from file.
20
24
  */
21
- function loadWsToken(authStatePath) {
22
- const tokenPath = getWsTokenPath(authStatePath);
25
+ function loadSessionMeta(authStatePath) {
26
+ const metaPath = getSessionMetaPath(authStatePath);
23
27
  try {
24
- if (fs_1.default.existsSync(tokenPath)) {
25
- const content = fs_1.default.readFileSync(tokenPath, "utf8");
26
- const data = JSON.parse(content);
27
- // Check if token is not too old (Moodle tokens typically expire after some time)
28
- if (data.token && data.timestamp) {
29
- const age = Date.now() - data.timestamp;
30
- // Consider token valid if less than 24 hours old
31
- if (age < 24 * 60 * 60 * 1000) {
32
- return data.token;
33
- }
34
- }
28
+ if (node_fs_1.default.existsSync(metaPath)) {
29
+ const content = node_fs_1.default.readFileSync(metaPath, "utf8");
30
+ return JSON.parse(content);
35
31
  }
36
32
  }
37
33
  catch {
38
- // Ignore errors, token will be re-acquired
34
+ // Ignore errors, return empty meta
39
35
  }
40
- return null;
36
+ return {};
41
37
  }
42
- exports.loadWsToken = loadWsToken;
43
38
  /**
44
- * Save WS token to file.
39
+ * Save session metadata to file.
45
40
  */
46
- function saveWsToken(authStatePath, token) {
47
- const tokenPath = getWsTokenPath(authStatePath);
41
+ function saveSessionMeta(authStatePath, meta) {
42
+ const metaPath = getSessionMetaPath(authStatePath);
48
43
  try {
49
- const data = {
50
- token,
51
- timestamp: Date.now(),
52
- };
53
- fs_1.default.writeFileSync(tokenPath, JSON.stringify(data, null, 2));
44
+ node_fs_1.default.writeFileSync(metaPath, JSON.stringify(meta, null, 2));
54
45
  }
55
46
  catch {
56
47
  // Ignore save errors
57
48
  }
58
49
  }
59
- exports.saveWsToken = saveWsToken;
50
+ /**
51
+ * Load sesskey from metadata if it exists and is valid.
52
+ * Sesskey is typically valid for 6 hours.
53
+ */
54
+ function loadSesskey(authStatePath) {
55
+ const meta = loadSessionMeta(authStatePath);
56
+ if (meta.sesskey && meta.sesskeyTimestamp) {
57
+ const age = Date.now() - meta.sesskeyTimestamp;
58
+ if (age < 6 * 60 * 60 * 1000) {
59
+ return meta.sesskey;
60
+ }
61
+ }
62
+ return null;
63
+ }
64
+ /**
65
+ * Save sesskey to metadata.
66
+ */
67
+ function saveSesskey(authStatePath, sesskey) {
68
+ const meta = loadSessionMeta(authStatePath);
69
+ meta.sesskey = sesskey;
70
+ meta.sesskeyTimestamp = Date.now();
71
+ saveSessionMeta(authStatePath, meta);
72
+ }
73
+ /**
74
+ * Load WS token from metadata if it exists and is valid.
75
+ * WS token is valid for 24 hours.
76
+ */
77
+ function loadWsToken(authStatePath) {
78
+ const meta = loadSessionMeta(authStatePath);
79
+ if (meta.wsToken && meta.wsTokenTimestamp) {
80
+ const age = Date.now() - meta.wsTokenTimestamp;
81
+ if (age < 24 * 60 * 60 * 1000) {
82
+ return meta.wsToken;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ /**
88
+ * Save WS token to metadata.
89
+ */
90
+ function saveWsToken(authStatePath, token) {
91
+ const meta = loadSessionMeta(authStatePath);
92
+ meta.wsToken = token;
93
+ meta.wsTokenTimestamp = Date.now();
94
+ saveSessionMeta(authStatePath, meta);
95
+ }
60
96
  /**
61
97
  * Extract and decode the Web Service Token from moodlemobile:// URL
62
98
  * Format: moodlemobile://token=BASE64_DATA
@@ -151,4 +187,3 @@ async function acquireWsToken(page, config, log) {
151
187
  throw error;
152
188
  }
153
189
  }
154
- exports.acquireWsToken = acquireWsToken;
@@ -35,6 +35,7 @@ export interface SuperVideoModule {
35
35
  cmid: string;
36
36
  name: string;
37
37
  url: string;
38
+ instance?: number;
38
39
  isComplete: boolean;
39
40
  }
40
41
  export interface VideoActivity {
@@ -78,12 +79,21 @@ export interface ForumDiscussion {
78
79
  name: string;
79
80
  firstPostId: number;
80
81
  userId: number;
82
+ userFullName: string;
81
83
  groupId?: number;
82
84
  timedue?: number;
83
85
  timeModified: number;
86
+ timeStart?: number;
87
+ timeEnd?: number;
84
88
  userModified?: number;
89
+ userModifiedFullName?: string;
85
90
  postCount?: number;
86
91
  unread?: boolean;
92
+ subject?: string;
93
+ message?: string;
94
+ pinned?: boolean;
95
+ locked?: boolean;
96
+ starred?: boolean;
87
97
  }
88
98
  export interface AnnouncementPost {
89
99
  id: number;
@@ -259,3 +269,4 @@ export interface CommandOptions {
259
269
  headed?: boolean;
260
270
  dryRun?: boolean;
261
271
  }
272
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/src/lib/types.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,37 @@
1
+ import type { OutputFormat } from "./types.js";
1
2
  /**
2
3
  * Returns the base directory for config/storage resolving.
3
4
  * Handles both Deno (raw/compiled) and Node.js (npx).
4
5
  */
5
6
  export declare function getBaseDir(): string;
7
+ /**
8
+ * Strip HTML tags from a string.
9
+ * Preserves text content while removing all HTML markup.
10
+ */
11
+ export declare function stripHtmlTags(html: string): string;
12
+ /**
13
+ * Extract clean course name from Moodle fullname.
14
+ * Removes mlang tags, course codes, and instructor info.
15
+ * Example: "{mlang zh-tw}1142爵士樂賞析(遠距)-楊曊恩..." -> "爵士樂賞析"
16
+ */
17
+ export declare function extractCourseName(fullname: string): string;
18
+ /**
19
+ * Get output format from command options (global or local).
20
+ * Defaults to "json" if not specified.
21
+ */
22
+ export declare function getOutputFormat(command: {
23
+ optsWithGlobals(): {
24
+ output?: OutputFormat;
25
+ };
26
+ }): OutputFormat;
27
+ /**
28
+ * Determine if logs should be silenced based on output format and verbosity.
29
+ * JSON output without verbose flag silences logs.
30
+ */
31
+ export declare function shouldSilenceLogs(outputFormat: OutputFormat, verbose?: boolean): boolean;
32
+ /**
33
+ * Sanitize filename by removing invalid characters and limiting length.
34
+ * Replaces invalid characters with underscores and limits to maxLength.
35
+ */
36
+ export declare function sanitizeFilename(name: string, maxLength?: number): string;
37
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/src/lib/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAcnC;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAelD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,eAAe,IAAI;QAAE,MAAM,CAAC,EAAE,YAAY,CAAA;KAAE,CAAA;CAAE,GAAG,YAAY,CAGvG;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAExF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,MAAM,CAK9E"}
@@ -15,17 +15,32 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
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
+ })();
25
35
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.getBaseDir = void 0;
36
+ exports.getBaseDir = getBaseDir;
37
+ exports.stripHtmlTags = stripHtmlTags;
38
+ exports.extractCourseName = extractCourseName;
39
+ exports.getOutputFormat = getOutputFormat;
40
+ exports.shouldSilenceLogs = shouldSilenceLogs;
41
+ exports.sanitizeFilename = sanitizeFilename;
27
42
  const dntShim = __importStar(require("../../_dnt.shims.js"));
28
- const path_1 = require("path");
43
+ const node_path_1 = require("node:path");
29
44
  /**
30
45
  * Returns the base directory for config/storage resolving.
31
46
  * Handles both Deno (raw/compiled) and Node.js (npx).
@@ -33,11 +48,76 @@ const path_1 = require("path");
33
48
  function getBaseDir() {
34
49
  // @ts-ignore - Deno global is available in Deno
35
50
  if (typeof dntShim.Deno !== "undefined" && typeof dntShim.Deno.execPath === "function") {
36
- // @ts-ignore
37
- const exeDir = (0, path_1.dirname)(dntShim.Deno.execPath());
38
- return exeDir.includes("deno") ? process.cwd() : exeDir;
51
+ try {
52
+ // @ts-ignore
53
+ const exeDir = (0, node_path_1.dirname)(dntShim.Deno.execPath());
54
+ return exeDir.includes("deno") ? process.cwd() : exeDir;
55
+ }
56
+ catch {
57
+ // Deno shim (dnt) or Deno not installed
58
+ return process.cwd();
59
+ }
39
60
  }
40
61
  // Node.js or dnt runtime
41
62
  return process.cwd();
42
63
  }
43
- exports.getBaseDir = getBaseDir;
64
+ /**
65
+ * Strip HTML tags from a string.
66
+ * Preserves text content while removing all HTML markup.
67
+ */
68
+ function stripHtmlTags(html) {
69
+ if (!html)
70
+ return "";
71
+ // Remove HTML tags
72
+ return html.replace(/<[^>]*>/g, "")
73
+ // Replace HTML entities with their characters
74
+ .replace(/&nbsp;/g, " ")
75
+ .replace(/&amp;/g, "&")
76
+ .replace(/&lt;/g, "<")
77
+ .replace(/&gt;/g, ">")
78
+ .replace(/&quot;/g, '"')
79
+ .replace(/&#39;/g, "'")
80
+ .replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(parseInt(dec, 10)))
81
+ // Clean up excessive whitespace
82
+ .replace(/\s+/g, " ")
83
+ .trim();
84
+ }
85
+ /**
86
+ * Extract clean course name from Moodle fullname.
87
+ * Removes mlang tags, course codes, and instructor info.
88
+ * Example: "{mlang zh-tw}1142爵士樂賞析(遠距)-楊曊恩..." -> "爵士樂賞析"
89
+ */
90
+ function extractCourseName(fullname) {
91
+ if (!fullname)
92
+ return "";
93
+ // Remove {mlang ...} tags
94
+ let cleaned = fullname.replace(/\{mlang[^}]*\}/g, "");
95
+ // Match: 4+ digits + course name (until (, -, or [)
96
+ const match = cleaned.match(/\d{4,}([^([-]+)/);
97
+ return match ? match[1].trim() : fullname;
98
+ }
99
+ /**
100
+ * Get output format from command options (global or local).
101
+ * Defaults to "json" if not specified.
102
+ */
103
+ function getOutputFormat(command) {
104
+ const opts = command.optsWithGlobals();
105
+ return opts.output || "json";
106
+ }
107
+ /**
108
+ * Determine if logs should be silenced based on output format and verbosity.
109
+ * JSON output without verbose flag silences logs.
110
+ */
111
+ function shouldSilenceLogs(outputFormat, verbose) {
112
+ return outputFormat === "json" && !verbose;
113
+ }
114
+ /**
115
+ * Sanitize filename by removing invalid characters and limiting length.
116
+ * Replaces invalid characters with underscores and limits to maxLength.
117
+ */
118
+ function sanitizeFilename(name, maxLength = 200) {
119
+ return name
120
+ .replace(/[<>:"/\\|?*]/g, "_")
121
+ .replace(/\s+/g, "_")
122
+ .substring(0, maxLength);
123
+ }
@@ -9,39 +9,29 @@ Use the `openape` command to access CYCU iLearning (Moodle) platform. OpenApe pr
9
9
 
10
10
  ## Setup
11
11
 
12
- Install via npm:
12
+ If `openape` is not installed:
13
13
  ```bash
14
14
  npm install -g @mo7yw4ng/openape
15
15
  ```
16
16
 
17
- Or run without installing:
18
- ```bash
19
- npx @mo7yw4ng/openape --help
20
- ```
21
-
22
- Or install via Deno/JSR:
23
- ```bash
24
- deno install -A -g -n openape jsr:@openape/openape
25
- ```
26
-
27
17
  If not authenticated, run:
28
18
  ```bash
29
19
  openape login
30
20
  ```
31
- A browser will open for Microsoft OAuth SSO. Complete MFA login manually — no username/password input needed in the terminal.
21
+ A browser will open for Microsoft OAuth SSO. Complete MFA login manually.
32
22
 
33
23
  Session is saved to `.auth/storage-state.json` and persists between runs. If session expires, run `openape login` again.
34
24
 
35
25
  ## Discovering Commands
36
26
 
37
- Every command supports `--help` for full option details:
27
+ **Every command supports `--help` for full option details:**
38
28
  ```bash
39
29
  openape --help
40
30
  openape courses --help
41
31
  openape videos complete --help
42
32
  ```
43
33
 
44
- Add `--output json` to any command for machine-readable output. Use `--output csv` for spreadsheet format, `--output table` for human-readable tables, or `--output silent` to suppress output.
34
+ Add `--output json` to any command for machine-readable output. Use `--output csv` for spreadsheet format, `--output table` for human-readable tables.
45
35
 
46
36
  ## Course Commands
47
37
 
@@ -94,18 +84,10 @@ openape videos list <course-id> --incomplete-only
94
84
  # Complete all incomplete videos in a course
95
85
  openape videos complete <course-id>
96
86
 
97
- # Dry-run: discover videos without completing
98
- openape videos complete <course-id> --dry-run
99
-
100
87
  # Complete all incomplete videos across all courses
101
88
  openape videos complete-all
102
-
103
- # Dry-run all courses
104
- openape videos complete-all --dry-run
105
89
  ```
106
90
 
107
- **Note:** Video completion forges SuperVideo progress AJAX calls to simulate watching the entire video. The server accepts the progress but completion status may take time to update in the course state.
108
-
109
91
  ### Downloading videos
110
92
 
111
93
  ```bash
@@ -305,6 +287,7 @@ openape courses syllabus <course-id>
305
287
  **Review discussions:** Catch up on forum activity.
306
288
  ```bash
307
289
  openape forums list --level in_progress
290
+ openape forums list-all
308
291
  openape forums discussions <forum-id>
309
292
  openape forums posts <discussion-id>
310
293
  ```
@@ -323,9 +306,6 @@ openape materials download-all --level in_progress
323
306
 
324
307
  ## Tips
325
308
 
326
- - Use `--dry-run` with `videos complete` to preview what will be completed
327
309
  - Use `--level in_progress` (default) to focus on active courses
328
310
  - Use `--output json` for scripting and automation
329
- - Use `--output table` for human-readable output
330
- - Session persists after login, no need to re-authenticate
331
- - WS API mode is used by default for faster performance; browser mode is fallback
311
+ - Use `--output table` for human-readable output