@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.
- package/bin/openape +29 -0
- package/bin/openape.js +29 -0
- package/package.json +22 -28
- package/LICENSE +0 -21
- package/README.md +0 -138
- package/esm/_dnt.polyfills.d.ts +0 -101
- package/esm/_dnt.polyfills.d.ts.map +0 -1
- package/esm/_dnt.polyfills.js +0 -127
- package/esm/_dnt.shims.d.ts +0 -6
- package/esm/_dnt.shims.d.ts.map +0 -1
- package/esm/_dnt.shims.js +0 -61
- package/esm/deno.d.ts +0 -25
- package/esm/deno.d.ts.map +0 -1
- package/esm/deno.js +0 -23
- package/esm/package.json +0 -3
- package/esm/src/commands/announcements.d.ts +0 -3
- package/esm/src/commands/announcements.d.ts.map +0 -1
- package/esm/src/commands/announcements.js +0 -71
- package/esm/src/commands/assignments.d.ts +0 -3
- package/esm/src/commands/assignments.d.ts.map +0 -1
- package/esm/src/commands/assignments.js +0 -229
- package/esm/src/commands/auth.d.ts +0 -3
- package/esm/src/commands/auth.d.ts.map +0 -1
- package/esm/src/commands/auth.js +0 -290
- package/esm/src/commands/calendar.d.ts +0 -3
- package/esm/src/commands/calendar.d.ts.map +0 -1
- package/esm/src/commands/calendar.js +0 -127
- package/esm/src/commands/courses.d.ts +0 -3
- package/esm/src/commands/courses.d.ts.map +0 -1
- package/esm/src/commands/courses.js +0 -312
- package/esm/src/commands/forums.d.ts +0 -3
- package/esm/src/commands/forums.d.ts.map +0 -1
- package/esm/src/commands/forums.js +0 -190
- package/esm/src/commands/grades.d.ts +0 -3
- package/esm/src/commands/grades.d.ts.map +0 -1
- package/esm/src/commands/grades.js +0 -84
- package/esm/src/commands/materials.d.ts +0 -3
- package/esm/src/commands/materials.d.ts.map +0 -1
- package/esm/src/commands/materials.js +0 -402
- package/esm/src/commands/quizzes.d.ts +0 -3
- package/esm/src/commands/quizzes.d.ts.map +0 -1
- package/esm/src/commands/quizzes.js +0 -236
- package/esm/src/commands/skills.d.ts +0 -3
- package/esm/src/commands/skills.d.ts.map +0 -1
- package/esm/src/commands/skills.js +0 -106
- package/esm/src/commands/upload.d.ts +0 -3
- package/esm/src/commands/upload.d.ts.map +0 -1
- package/esm/src/commands/upload.js +0 -55
- package/esm/src/commands/videos.d.ts +0 -3
- package/esm/src/commands/videos.d.ts.map +0 -1
- package/esm/src/commands/videos.js +0 -266
- package/esm/src/index.d.ts +0 -28
- package/esm/src/index.d.ts.map +0 -1
- package/esm/src/index.js +0 -164
- package/esm/src/lib/auth.d.ts +0 -66
- package/esm/src/lib/auth.d.ts.map +0 -1
- package/esm/src/lib/auth.js +0 -286
- package/esm/src/lib/config.d.ts +0 -6
- package/esm/src/lib/config.d.ts.map +0 -1
- package/esm/src/lib/config.js +0 -36
- package/esm/src/lib/logger.d.ts +0 -3
- package/esm/src/lib/logger.d.ts.map +0 -1
- package/esm/src/lib/logger.js +0 -26
- package/esm/src/lib/moodle.d.ts +0 -447
- package/esm/src/lib/moodle.d.ts.map +0 -1
- package/esm/src/lib/moodle.js +0 -1353
- package/esm/src/lib/session.d.ts +0 -8
- package/esm/src/lib/session.d.ts.map +0 -1
- package/esm/src/lib/session.js +0 -42
- package/esm/src/lib/token.d.ts +0 -38
- package/esm/src/lib/token.d.ts.map +0 -1
- package/esm/src/lib/token.js +0 -178
- package/esm/src/lib/types.d.ts +0 -189
- package/esm/src/lib/types.d.ts.map +0 -1
- package/esm/src/lib/types.js +0 -2
- package/esm/src/lib/utils.d.ts +0 -52
- package/esm/src/lib/utils.d.ts.map +0 -1
- package/esm/src/lib/utils.js +0 -122
- package/script/_dnt.polyfills.d.ts +0 -101
- package/script/_dnt.polyfills.d.ts.map +0 -1
- package/script/_dnt.polyfills.js +0 -130
- package/script/_dnt.shims.d.ts +0 -6
- package/script/_dnt.shims.d.ts.map +0 -1
- package/script/_dnt.shims.js +0 -65
- package/script/deno.d.ts +0 -25
- package/script/deno.d.ts.map +0 -1
- package/script/deno.js +0 -25
- package/script/package.json +0 -3
- package/script/src/commands/announcements.d.ts +0 -3
- package/script/src/commands/announcements.d.ts.map +0 -1
- package/script/src/commands/announcements.js +0 -74
- package/script/src/commands/assignments.d.ts +0 -3
- package/script/src/commands/assignments.d.ts.map +0 -1
- package/script/src/commands/assignments.js +0 -268
- package/script/src/commands/auth.d.ts +0 -3
- package/script/src/commands/auth.d.ts.map +0 -1
- package/script/src/commands/auth.js +0 -296
- package/script/src/commands/calendar.d.ts +0 -3
- package/script/src/commands/calendar.d.ts.map +0 -1
- package/script/src/commands/calendar.js +0 -133
- package/script/src/commands/courses.d.ts +0 -3
- package/script/src/commands/courses.d.ts.map +0 -1
- package/script/src/commands/courses.js +0 -315
- package/script/src/commands/forums.d.ts +0 -3
- package/script/src/commands/forums.d.ts.map +0 -1
- package/script/src/commands/forums.js +0 -193
- package/script/src/commands/grades.d.ts +0 -3
- package/script/src/commands/grades.d.ts.map +0 -1
- package/script/src/commands/grades.js +0 -87
- package/script/src/commands/materials.d.ts +0 -3
- package/script/src/commands/materials.d.ts.map +0 -1
- package/script/src/commands/materials.js +0 -408
- package/script/src/commands/quizzes.d.ts +0 -3
- package/script/src/commands/quizzes.d.ts.map +0 -1
- package/script/src/commands/quizzes.js +0 -239
- package/script/src/commands/skills.d.ts +0 -3
- package/script/src/commands/skills.d.ts.map +0 -1
- package/script/src/commands/skills.js +0 -112
- package/script/src/commands/upload.d.ts +0 -3
- package/script/src/commands/upload.d.ts.map +0 -1
- package/script/src/commands/upload.js +0 -61
- package/script/src/commands/videos.d.ts +0 -3
- package/script/src/commands/videos.d.ts.map +0 -1
- package/script/src/commands/videos.js +0 -272
- package/script/src/index.d.ts +0 -28
- package/script/src/index.d.ts.map +0 -1
- package/script/src/index.js +0 -171
- package/script/src/lib/auth.d.ts +0 -66
- package/script/src/lib/auth.d.ts.map +0 -1
- package/script/src/lib/auth.js +0 -296
- package/script/src/lib/config.d.ts +0 -6
- package/script/src/lib/config.d.ts.map +0 -1
- package/script/src/lib/config.js +0 -42
- package/script/src/lib/logger.d.ts +0 -3
- package/script/src/lib/logger.d.ts.map +0 -1
- package/script/src/lib/logger.js +0 -29
- package/script/src/lib/moodle.d.ts +0 -447
- package/script/src/lib/moodle.d.ts.map +0 -1
- package/script/src/lib/moodle.js +0 -1425
- package/script/src/lib/session.d.ts +0 -8
- package/script/src/lib/session.d.ts.map +0 -1
- package/script/src/lib/session.js +0 -45
- package/script/src/lib/token.d.ts +0 -38
- package/script/src/lib/token.d.ts.map +0 -1
- package/script/src/lib/token.js +0 -189
- package/script/src/lib/types.d.ts +0 -189
- package/script/src/lib/types.d.ts.map +0 -1
- package/script/src/lib/types.js +0 -3
- package/script/src/lib/utils.d.ts +0 -52
- package/script/src/lib/utils.d.ts.map +0 -1
- package/script/src/lib/utils.js +0 -167
- package/skills/openape/SKILL.md +0 -115
|
@@ -1,272 +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.registerVideosCommand = registerVideosCommand;
|
|
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_path_1 = __importDefault(require("node:path"));
|
|
12
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
-
function registerVideosCommand(program) {
|
|
14
|
-
const videosCmd = program.command("videos");
|
|
15
|
-
videosCmd.description("Video progress operations");
|
|
16
|
-
videosCmd
|
|
17
|
-
.command("list")
|
|
18
|
-
.description("List videos in a course")
|
|
19
|
-
.argument("<course-id>", "Course ID")
|
|
20
|
-
.option("--incomplete-only", "Show only incomplete videos")
|
|
21
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
22
|
-
.action(async (courseId, options, command) => {
|
|
23
|
-
const output = (0, utils_js_1.getOutputFormat)(command);
|
|
24
|
-
const apiContext = await (0, auth_js_1.createApiContext)(options, command);
|
|
25
|
-
if (!apiContext) {
|
|
26
|
-
process.exitCode = 1;
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
let videos = await (0, moodle_js_1.getSupervideosInCourseApi)(apiContext.session, parseInt(courseId, 10));
|
|
30
|
-
// Filter for incomplete videos if requested
|
|
31
|
-
if (options.incompleteOnly) {
|
|
32
|
-
videos = videos.filter(v => !v.isComplete);
|
|
33
|
-
}
|
|
34
|
-
(0, index_js_1.formatAndOutput)(videos, output, apiContext.log);
|
|
35
|
-
});
|
|
36
|
-
videosCmd
|
|
37
|
-
.command("complete")
|
|
38
|
-
.description("Complete videos in a course (uses API for list & completion, browser for metadata)")
|
|
39
|
-
.argument("<course-id>", "Course ID")
|
|
40
|
-
.option("--dry-run", "Discover videos but don't complete them")
|
|
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
|
-
// Get API context for getting incomplete videos and completion
|
|
45
|
-
const apiContext = await (0, auth_js_1.createApiContext)(options, command);
|
|
46
|
-
if (!apiContext) {
|
|
47
|
-
process.exitCode = 1;
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
// Get incomplete videos via API (fast, no browser needed)
|
|
51
|
-
const incompleteVideos = await (0, moodle_js_1.getIncompleteVideosApi)(apiContext.session, parseInt(courseId, 10));
|
|
52
|
-
if (incompleteVideos.length === 0) {
|
|
53
|
-
apiContext.log.info("所有影片已完成(或無影片)。");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
apiContext.log.info(`找到 ${incompleteVideos.length} 部未完成影片`);
|
|
57
|
-
// Dry-run: show videos without needing browser
|
|
58
|
-
if (options.dryRun) {
|
|
59
|
-
const results = incompleteVideos.map(v => ({ name: v.name, success: true }));
|
|
60
|
-
for (const video of incompleteVideos) {
|
|
61
|
-
apiContext.log.info(` [試執行] ${video.name}`);
|
|
62
|
-
}
|
|
63
|
-
apiContext.log.info(`\n執行結果: ${results.length} 影片將被完成`);
|
|
64
|
-
if (output !== "silent") {
|
|
65
|
-
(0, index_js_1.formatAndOutput)(results, output, apiContext.log);
|
|
66
|
-
}
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
// Need browser only for getting viewId and duration (not needed for dry-run)
|
|
70
|
-
const context = await (0, auth_js_1.createBrowserContext)(options, command);
|
|
71
|
-
if (!context) {
|
|
72
|
-
process.exitCode = 1;
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const { log, page, browser, context: browserContext } = context;
|
|
76
|
-
try {
|
|
77
|
-
const results = [];
|
|
78
|
-
for (const sv of incompleteVideos) {
|
|
79
|
-
log.info(`處理中: ${sv.name}`);
|
|
80
|
-
try {
|
|
81
|
-
const video = await (0, moodle_js_1.getVideoMetadata)(page, sv.url, log);
|
|
82
|
-
// Use WS API for completion
|
|
83
|
-
const success = await (0, moodle_js_1.completeVideoApi)(apiContext.session, { ...video, cmid: sv.cmid.toString() });
|
|
84
|
-
if (success) {
|
|
85
|
-
log.success(` 已完成!`);
|
|
86
|
-
results.push({ name: sv.name, success: true });
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
log.error(` 失敗。`);
|
|
90
|
-
results.push({ name: sv.name, success: false, error: "Failed to complete" });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
95
|
-
log.error(` 錯誤: ${msg}`);
|
|
96
|
-
results.push({ name: sv.name, success: false, error: msg });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const completed = results.filter(r => r.success).length;
|
|
100
|
-
const failed = results.filter(r => !r.success).length;
|
|
101
|
-
log.info(`\n執行結果: ${completed} 成功, ${failed} 失敗`);
|
|
102
|
-
if (output !== "silent") {
|
|
103
|
-
(0, index_js_1.formatAndOutput)(results, output, log);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
await (0, auth_js_1.closeBrowserSafely)(browser, browserContext);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
videosCmd
|
|
111
|
-
.command("complete-all")
|
|
112
|
-
.description("Complete all incomplete videos across all courses (uses API for list & completion, browser for metadata)")
|
|
113
|
-
.option("--dry-run", "Discover videos but don't complete them")
|
|
114
|
-
.option("--output <format>", "Output format: json|csv|table|silent")
|
|
115
|
-
.action(async (options, command) => {
|
|
116
|
-
const output = (0, utils_js_1.getOutputFormat)(command);
|
|
117
|
-
// Get API context for getting incomplete videos and completion
|
|
118
|
-
const apiContext = await (0, auth_js_1.createApiContext)(options, command);
|
|
119
|
-
if (!apiContext) {
|
|
120
|
-
process.exitCode = 1;
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
// Get all courses via API
|
|
124
|
-
const classification = undefined; // all courses
|
|
125
|
-
const courses = await (0, moodle_js_1.getEnrolledCoursesApi)(apiContext.session, { classification });
|
|
126
|
-
apiContext.log.info(`掃描 ${courses.length} 個課程...`);
|
|
127
|
-
// Collect all incomplete videos across all courses using flatMap for cleaner code
|
|
128
|
-
const allIncompleteVideos = (await Promise.allSettled(courses.map(async (course) => {
|
|
129
|
-
try {
|
|
130
|
-
const videos = await (0, moodle_js_1.getIncompleteVideosApi)(apiContext.session, course.id);
|
|
131
|
-
return videos.map((video) => ({
|
|
132
|
-
courseId: course.id,
|
|
133
|
-
courseName: course.fullname,
|
|
134
|
-
cmid: video.cmid,
|
|
135
|
-
name: video.name,
|
|
136
|
-
url: video.url,
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
139
|
-
catch (e) {
|
|
140
|
-
apiContext.log.warn(`無法取得課程 ${course.fullname} 的影片: ${e}`);
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
})))
|
|
144
|
-
.filter((result) => result.status === "fulfilled")
|
|
145
|
-
.flatMap((result) => result.status === "fulfilled" ? result.value : []);
|
|
146
|
-
if (allIncompleteVideos.length === 0) {
|
|
147
|
-
apiContext.log.info("所有影片已完成(或無影片)。");
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
apiContext.log.info(`找到 ${allIncompleteVideos.length} 部未完成影片`);
|
|
151
|
-
// Dry-run: show videos without needing browser
|
|
152
|
-
if (options.dryRun) {
|
|
153
|
-
for (const video of allIncompleteVideos) {
|
|
154
|
-
apiContext.log.info(` [試執行] [${video.courseName}] ${video.name}`);
|
|
155
|
-
}
|
|
156
|
-
apiContext.log.info("\n===== 執行結果 =====");
|
|
157
|
-
apiContext.log.info(`掃描課程數: ${courses.length}`);
|
|
158
|
-
apiContext.log.info(`找到未完成影片: ${allIncompleteVideos.length}`);
|
|
159
|
-
apiContext.log.info(`執行影片數: ${allIncompleteVideos.length} (試執行)`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
// Need browser only for getting viewId and duration (not needed for dry-run)
|
|
163
|
-
const context = await (0, auth_js_1.createBrowserContext)(options, command);
|
|
164
|
-
if (!context) {
|
|
165
|
-
process.exitCode = 1;
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
const { log, page, browser, context: browserContext } = context;
|
|
169
|
-
try {
|
|
170
|
-
const allResults = [];
|
|
171
|
-
let totalCompleted = 0;
|
|
172
|
-
let totalFailed = 0;
|
|
173
|
-
for (const video of allIncompleteVideos) {
|
|
174
|
-
log.info(`處理中: [${video.courseName}] ${video.name}`);
|
|
175
|
-
try {
|
|
176
|
-
const metadata = await (0, moodle_js_1.getVideoMetadata)(page, video.url, log);
|
|
177
|
-
// Use WS API for completion
|
|
178
|
-
const success = await (0, moodle_js_1.completeVideoApi)(apiContext.session, { ...metadata, cmid: video.cmid.toString() });
|
|
179
|
-
if (success) {
|
|
180
|
-
log.success(` 已完成!`);
|
|
181
|
-
allResults.push({ courseName: video.courseName, name: video.name, success: true });
|
|
182
|
-
totalCompleted++;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
log.error(` 失敗。`);
|
|
186
|
-
allResults.push({ courseName: video.courseName, name: video.name, success: false, error: "Failed to complete" });
|
|
187
|
-
totalFailed++;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch (err) {
|
|
191
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
192
|
-
log.error(` 錯誤: ${msg}`);
|
|
193
|
-
allResults.push({ courseName: video.courseName, name: video.name, success: false, error: msg });
|
|
194
|
-
totalFailed++;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
log.info("\n===== 執行結果 =====");
|
|
198
|
-
log.info(`掃描課程數: ${courses.length}`);
|
|
199
|
-
log.info(`找到未完成影片: ${allIncompleteVideos.length}`);
|
|
200
|
-
log.info(`執行影片數: ${totalCompleted}`);
|
|
201
|
-
if (totalFailed > 0)
|
|
202
|
-
log.warn(`失敗影片數: ${totalFailed}`);
|
|
203
|
-
if (output !== "silent") {
|
|
204
|
-
(0, index_js_1.formatAndOutput)(allResults, output, log);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
finally {
|
|
208
|
-
await (0, auth_js_1.closeBrowserSafely)(browser, browserContext);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
videosCmd
|
|
212
|
-
.command("download")
|
|
213
|
-
.description("Download videos from a course (requires browser)")
|
|
214
|
-
.argument("<course-id>", "Course ID")
|
|
215
|
-
.option("--output-dir <path>", "Output directory", "./downloads/videos")
|
|
216
|
-
.option("--incomplete-only", "Download only incomplete videos")
|
|
217
|
-
.action(async (courseId, options, command) => {
|
|
218
|
-
const context = await (0, auth_js_1.createBrowserContext)(options, command);
|
|
219
|
-
if (!context) {
|
|
220
|
-
process.exitCode = 1;
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const { log, page, session, browser, context: browserContext } = context;
|
|
224
|
-
try {
|
|
225
|
-
const videos = await (0, moodle_js_1.getSupervideosInCourse)(page, session, parseInt(courseId, 10), log, {
|
|
226
|
-
incompleteOnly: options.incompleteOnly,
|
|
227
|
-
});
|
|
228
|
-
log.info(`找到 ${videos.length} 個影片`);
|
|
229
|
-
const baseDir = (0, utils_js_1.getBaseDir)();
|
|
230
|
-
const outputDir = node_path_1.default.resolve(baseDir, options.outputDir);
|
|
231
|
-
node_fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
232
|
-
const downloaded = [];
|
|
233
|
-
for (const video of videos) {
|
|
234
|
-
const filename = (0, utils_js_1.sanitizeFilename)(video.name) + ".mp4";
|
|
235
|
-
const outputPath = node_path_1.default.join(outputDir, filename);
|
|
236
|
-
log.info(`處理中: ${video.name}`);
|
|
237
|
-
try {
|
|
238
|
-
const metadata = await (0, moodle_js_1.getVideoMetadata)(page, video.url, log);
|
|
239
|
-
const result = await (0, moodle_js_1.downloadVideo)(page, metadata, outputPath, log);
|
|
240
|
-
if (result.success) {
|
|
241
|
-
log.success(` 已下載: ${result.path}`);
|
|
242
|
-
downloaded.push({ name: video.name, path: result.path, success: true, type: result.type });
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
log.warn(` 失敗: ${result.error}`);
|
|
246
|
-
downloaded.push({ name: video.name, path: "", success: false, error: result.error, type: result.type });
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
catch (err) {
|
|
250
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
251
|
-
log.error(` 錯誤: ${msg}`);
|
|
252
|
-
downloaded.push({ name: video.name, path: "", success: false, error: msg });
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
const completed = downloaded.filter(d => d.success).length;
|
|
256
|
-
const failed = downloaded.filter(d => !d.success).length;
|
|
257
|
-
log.info(`\n執行結果: ${completed} 成功, ${failed} 失敗`);
|
|
258
|
-
(0, index_js_1.formatAndOutput)(downloaded, "json", log, {
|
|
259
|
-
status: "success",
|
|
260
|
-
timestamp: new Date().toISOString(),
|
|
261
|
-
course_id: courseId,
|
|
262
|
-
output_dir: outputDir,
|
|
263
|
-
total_videos: videos.length,
|
|
264
|
-
downloaded: completed,
|
|
265
|
-
failed,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
finally {
|
|
269
|
-
await (0, auth_js_1.closeBrowserSafely)(browser, browserContext);
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
}
|
package/script/src/index.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import "../_dnt.polyfills.js";
|
|
3
|
-
import { createLogger } from "./lib/logger.js";
|
|
4
|
-
import type { AppConfig, Logger, SessionInfo, OutputFormat } from "./lib/types.js";
|
|
5
|
-
/**
|
|
6
|
-
* Create a session context for commands that need authentication.
|
|
7
|
-
*/
|
|
8
|
-
export declare function createSessionContext(options: {
|
|
9
|
-
config?: string;
|
|
10
|
-
session?: string;
|
|
11
|
-
verbose?: boolean;
|
|
12
|
-
silent?: boolean;
|
|
13
|
-
headed?: boolean;
|
|
14
|
-
interactive?: boolean;
|
|
15
|
-
}): Promise<{
|
|
16
|
-
config: AppConfig;
|
|
17
|
-
log: Logger;
|
|
18
|
-
page: import("playwright-core").Page;
|
|
19
|
-
session: SessionInfo;
|
|
20
|
-
} | null>;
|
|
21
|
-
/**
|
|
22
|
-
* Helper to output formatted data.
|
|
23
|
-
* For JSON output (agent mode), exits immediately after output.
|
|
24
|
-
* If meta is provided, it is printed as the first line before items.
|
|
25
|
-
*/
|
|
26
|
-
export declare function formatAndOutput<T extends Record<string, unknown>>(data: T | T[], format: OutputFormat, log: Logger, meta?: Record<string, unknown>): void;
|
|
27
|
-
export { createLogger, type AppConfig, type Logger, type SessionInfo, type OutputFormat };
|
|
28
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":";AACA,OAAO,sBAAsB,CAAC;AAO9B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAwEnF;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,GACA,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,iBAAiB,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,WAAW,CAAA;CAAE,GAAG,IAAI,CAAC,CAoBhH;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/D,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EACb,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CA4BN;AA2CD,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,CAAC"}
|
package/script/src/index.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.createLogger = void 0;
|
|
8
|
-
exports.createSessionContext = createSessionContext;
|
|
9
|
-
exports.formatAndOutput = formatAndOutput;
|
|
10
|
-
require("../_dnt.polyfills.js");
|
|
11
|
-
const utils_js_1 = require("./lib/utils.js");
|
|
12
|
-
const commander_1 = require("commander");
|
|
13
|
-
const config_js_1 = require("./lib/config.js");
|
|
14
|
-
const auth_js_1 = require("./lib/auth.js");
|
|
15
|
-
const session_js_1 = require("./lib/session.js");
|
|
16
|
-
const logger_js_1 = require("./lib/logger.js");
|
|
17
|
-
Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_js_1.createLogger; } });
|
|
18
|
-
const deno_js_1 = __importDefault(require("../deno.js"));
|
|
19
|
-
// Import command handlers
|
|
20
|
-
const courses_js_1 = require("./commands/courses.js");
|
|
21
|
-
const videos_js_1 = require("./commands/videos.js");
|
|
22
|
-
const quizzes_js_1 = require("./commands/quizzes.js");
|
|
23
|
-
const auth_js_2 = require("./commands/auth.js");
|
|
24
|
-
const materials_js_1 = require("./commands/materials.js");
|
|
25
|
-
const grades_js_1 = require("./commands/grades.js");
|
|
26
|
-
const forums_js_1 = require("./commands/forums.js");
|
|
27
|
-
const announcements_js_1 = require("./commands/announcements.js");
|
|
28
|
-
const calendar_js_1 = require("./commands/calendar.js");
|
|
29
|
-
const skills_js_1 = require("./commands/skills.js");
|
|
30
|
-
const assignments_js_1 = require("./commands/assignments.js");
|
|
31
|
-
const upload_js_1 = require("./commands/upload.js");
|
|
32
|
-
const program = new commander_1.Command();
|
|
33
|
-
program
|
|
34
|
-
.name("openape")
|
|
35
|
-
.description(deno_js_1.default.description)
|
|
36
|
-
.version(deno_js_1.default.version);
|
|
37
|
-
// Global options
|
|
38
|
-
program
|
|
39
|
-
.option("--config <path>", "Custom config file path")
|
|
40
|
-
.option("--session <path>", "Session file path", ".auth/storage-state.json")
|
|
41
|
-
.option("--output <format>", "Output format: json|csv|table|silent", "json")
|
|
42
|
-
.option("--verbose", "Enable debug logging")
|
|
43
|
-
.option("--silent", "Suppress all log output (JSON only)")
|
|
44
|
-
.option("--headed", "Run browser in visible mode");
|
|
45
|
-
// Register subcommands
|
|
46
|
-
(0, auth_js_2.registerCommand)(program);
|
|
47
|
-
(0, courses_js_1.registerCoursesCommand)(program);
|
|
48
|
-
(0, videos_js_1.registerVideosCommand)(program);
|
|
49
|
-
(0, quizzes_js_1.registerQuizzesCommand)(program);
|
|
50
|
-
(0, materials_js_1.registerMaterialsCommand)(program);
|
|
51
|
-
(0, grades_js_1.registerGradesCommand)(program);
|
|
52
|
-
(0, forums_js_1.registerForumsCommand)(program);
|
|
53
|
-
(0, announcements_js_1.registerAnnouncementsCommand)(program);
|
|
54
|
-
(0, calendar_js_1.registerCalendarCommand)(program);
|
|
55
|
-
(0, skills_js_1.registerSkillsCommand)(program);
|
|
56
|
-
(0, assignments_js_1.registerAssignmentsCommand)(program);
|
|
57
|
-
(0, upload_js_1.registerUploadCommand)(program);
|
|
58
|
-
/**
|
|
59
|
-
* Load configuration and authenticate, returning the context for commands.
|
|
60
|
-
*/
|
|
61
|
-
async function createCommandContext(options) {
|
|
62
|
-
const log = (0, logger_js_1.createLogger)(options.verbose, options.silent);
|
|
63
|
-
const baseDir = (0, utils_js_1.getBaseDir)();
|
|
64
|
-
const config = (0, config_js_1.loadConfig)(baseDir);
|
|
65
|
-
// Apply CLI overrides
|
|
66
|
-
if (options.headed)
|
|
67
|
-
config.headless = false;
|
|
68
|
-
if (options.session)
|
|
69
|
-
config.authStatePath = options.session;
|
|
70
|
-
return { config, log };
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Create a session context for commands that need authentication.
|
|
74
|
-
*/
|
|
75
|
-
async function createSessionContext(options) {
|
|
76
|
-
const context = await createCommandContext(options);
|
|
77
|
-
if (!context)
|
|
78
|
-
return null;
|
|
79
|
-
const { config, log } = context;
|
|
80
|
-
log.info("啟動瀏覽器...");
|
|
81
|
-
const { browser, context: browserContext, page, wsToken } = await (0, auth_js_1.launchAuthenticated)(config, log);
|
|
82
|
-
try {
|
|
83
|
-
const session = await (0, session_js_1.extractSessionInfo)(page, config, log, wsToken);
|
|
84
|
-
// Keep the browser context alive for the duration of the command
|
|
85
|
-
// Note: Caller is responsible for closing the browser
|
|
86
|
-
return { config, log, page, session };
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
await browserContext.close();
|
|
90
|
-
await browser.close();
|
|
91
|
-
throw err;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Helper to output formatted data.
|
|
96
|
-
* For JSON output (agent mode), exits immediately after output.
|
|
97
|
-
* If meta is provided, it is printed as the first line before items.
|
|
98
|
-
*/
|
|
99
|
-
function formatAndOutput(data, format, log, meta) {
|
|
100
|
-
if (format === "json") {
|
|
101
|
-
if (meta) {
|
|
102
|
-
console.log(JSON.stringify(meta));
|
|
103
|
-
}
|
|
104
|
-
if (Array.isArray(data)) {
|
|
105
|
-
for (const item of data) {
|
|
106
|
-
console.log(JSON.stringify(item));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
console.log(JSON.stringify(data));
|
|
111
|
-
}
|
|
112
|
-
// Exit immediately for AI agent - no need to wait for browser cleanup
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
115
|
-
else if (format === "csv") {
|
|
116
|
-
const arr = Array.isArray(data) ? data : [data];
|
|
117
|
-
if (arr.length === 0)
|
|
118
|
-
return;
|
|
119
|
-
const fields = Object.keys(arr[0]);
|
|
120
|
-
console.log(formatAsCsv(arr, fields));
|
|
121
|
-
}
|
|
122
|
-
else if (format === "table") {
|
|
123
|
-
const arr = Array.isArray(data) ? data : [data];
|
|
124
|
-
if (arr.length === 0) {
|
|
125
|
-
console.log("No data");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
console.log(formatAsTable(arr));
|
|
129
|
-
}
|
|
130
|
-
// "silent" produces no output
|
|
131
|
-
}
|
|
132
|
-
function formatAsCsv(data, fields) {
|
|
133
|
-
const headers = fields.join(",");
|
|
134
|
-
const rows = data.map((item) => {
|
|
135
|
-
return fields.map((field) => {
|
|
136
|
-
const value = item[field];
|
|
137
|
-
if (value === null || value === undefined)
|
|
138
|
-
return "";
|
|
139
|
-
if (typeof value === "string") {
|
|
140
|
-
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
141
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
142
|
-
}
|
|
143
|
-
return value;
|
|
144
|
-
}
|
|
145
|
-
return String(value);
|
|
146
|
-
}).join(",");
|
|
147
|
-
});
|
|
148
|
-
return [headers, ...rows].join("\n");
|
|
149
|
-
}
|
|
150
|
-
function formatAsTable(data) {
|
|
151
|
-
const allFields = Array.from(new Set(data.flatMap((item) => Object.keys(item))));
|
|
152
|
-
const widths = {};
|
|
153
|
-
allFields.forEach((field) => {
|
|
154
|
-
widths[field] = Math.max(field.length, ...data.map((item) => String(item[field] ?? "").length)) + 2;
|
|
155
|
-
});
|
|
156
|
-
const header = allFields.map((f) => f.padEnd(widths[f])).join(" | ");
|
|
157
|
-
const separator = allFields.map((f) => "-".repeat(widths[f] - 1)).join("-+-");
|
|
158
|
-
const rows = data.map((item) => {
|
|
159
|
-
return allFields.map((f) => String(item[f] ?? "").padEnd(widths[f])).join(" | ");
|
|
160
|
-
});
|
|
161
|
-
return [header, separator, ...rows].join("\n");
|
|
162
|
-
}
|
|
163
|
-
// Run the program
|
|
164
|
-
if (globalThis[Symbol.for("import-meta-ponyfill-commonjs")](require, module).main) {
|
|
165
|
-
// If no subcommand provided, show help
|
|
166
|
-
const args = process.argv.slice(2);
|
|
167
|
-
if (args.length === 0) {
|
|
168
|
-
program.help();
|
|
169
|
-
}
|
|
170
|
-
program.parse();
|
|
171
|
-
}
|
package/script/src/lib/auth.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { type Browser, type BrowserContext, type Page } from "playwright-core";
|
|
2
|
-
import type { AppConfig, Logger, OutputFormat, SessionInfo } from "./types.js";
|
|
3
|
-
/**
|
|
4
|
-
* Find a Chromium-based browser executable on Windows, macOS, or Linux.
|
|
5
|
-
* Priority: Edge → Chrome → Brave
|
|
6
|
-
*/
|
|
7
|
-
export declare function findEdgePath(): string;
|
|
8
|
-
/**
|
|
9
|
-
* Launch a browser and return an authenticated context.
|
|
10
|
-
* Tries to restore a saved session first; falls back to fresh OAuth login.
|
|
11
|
-
* Also acquires Moodle Web Service Token for API calls.
|
|
12
|
-
*/
|
|
13
|
-
export declare function launchAuthenticated(config: AppConfig, log: Logger): Promise<{
|
|
14
|
-
browser: Browser;
|
|
15
|
-
context: BrowserContext;
|
|
16
|
-
page: Page;
|
|
17
|
-
wsToken?: string;
|
|
18
|
-
}>;
|
|
19
|
-
/**
|
|
20
|
-
* Safely close browser and context with timeout.
|
|
21
|
-
* Designed for AI agent usage - no human interaction needed.
|
|
22
|
-
* If noWait is true, initiates cleanup but doesn't wait for completion.
|
|
23
|
-
*
|
|
24
|
-
* Note: Closes sequentially (context first, then browser) to avoid libuv
|
|
25
|
-
* assertion failures on Windows when handles are closed concurrently.
|
|
26
|
-
*/
|
|
27
|
-
export declare function closeBrowserSafely(browser: Browser, context?: BrowserContext, timeoutMs?: number, noWait?: boolean): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Create API context for WS token operations (no browser required).
|
|
30
|
-
* Returns null if session is invalid or WS token is missing.
|
|
31
|
-
*/
|
|
32
|
-
export declare function createApiContext(options: {
|
|
33
|
-
verbose?: boolean;
|
|
34
|
-
headed?: boolean;
|
|
35
|
-
}, command?: {
|
|
36
|
-
optsWithGlobals(): {
|
|
37
|
-
output?: OutputFormat;
|
|
38
|
-
verbose?: boolean;
|
|
39
|
-
};
|
|
40
|
-
}): Promise<{
|
|
41
|
-
log: Logger;
|
|
42
|
-
session: {
|
|
43
|
-
wsToken: string;
|
|
44
|
-
moodleBaseUrl: string;
|
|
45
|
-
};
|
|
46
|
-
} | null>;
|
|
47
|
-
/**
|
|
48
|
-
* Create an authenticated browser context for commands that need page access.
|
|
49
|
-
* Launches a browser, restores or creates a session, and extracts session info.
|
|
50
|
-
*/
|
|
51
|
-
export declare function createBrowserContext(options: {
|
|
52
|
-
verbose?: boolean;
|
|
53
|
-
headed?: boolean;
|
|
54
|
-
}, command?: {
|
|
55
|
-
optsWithGlobals(): {
|
|
56
|
-
output?: OutputFormat;
|
|
57
|
-
verbose?: boolean;
|
|
58
|
-
};
|
|
59
|
-
}): Promise<{
|
|
60
|
-
log: Logger;
|
|
61
|
-
page: Page;
|
|
62
|
-
session: SessionInfo;
|
|
63
|
-
browser: Browser;
|
|
64
|
-
context: BrowserContext;
|
|
65
|
-
} | null>;
|
|
66
|
-
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/src/lib/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM/E;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAiDrC;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA4DtF;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,cAAc,EACxB,SAAS,GAAE,MAAa,EACxB,MAAM,GAAE,OAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAoHD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAChD,OAAO,CAAC,EAAE;IAAE,eAAe,IAAI;QAAE,MAAM,CAAC,EAAE,YAAY,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GAC5E,OAAO,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD,GAAG,IAAI,CAAC,CAoBR;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAChD,OAAO,CAAC,EAAE;IAAE,eAAe,IAAI;QAAE,MAAM,CAAC,EAAE,YAAY,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,GAC5E,OAAO,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;CACzB,GAAG,IAAI,CAAC,CA0BR"}
|