@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,113 @@
|
|
|
1
|
+
import { pause, readingSeconds } from "../../core/pace.js";
|
|
2
|
+
import { openSitePage } from "../../core/session.js";
|
|
3
|
+
import { readMedia, readTexts } from "../../core/text.js";
|
|
4
|
+
import { MoodleError } from "../../core/errors.js";
|
|
5
|
+
export const CONTENT_TYPES = ["page", "book", "label", "url", "resource", "folder"];
|
|
6
|
+
// A book has a reasonable number of chapters; the bound stops a loop on a broken "next" link.
|
|
7
|
+
const MAX_CHAPTERS = 200;
|
|
8
|
+
/** The activity isn't of the given type, or the person can't view it. */
|
|
9
|
+
export class NotViewableError extends MoodleError {
|
|
10
|
+
name = "NotViewableError";
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message, "not-viewable");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function links(page, selector) {
|
|
16
|
+
return page.evaluate((selector) => {
|
|
17
|
+
const anchors = globalThis.document.querySelectorAll(selector);
|
|
18
|
+
return Array.from(anchors).map((a) => ({ name: (a.textContent ?? "").replace(/\s+/g, " ").trim(), url: a.href }));
|
|
19
|
+
}, selector);
|
|
20
|
+
}
|
|
21
|
+
/** A book chapter's title and text, the title taken off the text's start. */
|
|
22
|
+
function chapter(title, body) {
|
|
23
|
+
return { title, text: body.startsWith(title) ? body.slice(title.length).trim() : body };
|
|
24
|
+
}
|
|
25
|
+
export async function activityContent(context, siteUrl, activityOrId, typeOrOptions, options) {
|
|
26
|
+
if (typeof activityOrId === "number")
|
|
27
|
+
return readContent(context, siteUrl, activityOrId, typeOrOptions, options);
|
|
28
|
+
const { id, type } = activityOrId;
|
|
29
|
+
if (!CONTENT_TYPES.includes(type)) {
|
|
30
|
+
throw new NotViewableError(`Activity ${id} is a ${type}: activityContent reads ${CONTENT_TYPES.join(", ")}.`);
|
|
31
|
+
}
|
|
32
|
+
return readContent(context, siteUrl, id, type, typeOrOptions);
|
|
33
|
+
}
|
|
34
|
+
async function readContent(context, siteUrl, activityId, type, { trail = "realistic", onProgress } = {}) {
|
|
35
|
+
const realistic = trail === "realistic";
|
|
36
|
+
const output = { activity: { id: activityId, type }, trail };
|
|
37
|
+
let secondsRead = 0;
|
|
38
|
+
const readAsAPerson = async (text) => {
|
|
39
|
+
if (!realistic)
|
|
40
|
+
return;
|
|
41
|
+
const seconds = readingSeconds(text);
|
|
42
|
+
await pause(seconds, (elapsed) => onProgress?.(secondsRead + elapsed));
|
|
43
|
+
secondsRead += seconds;
|
|
44
|
+
};
|
|
45
|
+
// Opens the activity's page and checks it shows what this type should.
|
|
46
|
+
const view = async (path, ready) => {
|
|
47
|
+
const page = await openSitePage(context, siteUrl, path);
|
|
48
|
+
if ((await page.locator(ready).count()) === 0) {
|
|
49
|
+
await page.close().catch(() => undefined);
|
|
50
|
+
throw new NotViewableError(`Activity ${activityId} isn't a ${type} you can view. Take its id and module from the course structure.`);
|
|
51
|
+
}
|
|
52
|
+
return page;
|
|
53
|
+
};
|
|
54
|
+
if (type === "page" || type === "label") {
|
|
55
|
+
const path = type === "page" ? `mod/page/view.php?id=${activityId}` : `mod/label/view.php?id=${activityId}`;
|
|
56
|
+
const selector = type === "page" ? "#region-main .generalbox" : `[data-for="cmitem"][data-id="${activityId}"] .activity-altcontent`;
|
|
57
|
+
const page = await view(path, selector);
|
|
58
|
+
output.text = (await readTexts(page, selector))[0] ?? "";
|
|
59
|
+
output.media = await readMedia(page, selector);
|
|
60
|
+
await page.close();
|
|
61
|
+
await readAsAPerson(output.text);
|
|
62
|
+
}
|
|
63
|
+
else if (type === "book") {
|
|
64
|
+
output.chapters = [];
|
|
65
|
+
const media = [];
|
|
66
|
+
if (realistic) {
|
|
67
|
+
// Chapter by chapter, following "next", as a reader does.
|
|
68
|
+
const page = await view(`mod/book/view.php?id=${activityId}`, "#region-main .book_content");
|
|
69
|
+
try {
|
|
70
|
+
for (let i = 0; i < MAX_CHAPTERS; i++) {
|
|
71
|
+
const title = (await readTexts(page, "#region-main .book_content h3, #region-main .book_content h2"))[0] ?? "";
|
|
72
|
+
const read = chapter(title, (await readTexts(page, "#region-main .book_content"))[0] ?? "");
|
|
73
|
+
output.chapters.push(read);
|
|
74
|
+
media.push(...(await readMedia(page, "#region-main .book_content")));
|
|
75
|
+
await readAsAPerson(read.text);
|
|
76
|
+
const next = page.locator("#region-main a.btn-next").first();
|
|
77
|
+
if ((await next.count()) === 0)
|
|
78
|
+
break;
|
|
79
|
+
await page.goto((await next.getAttribute("href")), { waitUntil: "domcontentloaded" });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await page.close().catch(() => undefined);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const page = await view(`mod/book/tool/print/index.php?id=${activityId}`, ".book_chapter");
|
|
88
|
+
const titles = await readTexts(page, ".book_chapter h2, .book_chapter h3");
|
|
89
|
+
const bodies = await readTexts(page, ".book_chapter");
|
|
90
|
+
output.chapters = bodies.map((body, i) => chapter(titles[i] ?? "", body));
|
|
91
|
+
media.push(...(await readMedia(page, ".book_chapter")));
|
|
92
|
+
await page.close();
|
|
93
|
+
}
|
|
94
|
+
// The same video can appear in several chapters: once each.
|
|
95
|
+
output.media = [...new Map(media.map((item) => [item.url, item])).values()];
|
|
96
|
+
}
|
|
97
|
+
else if (type === "url") {
|
|
98
|
+
// forceview logs the view and shows the link instead of leaving the site.
|
|
99
|
+
const page = await view(`mod/url/view.php?id=${activityId}&forceview=1`, ".urlworkaround a");
|
|
100
|
+
output.url = (await links(page, ".urlworkaround a"))[0]?.url;
|
|
101
|
+
await page.close();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const [path, selector] = type === "resource" ? [`mod/resource/view.php?id=${activityId}&forceview=1`, ".resourceworkaround a"] : [`mod/folder/view.php?id=${activityId}`, '.foldertree a[href*="pluginfile.php"]'];
|
|
105
|
+
const page = await view(path, selector);
|
|
106
|
+
output.files = await links(page, selector);
|
|
107
|
+
await page.close();
|
|
108
|
+
}
|
|
109
|
+
if (secondsRead > 0)
|
|
110
|
+
output.readingSeconds = secondsRead;
|
|
111
|
+
return output;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../../src/areas/activities/content.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAkB,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAU,CAAC;AAG7F,8FAA8F;AAC9F,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB,yEAAyE;AACzE,MAAM,OAAO,gBAAiB,SAAQ,WAAW;IACtC,IAAI,GAAG,kBAAkB,CAAC;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IACjC,CAAC;CACF;AAmBD,KAAK,UAAU,KAAK,CAAC,IAAU,EAAE,QAAgB;IAC/C,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChC,MAAM,OAAO,GAAI,UAAgI,CAAC,QAAQ,CAAC,gBAAgB,CACzK,QAAQ,CACT,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACpH,CAAC,EAAE,QAAQ,CAAC,CAAC;AACf,CAAC;AAED,6EAA6E;AAC7E,SAAS,OAAO,CAAC,KAAa,EAAE,IAAY;IAC1C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1F,CAAC;AAUD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAuB,EACvB,OAAe,EACf,YAAmD,EACnD,aAA4C,EAC5C,OAAwB;IAExB,IAAI,OAAO,YAAY,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAA4B,EAAE,OAAO,CAAC,CAAC;IAChI,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC;IAClC,IAAI,CAAE,aAAmC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,gBAAgB,CAAC,YAAY,EAAE,SAAS,IAAI,2BAA2B,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChH,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAmB,EAAE,aAA2C,CAAC,CAAC;AAC7G,CAAC;AASD,KAAK,UAAU,WAAW,CACxB,OAAuB,EACvB,OAAe,EACf,UAAkB,EAClB,IAAiB,EACjB,EAAE,KAAK,GAAG,WAAW,EAAE,UAAU,KAAqB,EAAE;IAExD,MAAM,SAAS,GAAG,KAAK,KAAK,WAAW,CAAC;IACxC,MAAM,MAAM,GAAoB,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC;IAC9E,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,aAAa,GAAG,KAAK,EAAE,IAAY,EAAE,EAAE;QAC3C,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,EAAE,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;QACvE,WAAW,IAAI,OAAO,CAAC;IACzB,CAAC,CAAC;IAEF,uEAAuE;IACvE,MAAM,IAAI,GAAG,KAAK,EAAE,IAAY,EAAE,KAAa,EAAiB,EAAE;QAChE,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACxD,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1C,MAAM,IAAI,gBAAgB,CAAC,YAAY,UAAU,YAAY,IAAI,kEAAkE,CAAC,CAAC;QACvI,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,wBAAwB,UAAU,EAAE,CAAC,CAAC,CAAC,yBAAyB,UAAU,EAAE,CAAC;QAC5G,MAAM,QAAQ,GAAG,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,gCAAgC,UAAU,yBAAyB,CAAC;QACpI,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,CAAC,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC/C,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;SAAM,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,MAAM,CAAC,QAAQ,GAAG,EAAE,CAAC;QACrB,MAAM,KAAK,GAAgB,EAAE,CAAC;QAC9B,IAAI,SAAS,EAAE,CAAC;YACd,0DAA0D;YAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,wBAAwB,UAAU,EAAE,EAAE,4BAA4B,CAAC,CAAC;YAC5F,IAAI,CAAC;gBACH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;oBACtC,MAAM,KAAK,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,8DAA8D,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC/G,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,4BAA4B,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;oBAC5F,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC3B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,4BAA4B,CAAC,CAAC,CAAC,CAAC;oBACrE,MAAM,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,KAAK,EAAE,CAAC;oBAC7D,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC;wBAAE,MAAM;oBACtC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAE,EAAE,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACzF,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,oCAAoC,UAAU,EAAE,EAAE,eAAe,CAAC,CAAC;YAC3F,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,oCAAoC,CAAC,CAAC;YAC3E,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;YACtD,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;YAC1E,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;YACxD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QACD,4DAA4D;QAC5D,MAAM,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,CAAC;SAAM,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QAC1B,0EAA0E;QAC1E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,uBAAuB,UAAU,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAC7F,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;QAC7D,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GACpB,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,4BAA4B,UAAU,cAAc,EAAE,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,UAAU,EAAE,EAAE,uCAAuC,CAAC,CAAC;QAC5L,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI,WAAW,GAAG,CAAC;QAAE,MAAM,CAAC,cAAc,GAAG,WAAW,CAAC;IACzD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import { type FieldValue } from "../../core/forms.js";
|
|
3
|
+
import { MoodleError } from "../../core/errors.js";
|
|
4
|
+
/** An assignment's ids beyond its course module id, which only the grader page shows. */
|
|
5
|
+
export interface AssignmentIds {
|
|
6
|
+
instanceId: number;
|
|
7
|
+
contextId: number;
|
|
8
|
+
}
|
|
9
|
+
/** The id isn't an assignment's course module, or the person can't grade it. */
|
|
10
|
+
export declare class NotAnAssignmentError extends MoodleError {
|
|
11
|
+
name: string;
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
/** Reads `data-assignmentid` and `data-contextid` from a page (the grader's), or `undefined`. */
|
|
15
|
+
export declare function readAssignmentIds(page: Page): Promise<AssignmentIds | undefined>;
|
|
16
|
+
export declare function assignmentIds(context: BrowserContext, siteUrl: string, activityId: number): Promise<AssignmentIds>;
|
|
17
|
+
/** A participant as `mod_assign_list_participants` returns it (Moodle 5.2), the fields read. */
|
|
18
|
+
export interface MoodleParticipant {
|
|
19
|
+
id: number;
|
|
20
|
+
fullname: string;
|
|
21
|
+
submitted: boolean;
|
|
22
|
+
requiregrading: boolean;
|
|
23
|
+
grantedextension: boolean;
|
|
24
|
+
submissionstatus: string;
|
|
25
|
+
}
|
|
26
|
+
/** The dates `mod_assign_get_participant` gives for one participant (Unix seconds; 0 is none). */
|
|
27
|
+
export interface MoodleParticipantDates {
|
|
28
|
+
allowsubmissionsfromdate: number;
|
|
29
|
+
duedate: number;
|
|
30
|
+
cutoffdate: number;
|
|
31
|
+
}
|
|
32
|
+
export declare function listParticipants(context: BrowserContext, siteUrl: string, instanceId: number): Promise<MoodleParticipant[]>;
|
|
33
|
+
export declare function participantDates(context: BrowserContext, siteUrl: string, instanceId: number, userId: number): Promise<MoodleParticipantDates>;
|
|
34
|
+
/** One submission plugin's content: its text, paragraphs kept, and its files. */
|
|
35
|
+
export interface PluginContent {
|
|
36
|
+
plugin: string;
|
|
37
|
+
text: string;
|
|
38
|
+
files: {
|
|
39
|
+
name: string;
|
|
40
|
+
url: string;
|
|
41
|
+
}[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The content of a submission as the grader's panel renders it: per plugin, the full block
|
|
45
|
+
* (`.full_assignsubmission_<plugin>_<sid>`) or, for content short enough to have none, the summary
|
|
46
|
+
* one. Parsed in the page, by class only (ADR-004). The comments plugin isn't the submission.
|
|
47
|
+
*/
|
|
48
|
+
export declare function readSubmissionContent(page: Page, html: string): Promise<PluginContent[]>;
|
|
49
|
+
/** The grader panel's HTML for one participant: their current submission and grade form. */
|
|
50
|
+
export declare function gradingPanelHtml(context: BrowserContext, siteUrl: string, contextId: number, userId: number): Promise<string>;
|
|
51
|
+
/** Moodle's grader didn't take the grade. */
|
|
52
|
+
export declare class GradingError extends MoodleError {
|
|
53
|
+
name: string;
|
|
54
|
+
constructor(message: string);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fills the grading panel's form for one participant in `page` (a blank tab), and returns it
|
|
58
|
+
* serialized as the grader does (`form.serialize()`), ready for `mod_assign_submit_grading_form`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function fillGradingForm(page: Page, panelHtml: string, fields: Record<string, FieldValue>): Promise<string>;
|
|
61
|
+
/** The grade and feedback currently in a grading panel, if any. */
|
|
62
|
+
export declare function panelGrade(page: Page, panelHtml: string): Promise<{
|
|
63
|
+
grade?: string;
|
|
64
|
+
gradebookGrade?: string;
|
|
65
|
+
feedback?: string;
|
|
66
|
+
}>;
|
|
67
|
+
/** Submits a serialized grading form; Moodle answers warnings when it refuses it. */
|
|
68
|
+
export declare function submitGradingForm(context: BrowserContext, siteUrl: string, instanceId: number, userId: number, serialized: string): Promise<void>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { callService } from "../../core/ajax.js";
|
|
2
|
+
import { setFields } from "../../core/forms.js";
|
|
3
|
+
import { openSitePage } from "../../core/session.js";
|
|
4
|
+
import { MoodleError } from "../../core/errors.js";
|
|
5
|
+
/** The id isn't an assignment's course module, or the person can't grade it. */
|
|
6
|
+
export class NotAnAssignmentError extends MoodleError {
|
|
7
|
+
name = "NotAnAssignmentError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message, "not-an-assignment");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// Ids never change for a course module: one grader page per assignment and site, for the process.
|
|
13
|
+
const idCache = new Map();
|
|
14
|
+
/** Reads `data-assignmentid` and `data-contextid` from a page (the grader's), or `undefined`. */
|
|
15
|
+
export function readAssignmentIds(page) {
|
|
16
|
+
return page.evaluate(() => {
|
|
17
|
+
const g = globalThis;
|
|
18
|
+
const element = g.document.querySelector("[data-assignmentid][data-contextid]");
|
|
19
|
+
const instanceId = Number(element?.getAttribute("data-assignmentid"));
|
|
20
|
+
const contextId = Number(element?.getAttribute("data-contextid"));
|
|
21
|
+
return instanceId > 0 && contextId > 0 ? { instanceId, contextId } : undefined;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export async function assignmentIds(context, siteUrl, activityId) {
|
|
25
|
+
const key = `${siteUrl}|${activityId}`;
|
|
26
|
+
const cached = idCache.get(key);
|
|
27
|
+
if (cached)
|
|
28
|
+
return cached;
|
|
29
|
+
const page = await openSitePage(context, siteUrl, `mod/assign/view.php?id=${activityId}&action=grader`);
|
|
30
|
+
try {
|
|
31
|
+
const ids = await readAssignmentIds(page);
|
|
32
|
+
if (!ids)
|
|
33
|
+
throw new NotAnAssignmentError(`Activity ${activityId} isn't an assignment you can grade (its grader page has no assignment). Take the id of an assignment (module "assign").`);
|
|
34
|
+
idCache.set(key, ids);
|
|
35
|
+
return ids;
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
await page.close().catch(() => undefined);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function listParticipants(context, siteUrl, instanceId) {
|
|
42
|
+
return callService(context, siteUrl, "mod_assign_list_participants", {
|
|
43
|
+
assignid: instanceId,
|
|
44
|
+
groupid: 0,
|
|
45
|
+
filter: "",
|
|
46
|
+
skip: 0,
|
|
47
|
+
limit: 0,
|
|
48
|
+
onlyids: false,
|
|
49
|
+
includeenrolments: false,
|
|
50
|
+
tablesort: false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export function participantDates(context, siteUrl, instanceId, userId) {
|
|
54
|
+
return callService(context, siteUrl, "mod_assign_get_participant", { assignid: instanceId, userid: userId, embeduser: false });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The content of a submission as the grader's panel renders it: per plugin, the full block
|
|
58
|
+
* (`.full_assignsubmission_<plugin>_<sid>`) or, for content short enough to have none, the summary
|
|
59
|
+
* one. Parsed in the page, by class only (ADR-004). The comments plugin isn't the submission.
|
|
60
|
+
*/
|
|
61
|
+
export function readSubmissionContent(page, html) {
|
|
62
|
+
return page.evaluate((html) => {
|
|
63
|
+
const g = globalThis;
|
|
64
|
+
const doc = new g.DOMParser().parseFromString(html, "text/html");
|
|
65
|
+
const BLOCKS = new Set(["P", "DIV", "LI", "UL", "OL", "TR", "TABLE", "PRE", "BLOCKQUOTE", "H1", "H2", "H3", "H4", "H5", "H6"]);
|
|
66
|
+
const found = new Map();
|
|
67
|
+
for (const element of Array.from(doc.querySelectorAll('[class*="assignsubmission_"]'))) {
|
|
68
|
+
const match = /\b(full|summary)_assignsubmission_([a-z0-9]+)_(\d+)\b/.exec(element.className);
|
|
69
|
+
if (!match || match[2] === "comments")
|
|
70
|
+
continue;
|
|
71
|
+
const key = `${match[2]}_${match[3]}`;
|
|
72
|
+
const entry = found.get(key) ?? { plugin: match[2] };
|
|
73
|
+
entry[match[1]] = element;
|
|
74
|
+
found.set(key, entry);
|
|
75
|
+
}
|
|
76
|
+
return Array.from(found.values()).map(({ plugin, full, summary }) => {
|
|
77
|
+
const block = (full ?? summary);
|
|
78
|
+
for (const control of Array.from(block.querySelectorAll(".expandsummaryicon, script, style")))
|
|
79
|
+
control.remove();
|
|
80
|
+
const files = Array.from(block.querySelectorAll('a[href*="pluginfile.php"]')).map((a) => ({ name: (a.textContent ?? "").trim(), url: a.getAttribute("href") ?? "" }));
|
|
81
|
+
// Depth first with a stack, not a named recursive function: tsx's keepNames would wrap it in a
|
|
82
|
+
// `__name` helper the page doesn't have (confirmed empirically).
|
|
83
|
+
let raw = "";
|
|
84
|
+
const stack = [block];
|
|
85
|
+
while (stack.length > 0) {
|
|
86
|
+
const item = stack.pop();
|
|
87
|
+
if (typeof item === "string")
|
|
88
|
+
raw += item;
|
|
89
|
+
else if (item.nodeType === 3)
|
|
90
|
+
raw += (item.textContent ?? "").replace(/\s+/g, " ");
|
|
91
|
+
else if (item.nodeName === "BR")
|
|
92
|
+
raw += "\n";
|
|
93
|
+
else if (item.nodeName === "PRE")
|
|
94
|
+
raw += `\n${item.textContent ?? ""}\n`;
|
|
95
|
+
else {
|
|
96
|
+
const isBlock = BLOCKS.has(item.nodeName);
|
|
97
|
+
raw += item.nodeName === "LI" ? "\n- " : isBlock ? "\n" : "";
|
|
98
|
+
// A list item ends where the next one starts: no blank line between items.
|
|
99
|
+
stack.push(isBlock && item.nodeName !== "LI" ? "\n" : "", ...Array.from(item.childNodes).reverse());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const text = raw
|
|
103
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
104
|
+
.replace(/\n[ \t]+/g, "\n")
|
|
105
|
+
.replace(/\n- +/g, "\n- ")
|
|
106
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
107
|
+
.trim();
|
|
108
|
+
return { plugin, text, files };
|
|
109
|
+
});
|
|
110
|
+
}, html);
|
|
111
|
+
}
|
|
112
|
+
/** The grader panel's HTML for one participant: their current submission and grade form. */
|
|
113
|
+
export async function gradingPanelHtml(context, siteUrl, contextId, userId) {
|
|
114
|
+
const fragment = await callService(context, siteUrl, "core_get_fragment", {
|
|
115
|
+
component: "mod_assign",
|
|
116
|
+
callback: "gradingpanel",
|
|
117
|
+
contextid: contextId,
|
|
118
|
+
args: [
|
|
119
|
+
{ name: "userid", value: String(userId) },
|
|
120
|
+
{ name: "attemptnumber", value: "-1" },
|
|
121
|
+
{ name: "jsonformdata", value: '""' },
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
return fragment.html;
|
|
125
|
+
}
|
|
126
|
+
/** Moodle's grader didn't take the grade. */
|
|
127
|
+
export class GradingError extends MoodleError {
|
|
128
|
+
name = "GradingError";
|
|
129
|
+
constructor(message) {
|
|
130
|
+
super(message, "grading");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Fills the grading panel's form for one participant in `page` (a blank tab), and returns it
|
|
135
|
+
* serialized as the grader does (`form.serialize()`), ready for `mod_assign_submit_grading_form`.
|
|
136
|
+
*/
|
|
137
|
+
export async function fillGradingForm(page, panelHtml, fields) {
|
|
138
|
+
await page.setContent(panelHtml);
|
|
139
|
+
await setFields(page, fields);
|
|
140
|
+
return page.evaluate(() => {
|
|
141
|
+
const g = globalThis;
|
|
142
|
+
return new g.URLSearchParams(new g.FormData(g.document.querySelector("form.gradeform"))).toString();
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** The grade and feedback currently in a grading panel, if any. */
|
|
146
|
+
export async function panelGrade(page, panelHtml) {
|
|
147
|
+
await page.setContent(panelHtml);
|
|
148
|
+
return page.evaluate(() => {
|
|
149
|
+
const doc = globalThis.document;
|
|
150
|
+
const grade = doc.querySelector('form.gradeform [name="grade"]')?.value;
|
|
151
|
+
const feedback = doc.querySelector('form.gradeform [name="assignfeedbackcomments_editor[text]"]')?.value;
|
|
152
|
+
// "Current grade in gradebook": the total Moodle computed, also for a rubric or a guide.
|
|
153
|
+
const gradebook = (doc.querySelector('form.gradeform [id^="fitem_id_currentgrade"] .felement')?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
154
|
+
return { grade: grade || undefined, ...(gradebook && gradebook !== "-" ? { gradebookGrade: gradebook } : {}), feedback: feedback || undefined };
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/** Submits a serialized grading form; Moodle answers warnings when it refuses it. */
|
|
158
|
+
export async function submitGradingForm(context, siteUrl, instanceId, userId, serialized) {
|
|
159
|
+
const warnings = await callService(context, siteUrl, "mod_assign_submit_grading_form", {
|
|
160
|
+
assignmentid: instanceId,
|
|
161
|
+
userid: userId,
|
|
162
|
+
jsonformdata: JSON.stringify(serialized),
|
|
163
|
+
});
|
|
164
|
+
if (warnings.length > 0) {
|
|
165
|
+
// Moodle's form messages don't reach the service's warnings: a grade out of range only says it couldn't save.
|
|
166
|
+
throw new GradingError(`Moodle didn't save the grade: ${warnings.map((w) => w.message ?? w.warningcode).join("; ")} Check the grade is within the assignment's range or scale, and the fields' names.`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=grader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grader.js","sourceRoot":"","sources":["../../../src/areas/assignments/grader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import { type PluginContent } from "./grader.js";
|
|
3
|
+
import { type FieldValue } from "../../core/forms.js";
|
|
4
|
+
export interface Submission {
|
|
5
|
+
userId: number;
|
|
6
|
+
fullName: string;
|
|
7
|
+
/** Moodle's status: "submitted", "draft", "new", "reopened", or "none" without a submission. */
|
|
8
|
+
status: string;
|
|
9
|
+
submitted: boolean;
|
|
10
|
+
needsGrading: boolean;
|
|
11
|
+
extensionGranted: boolean;
|
|
12
|
+
/** Until when, where an extension is granted. */
|
|
13
|
+
extensionDueDate?: string;
|
|
14
|
+
allowSubmissionsFrom?: string;
|
|
15
|
+
/** For this participant: overrides and extensions included. */
|
|
16
|
+
dueDate?: string;
|
|
17
|
+
cutoffDate?: string;
|
|
18
|
+
/** Per submission plugin; only for submitted and draft submissions. */
|
|
19
|
+
content?: PluginContent[];
|
|
20
|
+
}
|
|
21
|
+
/** Every participant's submission to an assignment, with its content unless `includeContent` is false. */
|
|
22
|
+
export declare function assignmentSubmissions(context: BrowserContext, siteUrl: string, activityId: number, { includeContent }?: {
|
|
23
|
+
includeContent?: boolean | undefined;
|
|
24
|
+
}): Promise<Submission[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Grades a participant as the grader does: a grade (simple grading), a rubric's level per criterion
|
|
27
|
+
* or a marking guide's score per criterion (by their ids, from `rubric()`), remarks per criterion,
|
|
28
|
+
* feedback comments (HTML), whether to notify, and any other grading field by name. Returns the
|
|
29
|
+
* grade saved and the gradebook's grade, the total Moodle computes for a rubric or a guide.
|
|
30
|
+
*/
|
|
31
|
+
export declare function gradeSubmission(context: BrowserContext, siteUrl: string, activityId: number, userId: number, { grade, rubric, guide, remarks, feedback, notify, fields, }: {
|
|
32
|
+
grade?: string | number;
|
|
33
|
+
/** Criterion id → level id. */
|
|
34
|
+
rubric?: Record<number, number>;
|
|
35
|
+
/** Criterion id → score. */
|
|
36
|
+
guide?: Record<number, number>;
|
|
37
|
+
/** Criterion id → remark, for a rubric or a guide. */
|
|
38
|
+
remarks?: Record<number, string>;
|
|
39
|
+
feedback?: string;
|
|
40
|
+
notify?: boolean;
|
|
41
|
+
fields?: Record<string, FieldValue>;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
grade?: string;
|
|
44
|
+
gradebookGrade?: string;
|
|
45
|
+
feedback?: string;
|
|
46
|
+
changed: string;
|
|
47
|
+
}>;
|
|
48
|
+
export interface GradingCriterion {
|
|
49
|
+
id: number;
|
|
50
|
+
/** A rubric criterion's description; a guide criterion's short name. */
|
|
51
|
+
description: string;
|
|
52
|
+
/** A guide criterion's description for students and for markers. */
|
|
53
|
+
details?: string;
|
|
54
|
+
markers?: string;
|
|
55
|
+
/** A rubric criterion's levels, by the ids grading takes. */
|
|
56
|
+
levels?: {
|
|
57
|
+
id: number;
|
|
58
|
+
definition: string;
|
|
59
|
+
score: number;
|
|
60
|
+
}[];
|
|
61
|
+
/** A guide criterion's maximum score. */
|
|
62
|
+
maxScore?: number;
|
|
63
|
+
}
|
|
64
|
+
export interface GradingDefinition {
|
|
65
|
+
/** "simple" for a plain grade (or a scale), "rubric" or "guide" for advanced grading. */
|
|
66
|
+
method: "simple" | "rubric" | "guide";
|
|
67
|
+
criteria: GradingCriterion[];
|
|
68
|
+
}
|
|
69
|
+
/** The grading method and its criteria, read from a grading panel's form by field names and blocks. */
|
|
70
|
+
export declare function readGradingDefinition(page: Page, panelHtml: string): Promise<GradingDefinition>;
|
|
71
|
+
/**
|
|
72
|
+
* An assignment's grading method and, for a rubric or a marking guide, its criteria with the ids
|
|
73
|
+
* `gradeSubmission` takes: read from the grader's panel for its first participant.
|
|
74
|
+
*/
|
|
75
|
+
export declare function rubric(context: BrowserContext, siteUrl: string, activityId: number): Promise<{
|
|
76
|
+
assignment: {
|
|
77
|
+
id: number;
|
|
78
|
+
};
|
|
79
|
+
} & GradingDefinition>;
|
|
80
|
+
/**
|
|
81
|
+
* Grants participants an extension until a date, through the assignment's extension form (several at
|
|
82
|
+
* once as its bulk action does). Moodle refuses a date before the assignment's due date.
|
|
83
|
+
*/
|
|
84
|
+
export declare function grantExtension(context: BrowserContext, siteUrl: string, activityId: number, userIds: number[], until: Date): Promise<{
|
|
85
|
+
changed: string;
|
|
86
|
+
}>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { GradingError, NotAnAssignmentError, assignmentIds, fillGradingForm, gradingPanelHtml, listParticipants, panelGrade, participantDates, readSubmissionContent, submitGradingForm } from "./grader.js";
|
|
2
|
+
import { isoDate } from "../courses/list.js";
|
|
3
|
+
import { dateSelectorDate, dateSelectorValues, openForm, setFields, submitForm } from "../../core/forms.js";
|
|
4
|
+
import { NotLoggedInError } from "../../core/session.js";
|
|
5
|
+
import { inPage } from "../../core/inPage.js";
|
|
6
|
+
import { MoodleError } from "../../core/errors.js";
|
|
7
|
+
/** The grader's feedback comments field, when the assignment has them on. */
|
|
8
|
+
const FEEDBACK_FIELD = "assignfeedbackcomments_editor[text]";
|
|
9
|
+
/** An extension's date, as its form holds it (no service open to AJAX gives it). */
|
|
10
|
+
async function extensionDate(context, siteUrl, activityId, userId) {
|
|
11
|
+
const page = await openForm(context, siteUrl, `mod/assign/view.php?id=${activityId}&action=grantextension&userid=${userId}`).catch((error) => {
|
|
12
|
+
if (error instanceof NotLoggedInError)
|
|
13
|
+
throw error;
|
|
14
|
+
return undefined;
|
|
15
|
+
});
|
|
16
|
+
if (!page)
|
|
17
|
+
return undefined;
|
|
18
|
+
try {
|
|
19
|
+
return (await dateSelectorDate(page, "extensionduedate"))?.toISOString();
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
await page.close().catch(() => undefined);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Every participant's submission to an assignment, with its content unless `includeContent` is false. */
|
|
26
|
+
export async function assignmentSubmissions(context, siteUrl, activityId, { includeContent = true } = {}) {
|
|
27
|
+
const ids = await assignmentIds(context, siteUrl, activityId);
|
|
28
|
+
const participants = await listParticipants(context, siteUrl, ids.instanceId);
|
|
29
|
+
// A blank tab parses the panels' HTML: nothing of Moodle's loads there.
|
|
30
|
+
const parser = includeContent ? await context.newPage() : undefined;
|
|
31
|
+
const submissions = [];
|
|
32
|
+
try {
|
|
33
|
+
// One participant at a time: one person at a time, as Moodle expects of a session.
|
|
34
|
+
for (const participant of participants) {
|
|
35
|
+
const dates = await participantDates(context, siteUrl, ids.instanceId, participant.id);
|
|
36
|
+
const status = participant.submissionstatus || "none";
|
|
37
|
+
const withContent = parser && (status === "submitted" || status === "draft");
|
|
38
|
+
submissions.push({
|
|
39
|
+
userId: participant.id,
|
|
40
|
+
fullName: participant.fullname,
|
|
41
|
+
status,
|
|
42
|
+
submitted: participant.submitted,
|
|
43
|
+
needsGrading: participant.requiregrading,
|
|
44
|
+
extensionGranted: participant.grantedextension,
|
|
45
|
+
extensionDueDate: participant.grantedextension ? await extensionDate(context, siteUrl, activityId, participant.id) : undefined,
|
|
46
|
+
allowSubmissionsFrom: isoDate(dates.allowsubmissionsfromdate),
|
|
47
|
+
dueDate: isoDate(dates.duedate),
|
|
48
|
+
cutoffDate: isoDate(dates.cutoffdate),
|
|
49
|
+
content: withContent ? await readSubmissionContent(parser, await gradingPanelHtml(context, siteUrl, ids.contextId, participant.id)) : undefined,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
await parser?.close().catch(() => undefined);
|
|
55
|
+
}
|
|
56
|
+
return submissions;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Grades a participant as the grader does: a grade (simple grading), a rubric's level per criterion
|
|
60
|
+
* or a marking guide's score per criterion (by their ids, from `rubric()`), remarks per criterion,
|
|
61
|
+
* feedback comments (HTML), whether to notify, and any other grading field by name. Returns the
|
|
62
|
+
* grade saved and the gradebook's grade, the total Moodle computes for a rubric or a guide.
|
|
63
|
+
*/
|
|
64
|
+
export async function gradeSubmission(context, siteUrl, activityId, userId, { grade, rubric, guide, remarks, feedback, notify, fields, }) {
|
|
65
|
+
const ids = await assignmentIds(context, siteUrl, activityId);
|
|
66
|
+
const values = { ...fields };
|
|
67
|
+
if (grade !== undefined)
|
|
68
|
+
values.grade = String(grade);
|
|
69
|
+
for (const [criterion, level] of Object.entries(rubric ?? {}))
|
|
70
|
+
values[`advancedgrading[criteria][${criterion}][levelid]`] = String(level);
|
|
71
|
+
for (const [criterion, score] of Object.entries(guide ?? {}))
|
|
72
|
+
values[`advancedgrading[criteria][${criterion}][score]`] = String(score);
|
|
73
|
+
for (const [criterion, remark] of Object.entries(remarks ?? {}))
|
|
74
|
+
values[`advancedgrading[criteria][${criterion}][remark]`] = remark;
|
|
75
|
+
if (feedback !== undefined)
|
|
76
|
+
values[FEEDBACK_FIELD] = feedback;
|
|
77
|
+
if (notify !== undefined)
|
|
78
|
+
values.sendstudentnotifications = notify ? 1 : 0;
|
|
79
|
+
// A blank tab holds the panel's form: nothing of the grader's page needs to load.
|
|
80
|
+
const page = await context.newPage();
|
|
81
|
+
try {
|
|
82
|
+
const serialized = await fillGradingForm(page, await gradingPanelHtml(context, siteUrl, ids.contextId, userId), values).catch((error) => {
|
|
83
|
+
if (feedback !== undefined && error.message.includes(FEEDBACK_FIELD)) {
|
|
84
|
+
throw new MoodleError("This assignment has no feedback comments: grade it without feedback, or turn feedback comments on in its settings.", "not-available");
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
});
|
|
88
|
+
await submitGradingForm(context, siteUrl, ids.instanceId, userId, serialized);
|
|
89
|
+
const saved = await panelGrade(page, await gradingPanelHtml(context, siteUrl, ids.contextId, userId));
|
|
90
|
+
return { changed: `Graded user ${userId} in assignment ${activityId}${feedback !== undefined ? ", with feedback" : ""}.`, ...saved };
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await page.close().catch(() => undefined);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** The grading method and its criteria, read from a grading panel's form by field names and blocks. */
|
|
97
|
+
export async function readGradingDefinition(page, panelHtml) {
|
|
98
|
+
await page.setContent(panelHtml);
|
|
99
|
+
// Through inPage: it has a helper function (see the conventions on page.evaluate).
|
|
100
|
+
return inPage(page, () => {
|
|
101
|
+
const doc = globalThis.document;
|
|
102
|
+
const text = (el) => (el?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
103
|
+
const method = doc.querySelector(".gradingform_rubric") ? "rubric" : doc.querySelector(".gradingform_guide") ? "guide" : "simple";
|
|
104
|
+
const criteria = Array.from(doc.querySelectorAll('.gradingform_rubric tr.criterion, .gradingform_guide tr.criterion')).flatMap((row) => {
|
|
105
|
+
const id = Number(/-criteria-(\d+)$/.exec(row.id)?.[1]);
|
|
106
|
+
if (!id)
|
|
107
|
+
return [];
|
|
108
|
+
if (method === "rubric") {
|
|
109
|
+
const levels = Array.from(row.querySelectorAll("td.level")).map((level) => ({
|
|
110
|
+
id: Number(level.querySelector('input[type="radio"]')?.value),
|
|
111
|
+
definition: text(level.querySelector(".definition")),
|
|
112
|
+
score: Number(text(level.querySelector(".scorevalue"))),
|
|
113
|
+
}));
|
|
114
|
+
return [{ id, description: text(row.querySelector("td.description")), levels }];
|
|
115
|
+
}
|
|
116
|
+
const max = Number(text(row.querySelector(".criteriondescriptionscore")));
|
|
117
|
+
const details = text(row.querySelector(".criteriondescription"));
|
|
118
|
+
const markers = text(row.querySelector(".criteriondescriptionmarkers"));
|
|
119
|
+
return [{ id, description: text(row.querySelector(".criterionshortname")), ...(details ? { details } : {}), ...(markers ? { markers } : {}), ...(max ? { maxScore: max } : {}) }];
|
|
120
|
+
});
|
|
121
|
+
return { method, criteria };
|
|
122
|
+
}, null);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* An assignment's grading method and, for a rubric or a marking guide, its criteria with the ids
|
|
126
|
+
* `gradeSubmission` takes: read from the grader's panel for its first participant.
|
|
127
|
+
*/
|
|
128
|
+
export async function rubric(context, siteUrl, activityId) {
|
|
129
|
+
const ids = await assignmentIds(context, siteUrl, activityId);
|
|
130
|
+
const [first] = await listParticipants(context, siteUrl, ids.instanceId);
|
|
131
|
+
if (!first)
|
|
132
|
+
throw new GradingError(`Assignment ${activityId} has no participants: its grading form can't be read without one.`);
|
|
133
|
+
const page = await context.newPage();
|
|
134
|
+
try {
|
|
135
|
+
return { assignment: { id: activityId }, ...(await readGradingDefinition(page, await gradingPanelHtml(context, siteUrl, ids.contextId, first.id))) };
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await page.close().catch(() => undefined);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Grants participants an extension until a date, through the assignment's extension form (several at
|
|
143
|
+
* once as its bulk action does). Moodle refuses a date before the assignment's due date.
|
|
144
|
+
*/
|
|
145
|
+
export async function grantExtension(context, siteUrl, activityId, userIds, until) {
|
|
146
|
+
if (userIds.length === 0)
|
|
147
|
+
throw new GradingError("Give the participants to grant the extension to.");
|
|
148
|
+
const page = await openForm(context, siteUrl, `mod/assign/view.php?id=${activityId}&action=grantextension&userid=${userIds[0]}`).catch((error) => {
|
|
149
|
+
if (error instanceof NotLoggedInError)
|
|
150
|
+
throw error;
|
|
151
|
+
throw new NotAnAssignmentError(`You can't grant extensions in activity ${activityId}: it isn't an assignment, or you aren't allowed.`);
|
|
152
|
+
});
|
|
153
|
+
try {
|
|
154
|
+
await setFields(page, { selectedusers: userIds.join(","), "extensionduedate[enabled]": true, ...(await dateSelectorValues(page, "extensionduedate", until)) });
|
|
155
|
+
await submitForm(page, "submitbutton");
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
await page.close().catch(() => undefined);
|
|
159
|
+
}
|
|
160
|
+
return { changed: `Extension until ${until.toISOString()} granted to user${userIds.length > 1 ? "s" : ""} ${userIds.join(", ")} in assignment ${activityId}.` };
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=grading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grading.js","sourceRoot":"","sources":["../../../src/areas/assignments/grading.ts"],"names":[],"mappings":"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"}
|