@falkenslab/moodle-driver 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/areas/activities/build.d.ts +98 -0
- package/dist/areas/activities/build.js +246 -0
- package/dist/areas/activities/build.js.map +1 -0
- package/dist/areas/activities/content.d.ts +53 -0
- package/dist/areas/activities/content.js +113 -0
- package/dist/areas/activities/content.js.map +1 -0
- package/dist/areas/assignments/grader.d.ts +68 -0
- package/dist/areas/assignments/grader.js +169 -0
- package/dist/areas/assignments/grader.js.map +1 -0
- package/dist/areas/assignments/grading.d.ts +86 -0
- package/dist/areas/assignments/grading.js +162 -0
- package/dist/areas/assignments/grading.js.map +1 -0
- package/dist/areas/assignments/student.d.ts +57 -0
- package/dist/areas/assignments/student.js +115 -0
- package/dist/areas/assignments/student.js.map +1 -0
- package/dist/areas/calendar/calendar.d.ts +43 -0
- package/dist/areas/calendar/calendar.js +84 -0
- package/dist/areas/calendar/calendar.js.map +1 -0
- package/dist/areas/completion/completion.d.ts +50 -0
- package/dist/areas/completion/completion.js +112 -0
- package/dist/areas/completion/completion.js.map +1 -0
- package/dist/areas/courses/list.d.ts +59 -0
- package/dist/areas/courses/list.js +74 -0
- package/dist/areas/courses/list.js.map +1 -0
- package/dist/areas/courses/overview.d.ts +83 -0
- package/dist/areas/courses/overview.js +137 -0
- package/dist/areas/courses/overview.js.map +1 -0
- package/dist/areas/courses/state.d.ts +51 -0
- package/dist/areas/courses/state.js +45 -0
- package/dist/areas/courses/state.js.map +1 -0
- package/dist/areas/courses/structure.d.ts +89 -0
- package/dist/areas/courses/structure.js +104 -0
- package/dist/areas/courses/structure.js.map +1 -0
- package/dist/areas/forums/discussions.d.ts +57 -0
- package/dist/areas/forums/discussions.js +119 -0
- package/dist/areas/forums/discussions.js.map +1 -0
- package/dist/areas/forums/posts.d.ts +83 -0
- package/dist/areas/forums/posts.js +114 -0
- package/dist/areas/forums/posts.js.map +1 -0
- package/dist/areas/grades/gradebook.d.ts +104 -0
- package/dist/areas/grades/gradebook.js +250 -0
- package/dist/areas/grades/gradebook.js.map +1 -0
- package/dist/areas/grades/user-report.d.ts +26 -0
- package/dist/areas/grades/user-report.js +71 -0
- package/dist/areas/grades/user-report.js.map +1 -0
- package/dist/areas/h5p/h5p.d.ts +33 -0
- package/dist/areas/h5p/h5p.js +104 -0
- package/dist/areas/h5p/h5p.js.map +1 -0
- package/dist/areas/lessons/lessons.d.ts +60 -0
- package/dist/areas/lessons/lessons.js +145 -0
- package/dist/areas/lessons/lessons.js.map +1 -0
- package/dist/areas/messages/messages.d.ts +68 -0
- package/dist/areas/messages/messages.js +84 -0
- package/dist/areas/messages/messages.js.map +1 -0
- package/dist/areas/participants/participants.d.ts +57 -0
- package/dist/areas/participants/participants.js +135 -0
- package/dist/areas/participants/participants.js.map +1 -0
- package/dist/areas/questions/banks.d.ts +83 -0
- package/dist/areas/questions/banks.js +239 -0
- package/dist/areas/questions/banks.js.map +1 -0
- package/dist/areas/quizzes/attempts.d.ts +123 -0
- package/dist/areas/quizzes/attempts.js +300 -0
- package/dist/areas/quizzes/attempts.js.map +1 -0
- package/dist/areas/quizzes/results.d.ts +36 -0
- package/dist/areas/quizzes/results.js +93 -0
- package/dist/areas/quizzes/results.js.map +1 -0
- package/dist/areas/quizzes/structure.d.ts +42 -0
- package/dist/areas/quizzes/structure.js +100 -0
- package/dist/areas/quizzes/structure.js.map +1 -0
- package/dist/areas/site/info.d.ts +25 -0
- package/dist/areas/site/info.js +45 -0
- package/dist/areas/site/info.js.map +1 -0
- package/dist/areas/site/users.d.ts +56 -0
- package/dist/areas/site/users.js +89 -0
- package/dist/areas/site/users.js.map +1 -0
- package/dist/areas/surveys/choice.d.ts +38 -0
- package/dist/areas/surveys/choice.js +105 -0
- package/dist/areas/surveys/choice.js.map +1 -0
- package/dist/areas/surveys/surveys.d.ts +51 -0
- package/dist/areas/surveys/surveys.js +201 -0
- package/dist/areas/surveys/surveys.js.map +1 -0
- package/dist/browser/chromium.d.ts +15 -0
- package/dist/browser/chromium.js +53 -0
- package/dist/browser/chromium.js.map +1 -0
- package/dist/browser/cookies.d.ts +11 -0
- package/dist/browser/cookies.js +31 -0
- package/dist/browser/cookies.js.map +1 -0
- package/dist/browser/manager.d.ts +57 -0
- package/dist/browser/manager.js +177 -0
- package/dist/browser/manager.js.map +1 -0
- package/dist/browser.d.ts +53 -0
- package/dist/browser.js +167 -0
- package/dist/browser.js.map +1 -0
- package/dist/categories.d.ts +28 -0
- package/dist/categories.js +222 -0
- package/dist/categories.js.map +1 -0
- package/dist/chromiumInstaller.d.ts +15 -0
- package/dist/chromiumInstaller.js +52 -0
- package/dist/chromiumInstaller.js.map +1 -0
- package/dist/cli/args.d.ts +33 -0
- package/dist/cli/args.js +194 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/commands.generated.d.ts +4 -0
- package/dist/cli/commands.generated.js +2203 -0
- package/dist/cli/commands.generated.js.map +1 -0
- package/dist/cli/config.d.ts +46 -0
- package/dist/cli/config.js +107 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +19 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.d.ts +4 -0
- package/dist/cli/output.js +71 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/run.d.ts +15 -0
- package/dist/cli/run.js +194 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spec.d.ts +42 -0
- package/dist/cli/spec.js +4 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/client.d.ts +217 -0
- package/dist/client.js +179 -0
- package/dist/client.js.map +1 -0
- package/dist/core/ajax.d.ts +25 -0
- package/dist/core/ajax.js +85 -0
- package/dist/core/ajax.js.map +1 -0
- package/dist/core/editor.d.ts +18 -0
- package/dist/core/editor.js +51 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/errors.d.ts +12 -0
- package/dist/core/errors.js +10 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/files.d.ts +16 -0
- package/dist/core/files.js +58 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/forms.d.ts +45 -0
- package/dist/core/forms.js +200 -0
- package/dist/core/forms.js.map +1 -0
- package/dist/core/http.d.ts +18 -0
- package/dist/core/http.js +22 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/inPage.d.ts +8 -0
- package/dist/core/inPage.js +10 -0
- package/dist/core/inPage.js.map +1 -0
- package/dist/core/login.d.ts +40 -0
- package/dist/core/login.js +83 -0
- package/dist/core/login.js.map +1 -0
- package/dist/core/pace.d.ts +4 -0
- package/dist/core/pace.js +21 -0
- package/dist/core/pace.js.map +1 -0
- package/dist/core/session.d.ts +55 -0
- package/dist/core/session.js +157 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/site.d.ts +8 -0
- package/dist/core/site.js +36 -0
- package/dist/core/site.js.map +1 -0
- package/dist/core/text.d.ts +21 -0
- package/dist/core/text.js +113 -0
- package/dist/core/text.js.map +1 -0
- package/dist/home.d.ts +19 -0
- package/dist/home.js +54 -0
- package/dist/home.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/internals.d.ts +11 -0
- package/dist/internals.js +15 -0
- package/dist/internals.js.map +1 -0
- package/dist/moodle/activities.d.ts +94 -0
- package/dist/moodle/activities.js +240 -0
- package/dist/moodle/activities.js.map +1 -0
- package/dist/moodle/ajax.d.ts +22 -0
- package/dist/moodle/ajax.js +91 -0
- package/dist/moodle/ajax.js.map +1 -0
- package/dist/moodle/assign.d.ts +65 -0
- package/dist/moodle/assign.js +162 -0
- package/dist/moodle/assign.js.map +1 -0
- package/dist/moodle/calendar.d.ts +41 -0
- package/dist/moodle/calendar.js +80 -0
- package/dist/moodle/calendar.js.map +1 -0
- package/dist/moodle/choice.d.ts +33 -0
- package/dist/moodle/choice.js +100 -0
- package/dist/moodle/choice.js.map +1 -0
- package/dist/moodle/completion.d.ts +48 -0
- package/dist/moodle/completion.js +108 -0
- package/dist/moodle/completion.js.map +1 -0
- package/dist/moodle/content.d.ts +43 -0
- package/dist/moodle/content.js +105 -0
- package/dist/moodle/content.js.map +1 -0
- package/dist/moodle/course.d.ts +51 -0
- package/dist/moodle/course.js +45 -0
- package/dist/moodle/course.js.map +1 -0
- package/dist/moodle/courses.d.ts +57 -0
- package/dist/moodle/courses.js +70 -0
- package/dist/moodle/courses.js.map +1 -0
- package/dist/moodle/editor.d.ts +18 -0
- package/dist/moodle/editor.js +50 -0
- package/dist/moodle/editor.js.map +1 -0
- package/dist/moodle/files.d.ts +16 -0
- package/dist/moodle/files.js +57 -0
- package/dist/moodle/files.js.map +1 -0
- package/dist/moodle/forms.d.ts +44 -0
- package/dist/moodle/forms.js +199 -0
- package/dist/moodle/forms.js.map +1 -0
- package/dist/moodle/forum.d.ts +55 -0
- package/dist/moodle/forum.js +115 -0
- package/dist/moodle/forum.js.map +1 -0
- package/dist/moodle/forumPosts.d.ts +83 -0
- package/dist/moodle/forumPosts.js +113 -0
- package/dist/moodle/forumPosts.js.map +1 -0
- package/dist/moodle/gradebook.d.ts +102 -0
- package/dist/moodle/gradebook.js +246 -0
- package/dist/moodle/gradebook.js.map +1 -0
- package/dist/moodle/grades.d.ts +24 -0
- package/dist/moodle/grades.js +67 -0
- package/dist/moodle/grades.js.map +1 -0
- package/dist/moodle/grading.d.ts +92 -0
- package/dist/moodle/grading.js +161 -0
- package/dist/moodle/grading.js.map +1 -0
- package/dist/moodle/h5p.d.ts +31 -0
- package/dist/moodle/h5p.js +100 -0
- package/dist/moodle/h5p.js.map +1 -0
- package/dist/moodle/inPage.d.ts +8 -0
- package/dist/moodle/inPage.js +10 -0
- package/dist/moodle/inPage.js.map +1 -0
- package/dist/moodle/lesson.d.ts +57 -0
- package/dist/moodle/lesson.js +140 -0
- package/dist/moodle/lesson.js.map +1 -0
- package/dist/moodle/login.d.ts +37 -0
- package/dist/moodle/login.js +77 -0
- package/dist/moodle/login.js.map +1 -0
- package/dist/moodle/messages.d.ts +66 -0
- package/dist/moodle/messages.js +80 -0
- package/dist/moodle/messages.js.map +1 -0
- package/dist/moodle/overview.d.ts +83 -0
- package/dist/moodle/overview.js +137 -0
- package/dist/moodle/overview.js.map +1 -0
- package/dist/moodle/pace.d.ts +4 -0
- package/dist/moodle/pace.js +19 -0
- package/dist/moodle/pace.js.map +1 -0
- package/dist/moodle/participants.d.ts +55 -0
- package/dist/moodle/participants.js +131 -0
- package/dist/moodle/participants.js.map +1 -0
- package/dist/moodle/questionBank.d.ts +81 -0
- package/dist/moodle/questionBank.js +235 -0
- package/dist/moodle/questionBank.js.map +1 -0
- package/dist/moodle/quiz.d.ts +117 -0
- package/dist/moodle/quiz.js +291 -0
- package/dist/moodle/quiz.js.map +1 -0
- package/dist/moodle/quizResults.d.ts +36 -0
- package/dist/moodle/quizResults.js +93 -0
- package/dist/moodle/quizResults.js.map +1 -0
- package/dist/moodle/quizStructure.d.ts +42 -0
- package/dist/moodle/quizStructure.js +100 -0
- package/dist/moodle/quizStructure.js.map +1 -0
- package/dist/moodle/session.d.ts +39 -0
- package/dist/moodle/session.js +99 -0
- package/dist/moodle/session.js.map +1 -0
- package/dist/moodle/site.d.ts +8 -0
- package/dist/moodle/site.js +36 -0
- package/dist/moodle/site.js.map +1 -0
- package/dist/moodle/siteInfo.d.ts +25 -0
- package/dist/moodle/siteInfo.js +45 -0
- package/dist/moodle/siteInfo.js.map +1 -0
- package/dist/moodle/structure.d.ts +94 -0
- package/dist/moodle/structure.js +103 -0
- package/dist/moodle/structure.js.map +1 -0
- package/dist/moodle/submission.d.ts +48 -0
- package/dist/moodle/submission.js +109 -0
- package/dist/moodle/submission.js.map +1 -0
- package/dist/moodle/survey.d.ts +47 -0
- package/dist/moodle/survey.js +197 -0
- package/dist/moodle/survey.js.map +1 -0
- package/dist/moodle/text.d.ts +21 -0
- package/dist/moodle/text.js +113 -0
- package/dist/moodle/text.js.map +1 -0
- package/dist/moodle/user.d.ts +54 -0
- package/dist/moodle/user.js +78 -0
- package/dist/moodle/user.js.map +1 -0
- package/dist/options.d.ts +30 -0
- package/dist/options.js +35 -0
- package/dist/options.js.map +1 -0
- package/dist/sessionCookies.d.ts +11 -0
- package/dist/sessionCookies.js +31 -0
- package/dist/sessionCookies.js.map +1 -0
- package/dist/testing/fakeMoodle.d.ts +55 -0
- package/dist/testing/fakeMoodle.js +103 -0
- package/dist/testing/fakeMoodle.js.map +1 -0
- package/dist/testing/gradingPanel.d.ts +6 -0
- package/dist/testing/gradingPanel.js +22 -0
- package/dist/testing/gradingPanel.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +18 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +77 -4
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { callService } from "../../core/ajax.js";
|
|
2
|
+
import { setFields } from "../../core/forms.js";
|
|
3
|
+
import { moodleErrorMessage, openSitePage } from "../../core/session.js";
|
|
4
|
+
import { MoodleError } from "../../core/errors.js";
|
|
5
|
+
/** The gradebook can't be read or changed: not allowed, recalculating, or a value Moodle refuses. */
|
|
6
|
+
export class GradebookError extends MoodleError {
|
|
7
|
+
name = "GradebookError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message, "gradebook");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Moodle's aggregations by their constant (GRADE_AGGREGATE_*). */
|
|
13
|
+
const AGGREGATION_CODES = {
|
|
14
|
+
mean: 0,
|
|
15
|
+
median: 2,
|
|
16
|
+
lowest: 4,
|
|
17
|
+
highest: 6,
|
|
18
|
+
mode: 8,
|
|
19
|
+
"weighted-mean": 10,
|
|
20
|
+
"simple-weighted-mean": 11,
|
|
21
|
+
"mean-with-extra-credits": 12,
|
|
22
|
+
natural: 13,
|
|
23
|
+
};
|
|
24
|
+
export const GRADE_AGGREGATIONS = Object.keys(AGGREGATION_CODES);
|
|
25
|
+
/** One page of the grader report, by data attributes and links, in any language. */
|
|
26
|
+
export function readGraderReport(page) {
|
|
27
|
+
return page.evaluate(() => {
|
|
28
|
+
const doc = globalThis.document;
|
|
29
|
+
const columns = Array.from(doc.querySelectorAll("table#user-grades tr.heading th[data-itemid]")).map((th) => {
|
|
30
|
+
const header = th.querySelector(".gradeitemheader");
|
|
31
|
+
const copy = header?.cloneNode(true);
|
|
32
|
+
for (const extra of Array.from(copy?.querySelectorAll("img, .visually-hidden") ?? []))
|
|
33
|
+
extra.remove();
|
|
34
|
+
const match = /\/mod\/([a-z0-9_]+)\/view\.php\?(?:.*&)?id=(\d+)/.exec(header?.getAttribute("href") ?? "");
|
|
35
|
+
const kind = /\bcourseitem\b/.test(th.className) ? "course" : /\bcategoryitem\b/.test(th.className) ? "category" : "item";
|
|
36
|
+
return {
|
|
37
|
+
id: Number(th.getAttribute("data-itemid")),
|
|
38
|
+
name: (copy?.textContent ?? "").replace(/\s+/g, " ").trim(),
|
|
39
|
+
kind,
|
|
40
|
+
...(match ? { activityId: Number(match[2]), activityType: match[1] } : {}),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
const rows = Array.from(doc.querySelectorAll("table#user-grades tr.userrow[data-uid]")).map((row) => {
|
|
44
|
+
const link = row.querySelector("a.username")?.cloneNode(true);
|
|
45
|
+
for (const extra of Array.from(link?.querySelectorAll(".userinitials, img, .visually-hidden") ?? []))
|
|
46
|
+
extra.remove();
|
|
47
|
+
const grades = Array.from(row.querySelectorAll("td.gradecell[data-itemid]")).map((cell) => {
|
|
48
|
+
const value = (cell.querySelector(".gradevalue")?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
49
|
+
return { itemId: Number(cell.getAttribute("data-itemid")), ...(value && value !== "-" ? { grade: value } : {}) };
|
|
50
|
+
});
|
|
51
|
+
return { userId: Number(row.getAttribute("data-uid")), fullName: (link?.textContent ?? "").replace(/\s+/g, " ").trim(), grades };
|
|
52
|
+
});
|
|
53
|
+
return { columns, rows };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const MAX_PAGES = 100;
|
|
57
|
+
/**
|
|
58
|
+
* Everyone's grades in a course as the grader report shows them, page by page, in one group when
|
|
59
|
+
* given. People come as user id and full name only.
|
|
60
|
+
*/
|
|
61
|
+
export async function graderReport(context, siteUrl, courseId, { group } = {}) {
|
|
62
|
+
const report = { columns: [], rows: [] };
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
for (let p = 0; p < MAX_PAGES; p++) {
|
|
65
|
+
const page = await openSitePage(context, siteUrl, `grade/report/grader/index.php?id=${courseId}&page=${p}${group ? `&group=${group}` : ""}`);
|
|
66
|
+
try {
|
|
67
|
+
const refused = await moodleErrorMessage(page);
|
|
68
|
+
if (refused !== undefined || (await page.locator("body#page-grade-report-grader-index").count()) === 0) {
|
|
69
|
+
throw new GradebookError(`You can't see course ${courseId}'s grader report: ${refused ?? "the course doesn't exist, or you aren't allowed"}`);
|
|
70
|
+
}
|
|
71
|
+
// Moodle shows a progress bar instead while it recalculates grades (deleted activities, changed weights).
|
|
72
|
+
if ((await page.locator("table#user-grades").count()) === 0) {
|
|
73
|
+
throw new GradebookError(`Course ${courseId}'s grader report isn't available now: Moodle may be recalculating its grades. Try again in a moment.`);
|
|
74
|
+
}
|
|
75
|
+
const read = await readGraderReport(page);
|
|
76
|
+
if (p === 0)
|
|
77
|
+
report.columns = read.columns;
|
|
78
|
+
const fresh = read.rows.filter((row) => !seen.has(row.userId));
|
|
79
|
+
if (fresh.length === 0)
|
|
80
|
+
break;
|
|
81
|
+
for (const row of fresh)
|
|
82
|
+
seen.add(row.userId);
|
|
83
|
+
report.rows.push(...fresh);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
await page.close().catch(() => undefined);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return report;
|
|
90
|
+
}
|
|
91
|
+
/** The gradebook setup page's rows, by data attributes and field names. */
|
|
92
|
+
export function readGradebookSetup(page) {
|
|
93
|
+
return page.evaluate((codes) => {
|
|
94
|
+
const doc = globalThis.document;
|
|
95
|
+
const names = Object.fromEntries(Object.entries(codes).map(([name, code]) => [String(code), name]));
|
|
96
|
+
const categories = [];
|
|
97
|
+
const items = [];
|
|
98
|
+
for (const row of Array.from(doc.querySelectorAll("#grade_edit_tree_table tr[data-itemid]"))) {
|
|
99
|
+
const itemId = Number(row.getAttribute("data-itemid"));
|
|
100
|
+
const name = (row.querySelector(".rowtitle")?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
101
|
+
const parent = Number((row.getAttribute("data-parent-category") ?? "").replace(/^cg/, "")) || undefined;
|
|
102
|
+
const max = Number(row.getAttribute("data-grademax"));
|
|
103
|
+
const weightField = row.querySelector(`input[type="text"][name="weight_${itemId}"]`);
|
|
104
|
+
const weight = weightField && weightField.value !== "" ? Number(weightField.value) : undefined;
|
|
105
|
+
const category = row.getAttribute("data-category");
|
|
106
|
+
if (category) {
|
|
107
|
+
const aggregation = names[row.getAttribute("data-aggregation") ?? ""];
|
|
108
|
+
categories.push({
|
|
109
|
+
id: Number(category.replace(/^cg/, "")),
|
|
110
|
+
itemId,
|
|
111
|
+
name,
|
|
112
|
+
...(parent ? { parentId: parent } : {}),
|
|
113
|
+
...(aggregation ? { aggregation: aggregation } : {}),
|
|
114
|
+
...(max > 0 ? { maxGrade: max } : {}),
|
|
115
|
+
...(weight !== undefined ? { weight } : {}),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// The item's icon comes from its module's theme folder, or core's for a manual item.
|
|
120
|
+
const icon = /image\.php\/[^/]+\/([a-z0-9_]+)\//.exec(row.querySelector("img.itemicon")?.getAttribute("src") ?? "")?.[1];
|
|
121
|
+
items.push({
|
|
122
|
+
id: itemId,
|
|
123
|
+
name,
|
|
124
|
+
...(parent ? { categoryId: parent } : {}),
|
|
125
|
+
type: !icon || icon === "core" ? "manual" : icon,
|
|
126
|
+
...(max > 0 ? { maxGrade: max } : {}),
|
|
127
|
+
...(weight !== undefined ? { weight } : {}),
|
|
128
|
+
weightOverridden: row.querySelector(`input[type="checkbox"][name="weightoverride_${itemId}"]`)?.checked ?? false,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { categories, items };
|
|
133
|
+
}, AGGREGATION_CODES);
|
|
134
|
+
}
|
|
135
|
+
/** A course's gradebook setup: its categories (aggregation, weights) and grade items (category, maximum, weight). */
|
|
136
|
+
export async function gradebookSetup(context, siteUrl, courseId) {
|
|
137
|
+
const page = await openSitePage(context, siteUrl, `grade/edit/tree/index.php?id=${courseId}`);
|
|
138
|
+
try {
|
|
139
|
+
if ((await page.locator("body#page-grade-edit-tree-index #grade_edit_tree_table").count()) === 0) {
|
|
140
|
+
throw new GradebookError(`You can't see course ${courseId}'s gradebook setup: ${(await moodleErrorMessage(page)) ?? "the course doesn't exist, or you aren't allowed to manage its grades"}`);
|
|
141
|
+
}
|
|
142
|
+
const setup = await readGradebookSetup(page);
|
|
143
|
+
// Category and course totals are their categories' own items, not items of their own.
|
|
144
|
+
const totals = new Set(setup.categories.map((c) => c.itemId));
|
|
145
|
+
return { categories: setup.categories, items: setup.items.filter((item) => !totals.has(item.id)) };
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await page.close().catch(() => undefined);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Fills one of Moodle's dynamic forms (core_form_dynamic_form) as its modal does: rendered by the
|
|
153
|
+
* service, filled in a blank tab, serialized and sent back. Throws `GradebookError` with Moodle's
|
|
154
|
+
* messages when it comes back unsubmitted; `prepare` can check the form before it's filled.
|
|
155
|
+
*/
|
|
156
|
+
async function submitDynamicForm(context, siteUrl, form, params, values, prepare) {
|
|
157
|
+
const formdata = new URLSearchParams(Object.entries(params).map(([key, value]) => [key, String(value)])).toString();
|
|
158
|
+
const rendered = await callService(context, siteUrl, "core_form_dynamic_form", { form, formdata });
|
|
159
|
+
const page = await context.newPage();
|
|
160
|
+
try {
|
|
161
|
+
await page.setContent(rendered.html);
|
|
162
|
+
await prepare?.(page);
|
|
163
|
+
await setFields(page, values);
|
|
164
|
+
const serialized = await page.evaluate(() => {
|
|
165
|
+
const g = globalThis;
|
|
166
|
+
return new g.URLSearchParams(new g.FormData(g.document.querySelector("form.mform"))).toString();
|
|
167
|
+
});
|
|
168
|
+
const answer = await callService(context, siteUrl, "core_form_dynamic_form", { form, formdata: serialized });
|
|
169
|
+
if (answer.submitted)
|
|
170
|
+
return;
|
|
171
|
+
// Back unsubmitted: the form again, with its errors (ids carry a random suffix).
|
|
172
|
+
await page.setContent(answer.html);
|
|
173
|
+
const errors = await page.evaluate(() => Array.from(globalThis.document.querySelectorAll('[id^="id_error_"]'))
|
|
174
|
+
.map((element) => ({ field: element.id.slice("id_error_".length).replace(/_[A-Za-z0-9]{15}$/, ""), message: (element.textContent ?? "").replace(/\s+/g, " ").trim() }))
|
|
175
|
+
.filter((error) => error.message !== ""));
|
|
176
|
+
throw new GradebookError(`Moodle refused the form: ${errors.map((e) => `${e.field}: ${e.message}`).join("; ") || "no reason given"}.`);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
await page.close().catch(() => undefined);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Moves a grade item (`ig<id>`) or category (`cg<id>`) to the start of a category, as the setup page's "Move" does. */
|
|
183
|
+
async function moveIntoCategory(context, siteUrl, courseId, element, categoryId) {
|
|
184
|
+
const page = await openSitePage(context, siteUrl, `grade/edit/tree/index.php?id=${courseId}`);
|
|
185
|
+
try {
|
|
186
|
+
const sesskey = await page.evaluate(() => globalThis.M.cfg.sesskey);
|
|
187
|
+
const move = `grade/edit/tree/index.php?id=${courseId}&action=move&eid=${element}&moveafter=cg${categoryId}&first=1&sesskey=${sesskey}`;
|
|
188
|
+
await page.goto(new URL(move, siteUrl).href, { waitUntil: "domcontentloaded" });
|
|
189
|
+
if ((await page.locator("#grade_edit_tree_table").count()) === 0) {
|
|
190
|
+
throw new GradebookError(`Moodle didn't move ${element.startsWith("cg") ? "category" : "item"} ${element.slice(2)} into category ${categoryId}: check both ids in the gradebook setup.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
await page.close().catch(() => undefined);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Changes a grade item: moves it into a category, sets its weight (percent, overriding Natural's
|
|
199
|
+
* automatic one), or its maximum grade (a manual item's; an activity's is set in the activity).
|
|
200
|
+
* Returns the item afterwards.
|
|
201
|
+
*/
|
|
202
|
+
export async function updateGradeItem(context, siteUrl, courseId, itemId, { category, weight, maxGrade }) {
|
|
203
|
+
if (category !== undefined)
|
|
204
|
+
await moveIntoCategory(context, siteUrl, courseId, `ig${itemId}`, category);
|
|
205
|
+
if (weight !== undefined || maxGrade !== undefined) {
|
|
206
|
+
const values = {};
|
|
207
|
+
if (weight !== undefined)
|
|
208
|
+
Object.assign(values, { weightoverride: 1, aggregationcoef2: weight });
|
|
209
|
+
if (maxGrade !== undefined)
|
|
210
|
+
values.grademax = maxGrade;
|
|
211
|
+
await submitDynamicForm(context, siteUrl, "core_grades\\form\\add_item", { courseid: courseId, itemid: itemId }, values, async (page) => {
|
|
212
|
+
const fields = await page.evaluate(() => {
|
|
213
|
+
const doc = globalThis.document;
|
|
214
|
+
const max = doc.querySelector('[name="grademax"]');
|
|
215
|
+
return { maxEditable: max !== null && !max.disabled, weight: doc.querySelector('[name="aggregationcoef2"]') !== null };
|
|
216
|
+
});
|
|
217
|
+
if (maxGrade !== undefined && !fields.maxEditable) {
|
|
218
|
+
throw new GradebookError(`Item ${itemId} belongs to an activity: its maximum grade is set in the activity's settings, not in the gradebook.`);
|
|
219
|
+
}
|
|
220
|
+
if (weight !== undefined && !fields.weight)
|
|
221
|
+
throw new GradebookError(`Item ${itemId}'s category doesn't aggregate with weights that can be set (Natural): change its aggregation first.`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const done = [category !== undefined && `moved into category ${category}`, weight !== undefined && `weighted ${weight}%`, maxGrade !== undefined && `out of ${maxGrade}`].filter(Boolean);
|
|
225
|
+
const item = (await gradebookSetup(context, siteUrl, courseId)).items.find((i) => i.id === itemId);
|
|
226
|
+
return { changed: done.length > 0 ? `Grade item ${itemId} ${done.join(", ")}.` : "Nothing to change.", item };
|
|
227
|
+
}
|
|
228
|
+
/** Adds a grade category (at the course's top level, or inside `parentId`); returns it as the setup shows it. */
|
|
229
|
+
export async function addGradeCategory(context, siteUrl, courseId, { name, aggregation, parentId }) {
|
|
230
|
+
const before = new Set((await gradebookSetup(context, siteUrl, courseId)).categories.map((c) => c.id));
|
|
231
|
+
const values = { fullname: name };
|
|
232
|
+
if (aggregation !== undefined)
|
|
233
|
+
values.aggregation = AGGREGATION_CODES[aggregation];
|
|
234
|
+
await submitDynamicForm(context, siteUrl, "core_grades\\form\\add_category", { courseid: courseId }, values, async (page) => {
|
|
235
|
+
if (aggregation === undefined)
|
|
236
|
+
return;
|
|
237
|
+
const offered = await page.evaluate(() => Array.from(globalThis.document.querySelectorAll('[name="aggregation"] option')).map((o) => o.value));
|
|
238
|
+
if (!offered.includes(String(AGGREGATION_CODES[aggregation]))) {
|
|
239
|
+
const available = GRADE_AGGREGATIONS.filter((a) => offered.includes(String(AGGREGATION_CODES[a])));
|
|
240
|
+
throw new GradebookError(`The site doesn't offer the ${aggregation} aggregation; it offers: ${available.join(", ")}.`);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
let created = (await gradebookSetup(context, siteUrl, courseId)).categories.find((c) => !before.has(c.id));
|
|
244
|
+
if (created && parentId !== undefined) {
|
|
245
|
+
await moveIntoCategory(context, siteUrl, courseId, `cg${created.id}`, parentId);
|
|
246
|
+
created = (await gradebookSetup(context, siteUrl, courseId)).categories.find((c) => c.id === created.id);
|
|
247
|
+
}
|
|
248
|
+
return { changed: `Grade category ${created?.id ?? "?"} "${name}" added${parentId !== undefined ? ` in category ${parentId}` : ""}.`, category: created };
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=gradebook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradebook.js","sourceRoot":"","sources":["../../../src/areas/grades/gradebook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import { MoodleError } from "../../core/errors.js";
|
|
3
|
+
/** A row of the user grade report, values as Moodle shows them ("-" left out). */
|
|
4
|
+
export interface GradeRow {
|
|
5
|
+
/** A category's total row is "categoryTotal"; the course's, "courseTotal". */
|
|
6
|
+
kind: "item" | "category" | "categoryTotal" | "courseTotal";
|
|
7
|
+
name: string;
|
|
8
|
+
activityId?: number;
|
|
9
|
+
activityType?: string;
|
|
10
|
+
grade?: string;
|
|
11
|
+
range?: string;
|
|
12
|
+
percentage?: string;
|
|
13
|
+
weight?: string;
|
|
14
|
+
letter?: string;
|
|
15
|
+
feedback?: string;
|
|
16
|
+
contribution?: string;
|
|
17
|
+
}
|
|
18
|
+
/** The person can't see the grade report of that course. */
|
|
19
|
+
export declare class NoGradeReportError extends MoodleError {
|
|
20
|
+
name: string;
|
|
21
|
+
constructor(message: string);
|
|
22
|
+
}
|
|
23
|
+
/** The rows of `table.user-grade`, by cell classes, never by header texts (ADR-004). */
|
|
24
|
+
export declare function readGradeRows(page: Page): Promise<GradeRow[]>;
|
|
25
|
+
/** The person's own grade report for a course. */
|
|
26
|
+
export declare function myGrades(context: BrowserContext, siteUrl: string, courseId: number): Promise<GradeRow[]>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { openSitePage } from "../../core/session.js";
|
|
2
|
+
import { MoodleError } from "../../core/errors.js";
|
|
3
|
+
/** The person can't see the grade report of that course. */
|
|
4
|
+
export class NoGradeReportError extends MoodleError {
|
|
5
|
+
name = "NoGradeReportError";
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message, "no-grade-report");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** The rows of `table.user-grade`, by cell classes, never by header texts (ADR-004). */
|
|
11
|
+
export function readGradeRows(page) {
|
|
12
|
+
return page.evaluate(() => {
|
|
13
|
+
const doc = globalThis.document;
|
|
14
|
+
const rows = Array.from(doc.querySelectorAll("table.user-grade tr"));
|
|
15
|
+
const result = [];
|
|
16
|
+
for (const row of rows) {
|
|
17
|
+
const nameCell = row.querySelector(".column-itemname");
|
|
18
|
+
if (!nameCell || row.querySelector("th.column-itemname[scope='col'], thead"))
|
|
19
|
+
continue;
|
|
20
|
+
const classes = nameCell.className;
|
|
21
|
+
// Both totals are "baggt" rows; a category's holds a categoryitem block (confirmed empirically, Moodle 5.2).
|
|
22
|
+
const total = nameCell.querySelector(".categoryitem") ? "categoryTotal" : "courseTotal";
|
|
23
|
+
const kind = /\bbaggt\b/.test(classes) ? total : /\bcategory\b/.test(classes) ? "category" : /\bitem\b/.test(classes) ? "item" : undefined;
|
|
24
|
+
if (!kind)
|
|
25
|
+
continue;
|
|
26
|
+
const values = {};
|
|
27
|
+
for (const column of ["grade", "range", "percentage", "weight", "lettergrade", "feedback", "contributiontocoursetotal"]) {
|
|
28
|
+
const cell = row.querySelector(`.column-${column}`);
|
|
29
|
+
if (!cell)
|
|
30
|
+
continue;
|
|
31
|
+
// The grade cell also holds an action menu whose text is in the site's language.
|
|
32
|
+
const copy = cell.cloneNode(true);
|
|
33
|
+
for (const menu of Array.from(copy.querySelectorAll(".action-menu, .dropdown, .visually-hidden")))
|
|
34
|
+
menu.remove();
|
|
35
|
+
const text = (copy.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
36
|
+
values[column] = text === "" || text === "-" ? undefined : text;
|
|
37
|
+
}
|
|
38
|
+
const link = nameCell.querySelector('a[href*="/mod/"][href*="view.php"]');
|
|
39
|
+
const href = link?.getAttribute("href") ?? "";
|
|
40
|
+
const match = /\/mod\/([a-z0-9_]+)\/view\.php\?(?:.*&)?id=(\d+)/.exec(href);
|
|
41
|
+
result.push({
|
|
42
|
+
kind,
|
|
43
|
+
name: ((nameCell.querySelector(".rowtitle") ?? nameCell).textContent ?? "").replace(/\s+/g, " ").trim(),
|
|
44
|
+
activityId: match ? Number(match[2]) : undefined,
|
|
45
|
+
activityType: match ? match[1] : undefined,
|
|
46
|
+
grade: values.grade,
|
|
47
|
+
range: values.range,
|
|
48
|
+
percentage: values.percentage,
|
|
49
|
+
weight: values.weight,
|
|
50
|
+
letter: values.lettergrade,
|
|
51
|
+
feedback: values.feedback,
|
|
52
|
+
contribution: values.contributiontocoursetotal,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** The person's own grade report for a course. */
|
|
59
|
+
export async function myGrades(context, siteUrl, courseId) {
|
|
60
|
+
const page = await openSitePage(context, siteUrl, `grade/report/user/index.php?id=${courseId}`);
|
|
61
|
+
try {
|
|
62
|
+
if ((await page.locator("body#page-grade-report-user-index table.user-grade").count()) === 0) {
|
|
63
|
+
throw new NoGradeReportError(`There's no grade report of yours to read in course ${courseId}: the course doesn't exist, you aren't graded there, or grades are hidden.`);
|
|
64
|
+
}
|
|
65
|
+
return await readGradeRows(page);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await page.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=user-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-report.js","sourceRoot":"","sources":["../../../src/areas/grades/user-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import { MoodleError } from "../../core/errors.js";
|
|
3
|
+
/** The activity isn't an H5P activity the person can open. */
|
|
4
|
+
export declare class NotAnH5PActivityError extends MoodleError {
|
|
5
|
+
name: string;
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export interface H5PAttempt {
|
|
9
|
+
/** Moodle's id for the attempt (its report's), and its number for the person. */
|
|
10
|
+
id?: number;
|
|
11
|
+
number: number;
|
|
12
|
+
score?: number;
|
|
13
|
+
maxScore?: number;
|
|
14
|
+
completed?: boolean;
|
|
15
|
+
passed?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface H5PActivity {
|
|
18
|
+
id: number;
|
|
19
|
+
name: string;
|
|
20
|
+
/** The H5P library ("H5P.MultiChoice") and its version, from the content's player. */
|
|
21
|
+
contentType?: {
|
|
22
|
+
library: string;
|
|
23
|
+
version: string;
|
|
24
|
+
};
|
|
25
|
+
/** Whether Moodle records attempts and scores (the activity's tracking setting). */
|
|
26
|
+
tracked: boolean;
|
|
27
|
+
/** The person's own attempts, oldest first: a teacher's report lists participants instead, so none. */
|
|
28
|
+
attempts: H5PAttempt[];
|
|
29
|
+
}
|
|
30
|
+
/** The rows of the person's "all attempts" table: positions fixed by Moodle's template, icons by class. */
|
|
31
|
+
export declare function readH5PAttempts(page: Page): Promise<H5PAttempt[]>;
|
|
32
|
+
/** An H5P activity: its content type, whether attempts are tracked, and the person's attempts with their scores. */
|
|
33
|
+
export declare function h5pActivity(context: BrowserContext, siteUrl: string, activityId: number): Promise<H5PActivity>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { inPage } from "../../core/inPage.js";
|
|
2
|
+
import { openSitePage } from "../../core/session.js";
|
|
3
|
+
import { decodeEntities } from "../../core/text.js";
|
|
4
|
+
import { MoodleError } from "../../core/errors.js";
|
|
5
|
+
/** The activity isn't an H5P activity the person can open. */
|
|
6
|
+
export class NotAnH5PActivityError extends MoodleError {
|
|
7
|
+
name = "NotAnH5PActivityError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message, "not-an-h5p-activity");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** The rows of the person's "all attempts" table: positions fixed by Moodle's template, icons by class. */
|
|
13
|
+
export function readH5PAttempts(page) {
|
|
14
|
+
// Through inPage: it has a helper function (see the conventions on page.evaluate).
|
|
15
|
+
return inPage(page, () => {
|
|
16
|
+
const doc = globalThis.document;
|
|
17
|
+
// The page shows the scored attempt's table first and every attempt's last.
|
|
18
|
+
const tables = Array.from(doc.querySelectorAll("#region-main table")).filter((table) => table.querySelector('a[href*="attemptid="]'));
|
|
19
|
+
const all = tables[tables.length - 1];
|
|
20
|
+
if (!all)
|
|
21
|
+
return [];
|
|
22
|
+
// An attempt row starts with its number; a teacher's participants table has names there.
|
|
23
|
+
const rows = Array.from(all.querySelectorAll("tbody tr")).filter((row) => /^\d+$/.test((row.querySelector("th")?.textContent ?? "").trim()) && row.querySelectorAll("td").length >= 6);
|
|
24
|
+
return rows.map((row) => {
|
|
25
|
+
const cells = Array.from(row.querySelectorAll("td"));
|
|
26
|
+
// The score cell starts with the raw score; a hidden " / max" follows for small screens.
|
|
27
|
+
const raw = Array.from(cells[1]?.childNodes ?? [])
|
|
28
|
+
.filter((node) => node.nodeType === 3)
|
|
29
|
+
.map((node) => node.textContent ?? "")
|
|
30
|
+
.join("")
|
|
31
|
+
.trim();
|
|
32
|
+
const max = (cells[2]?.textContent ?? "").trim();
|
|
33
|
+
const completion = cells[4]?.querySelector("img, i");
|
|
34
|
+
const success = cells[5]?.querySelector("i, img");
|
|
35
|
+
const mark = (icon) => {
|
|
36
|
+
if (!icon)
|
|
37
|
+
return undefined;
|
|
38
|
+
const look = `${icon.className} ${icon.getAttribute("src") ?? ""}`;
|
|
39
|
+
return /check|-y\b|completion-[a-z]+-y/.test(look) ? true : /circle|-n\b/.test(look) ? false : undefined;
|
|
40
|
+
};
|
|
41
|
+
const link = row.querySelector('a[href*="attemptid="]')?.getAttribute("href") ?? "";
|
|
42
|
+
return {
|
|
43
|
+
...(/attemptid=(\d+)/.test(link) ? { id: Number(/attemptid=(\d+)/.exec(link)[1]) } : {}),
|
|
44
|
+
number: Number((row.querySelector("th")?.textContent ?? "").trim()),
|
|
45
|
+
...(raw !== "" && !Number.isNaN(Number(raw)) ? { score: Number(raw) } : {}),
|
|
46
|
+
...(max !== "" && !Number.isNaN(Number(max)) ? { maxScore: Number(max) } : {}),
|
|
47
|
+
...(mark(completion) !== undefined ? { completed: mark(completion) } : {}),
|
|
48
|
+
...(mark(success) !== undefined ? { passed: mark(success) } : {}),
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}, null);
|
|
52
|
+
}
|
|
53
|
+
/** An H5P activity: its content type, whether attempts are tracked, and the person's attempts with their scores. */
|
|
54
|
+
export async function h5pActivity(context, siteUrl, activityId) {
|
|
55
|
+
const page = await openSitePage(context, siteUrl, `mod/h5pactivity/view.php?id=${activityId}`);
|
|
56
|
+
let facts;
|
|
57
|
+
try {
|
|
58
|
+
if ((await page.locator("body#page-mod-h5pactivity-view").count()) === 0) {
|
|
59
|
+
throw new NotAnH5PActivityError(`Activity ${activityId} isn't an H5P activity you can open. Take the id of an H5P activity (module "h5pactivity") from the course structure.`);
|
|
60
|
+
}
|
|
61
|
+
facts = await page.evaluate(() => {
|
|
62
|
+
const doc = globalThis.document;
|
|
63
|
+
return {
|
|
64
|
+
name: doc.querySelector("[data-activityname]")?.getAttribute("data-activityname") ?? "",
|
|
65
|
+
player: doc.querySelector("iframe.h5p-player")?.getAttribute("src") ?? undefined,
|
|
66
|
+
// The attempts report is linked only where Moodle tracks attempts.
|
|
67
|
+
tracked: doc.querySelector('a[href*="/mod/h5pactivity/report.php"]') !== null,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
await page.close().catch(() => undefined);
|
|
73
|
+
}
|
|
74
|
+
const activity = { id: activityId, name: decodeEntities(facts.name), tracked: facts.tracked, attempts: [] };
|
|
75
|
+
if (facts.player) {
|
|
76
|
+
// The player page names the content's main library in its H5P settings, before any script runs.
|
|
77
|
+
const player = await context.newPage();
|
|
78
|
+
let html = "";
|
|
79
|
+
try {
|
|
80
|
+
await player.goto(new URL(facts.player, siteUrl).href, { waitUntil: "domcontentloaded" });
|
|
81
|
+
html = await player.content();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// No player to read: the content type stays unknown.
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await player.close().catch(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
const library = /"library":"([^"\s]+) (\d+\.\d+)"/.exec(html);
|
|
90
|
+
if (library)
|
|
91
|
+
activity.contentType = { library: library[1], version: library[2] };
|
|
92
|
+
}
|
|
93
|
+
if (facts.tracked) {
|
|
94
|
+
const report = await openSitePage(context, siteUrl, `mod/h5pactivity/report.php?id=${activityId}`);
|
|
95
|
+
try {
|
|
96
|
+
activity.attempts = (await readH5PAttempts(report)).sort((a, b) => a.number - b.number);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await report.close().catch(() => undefined);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return activity;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=h5p.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h5p.js","sourceRoot":"","sources":["../../../src/areas/h5p/h5p.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import type { Trail } from "../../options.js";
|
|
3
|
+
import { MoodleError } from "../../core/errors.js";
|
|
4
|
+
/** The lesson isn't there, or the page can't take that answer. */
|
|
5
|
+
export declare class LessonError extends MoodleError {
|
|
6
|
+
name: string;
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
/** One input of a question page, by its form name. */
|
|
10
|
+
export interface LessonField {
|
|
11
|
+
name: string;
|
|
12
|
+
kind: "choice" | "checkbox" | "text" | "select";
|
|
13
|
+
label?: string;
|
|
14
|
+
options?: {
|
|
15
|
+
value: string;
|
|
16
|
+
label: string;
|
|
17
|
+
}[];
|
|
18
|
+
}
|
|
19
|
+
export interface LessonPage {
|
|
20
|
+
/** What the page is: content with buttons, a question, the feedback after an answer, or the lesson's end. */
|
|
21
|
+
kind: "content" | "question" | "feedback" | "end";
|
|
22
|
+
/** The page's id; for feedback, the page it goes on to (-9 is the end). */
|
|
23
|
+
pageId?: number;
|
|
24
|
+
title?: string;
|
|
25
|
+
/** The page's text as shown: its content, the question, Moodle's messages, the score at the end. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** A content page's buttons, by label, and the page each jumps to (Moodle's jump codes: -1 next, -9 end…). */
|
|
28
|
+
buttons?: {
|
|
29
|
+
label: string;
|
|
30
|
+
jumpTo: number;
|
|
31
|
+
}[];
|
|
32
|
+
/** A question page's inputs. */
|
|
33
|
+
fields?: LessonField[];
|
|
34
|
+
/** After an answer: whether Moodle took it as right, and the teacher's response to it. */
|
|
35
|
+
feedback?: {
|
|
36
|
+
correct?: boolean;
|
|
37
|
+
response?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** The lesson page on screen, read by its forms and blocks. */
|
|
41
|
+
export declare function readLessonPage(page: Page): Promise<LessonPage>;
|
|
42
|
+
/** The lesson page the person is on (starting the lesson the first time). */
|
|
43
|
+
export declare function lessonPage(context: BrowserContext, siteUrl: string, activityId: number): Promise<LessonPage>;
|
|
44
|
+
/**
|
|
45
|
+
* Answers the page the person is on: a content page's button (its label or position), or a
|
|
46
|
+
* question's fields by name. Returns Moodle's feedback when it gives one, and the page that follows,
|
|
47
|
+
* going on past the feedback as its "Continue" does; reaching the end ends the attempt. With the
|
|
48
|
+
* realistic trail it first takes the time a reader needs for the page (ADR-005).
|
|
49
|
+
*/
|
|
50
|
+
export declare function answerLessonPage(context: BrowserContext, siteUrl: string, activityId: number, answer: {
|
|
51
|
+
button?: string | number;
|
|
52
|
+
responses?: Record<string, string>;
|
|
53
|
+
}, { trail, onProgress }?: {
|
|
54
|
+
trail?: Trail;
|
|
55
|
+
onProgress?: (elapsed: number, total: number) => void;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
changed: string;
|
|
58
|
+
feedback?: LessonPage;
|
|
59
|
+
page: LessonPage;
|
|
60
|
+
}>;
|