@falkenslab/moodle-driver 0.0.0-stage → 0.4.0
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 +131 -2
- package/dist/areas/activities/build.d.ts +98 -0
- package/dist/areas/activities/build.js +248 -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/scorm/scorm.d.ts +60 -0
- package/dist/areas/scorm/scorm.js +171 -0
- package/dist/areas/scorm/scorm.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 +225 -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 +2217 -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 +224 -0
- package/dist/client.js +182 -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 +41 -0
- package/dist/index.js +52 -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 +78 -4
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { MoodleServiceError, callService } from "../../core/ajax.js";
|
|
2
|
+
import { openSitePage } from "../../core/session.js";
|
|
3
|
+
import { readTexts } from "../../core/text.js";
|
|
4
|
+
import { MoodleError } from "../../core/errors.js";
|
|
5
|
+
/** The id isn't a forum's course module, or the person can't see it. */
|
|
6
|
+
export class NotAForumError extends MoodleError {
|
|
7
|
+
name = "NotAForumError";
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message, "not-a-forum");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// The page size asked for; the forum pages until a page brings no new discussions.
|
|
13
|
+
const PAGE_SIZE = 100;
|
|
14
|
+
const MAX_PAGES = 50;
|
|
15
|
+
/** The discussions on a forum page, read by structure: data attributes, links, timestamps. */
|
|
16
|
+
export function readDiscussionRows(page) {
|
|
17
|
+
return page.evaluate(() => {
|
|
18
|
+
const doc = globalThis.document;
|
|
19
|
+
return Array.from(doc.querySelectorAll('tr[data-region="discussion-list-item"][data-discussionid]')).map((row) => {
|
|
20
|
+
const times = Array.from(row.querySelectorAll("time[data-timestamp]"));
|
|
21
|
+
// A person's cell is their name followed by the time: the name is the text before it.
|
|
22
|
+
// (An anonymous callback, not a named helper: see the conventions on page.evaluate.)
|
|
23
|
+
const [startedBy, lastBy] = [times[0], times[1]].map((time) => {
|
|
24
|
+
const all = (time?.closest("td")?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
25
|
+
const when = (time?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
26
|
+
return when && all.endsWith(when) ? all.slice(0, all.length - when.length).trim() : all;
|
|
27
|
+
});
|
|
28
|
+
const lastLink = times[1]?.closest("a");
|
|
29
|
+
const parent = lastLink ? new URL(lastLink.getAttribute("href") ?? "", "http://x/").searchParams.get("parent") : null;
|
|
30
|
+
const repliesCell = Array.from(row.querySelectorAll("td")).find((cell) => /^\d+$/.test((cell.textContent ?? "").trim()) && !cell.querySelector("a"));
|
|
31
|
+
const locked = row.querySelector('[data-region="locked-label"]');
|
|
32
|
+
return {
|
|
33
|
+
id: Number(row.getAttribute("data-discussionid")),
|
|
34
|
+
subject: (row.querySelector('a[href*="discuss.php?d="]')?.textContent ?? "").replace(/\s+/g, " ").trim(),
|
|
35
|
+
author: startedBy ?? "",
|
|
36
|
+
started: times[0] ? Number(times[0].getAttribute("data-timestamp")) : undefined,
|
|
37
|
+
lastPost: { author: lastBy ?? "", time: times[1] ? Number(times[1].getAttribute("data-timestamp")) : undefined, postId: parent ? Number(parent) : undefined },
|
|
38
|
+
replies: Number((repliesCell?.textContent ?? "0").trim()),
|
|
39
|
+
locked: locked !== null && !locked.hasAttribute("hidden"),
|
|
40
|
+
pinned: /\bpinned\b/.test(row.className),
|
|
41
|
+
forumInstanceId: Number(row.getAttribute("data-forumid")) || undefined,
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Every discussion of a forum (its course module id), through all its pages. */
|
|
47
|
+
export async function listDiscussions(context, siteUrl, activityId) {
|
|
48
|
+
const all = new Map();
|
|
49
|
+
for (let p = 0; p < MAX_PAGES; p++) {
|
|
50
|
+
const page = await openSitePage(context, siteUrl, `mod/forum/view.php?id=${activityId}&s=${PAGE_SIZE}&p=${p}`);
|
|
51
|
+
try {
|
|
52
|
+
// The body id names the page in any language.
|
|
53
|
+
if ((await page.locator("body#page-mod-forum-view").count()) === 0) {
|
|
54
|
+
throw new NotAForumError(`Activity ${activityId} isn't a forum you can view. Take the id of a forum (module "forum") from the course structure.`);
|
|
55
|
+
}
|
|
56
|
+
const rows = await readDiscussionRows(page);
|
|
57
|
+
const before = all.size;
|
|
58
|
+
for (const row of rows)
|
|
59
|
+
all.set(row.id, row);
|
|
60
|
+
if (rows.length < PAGE_SIZE || all.size === before)
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await page.close().catch(() => undefined);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return Array.from(all.values());
|
|
68
|
+
}
|
|
69
|
+
export async function discussionPosts(context, siteUrl, discussionId) {
|
|
70
|
+
try {
|
|
71
|
+
return await callService(context, siteUrl, "mod_forum_get_discussion_posts", { discussionid: discussionId, sortby: "created", sortdirection: "ASC", includeinlineattachments: false });
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// A missing discussion fails inside Moodle with a PHP error (confirmed empirically, 5.2).
|
|
75
|
+
if (error instanceof MoodleServiceError && error.errorcode === "generalexceptionmessage") {
|
|
76
|
+
throw new NotAForumError(`Discussion ${discussionId} doesn't exist or you can't see it. Take its id from the forum's discussions. (${error.message})`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** Plain text of Moodle-cleaned HTML fragments, parsed in a blank tab. */
|
|
82
|
+
export async function htmlToTexts(context, fragments) {
|
|
83
|
+
if (fragments.length === 0)
|
|
84
|
+
return [];
|
|
85
|
+
const page = await context.newPage();
|
|
86
|
+
try {
|
|
87
|
+
await page.setContent(fragments.map((html) => `<div class="driver-fragment">${html}</div>`).join(""));
|
|
88
|
+
return await readTexts(page, "body > .driver-fragment");
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
await page.close().catch(() => undefined);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** A forum's instance id (what `post.php?forum=` takes), from its page. */
|
|
95
|
+
export async function forumInstanceId(context, siteUrl, activityId) {
|
|
96
|
+
const page = await openSitePage(context, siteUrl, `mod/forum/view.php?id=${activityId}`);
|
|
97
|
+
try {
|
|
98
|
+
if ((await page.locator("body#page-mod-forum-view").count()) === 0) {
|
|
99
|
+
throw new NotAForumError(`Activity ${activityId} isn't a forum you can view. Take the id of a forum (module "forum") from the course structure.`);
|
|
100
|
+
}
|
|
101
|
+
// The page's quick "new discussion" form carries it (also on an empty forum); discussion rows too.
|
|
102
|
+
const id = await page.evaluate(() => {
|
|
103
|
+
const doc = globalThis.document;
|
|
104
|
+
const fromForm = Number(doc.querySelector('form[action*="post.php"] [name="forum"]')?.value);
|
|
105
|
+
if (fromForm > 0)
|
|
106
|
+
return fromForm;
|
|
107
|
+
return Array.from(doc.querySelectorAll("[data-forumid]"))
|
|
108
|
+
.map((element) => Number(element.getAttribute("data-forumid")))
|
|
109
|
+
.find((value) => value > 0);
|
|
110
|
+
});
|
|
111
|
+
if (!id)
|
|
112
|
+
throw new NotAForumError(`Forum ${activityId}'s page doesn't say its id; you may not be allowed to post there.`);
|
|
113
|
+
return id;
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await page.close().catch(() => undefined);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=discussions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discussions.js","sourceRoot":"","sources":["../../../src/areas/forums/discussions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { BrowserContext } from "playwright-core";
|
|
2
|
+
import { type FieldValue } from "../../core/forms.js";
|
|
3
|
+
export interface ForumDiscussion {
|
|
4
|
+
id: number;
|
|
5
|
+
subject: string;
|
|
6
|
+
author: string;
|
|
7
|
+
started?: string;
|
|
8
|
+
lastPost: {
|
|
9
|
+
author: string;
|
|
10
|
+
time?: string;
|
|
11
|
+
postId?: number;
|
|
12
|
+
};
|
|
13
|
+
replies: number;
|
|
14
|
+
locked: boolean;
|
|
15
|
+
pinned: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** A forum's discussions, as its page lists them: who started each, replies, last post. */
|
|
18
|
+
export declare function forumDiscussions(context: BrowserContext, siteUrl: string, activityId: number): Promise<ForumDiscussion[]>;
|
|
19
|
+
export interface ForumPost {
|
|
20
|
+
id: number;
|
|
21
|
+
parentId?: number;
|
|
22
|
+
subject: string;
|
|
23
|
+
author: {
|
|
24
|
+
id: number;
|
|
25
|
+
fullName: string;
|
|
26
|
+
};
|
|
27
|
+
created?: string;
|
|
28
|
+
/** Only when edited more than a minute after posting. */
|
|
29
|
+
modified?: string;
|
|
30
|
+
/** The message as plain text, paragraphs and lists kept. */
|
|
31
|
+
text: string;
|
|
32
|
+
attachments: {
|
|
33
|
+
name: string;
|
|
34
|
+
url: string;
|
|
35
|
+
}[];
|
|
36
|
+
privateReply: boolean;
|
|
37
|
+
unread?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** A discussion's posts, oldest first, with their messages as text. */
|
|
40
|
+
export declare function discussion(context: BrowserContext, siteUrl: string, discussionId: number): Promise<{
|
|
41
|
+
discussion: {
|
|
42
|
+
id: number;
|
|
43
|
+
forumInstanceId: number;
|
|
44
|
+
courseId: number;
|
|
45
|
+
};
|
|
46
|
+
posts: ForumPost[];
|
|
47
|
+
}>;
|
|
48
|
+
export interface PostInput {
|
|
49
|
+
/** The message (HTML). */
|
|
50
|
+
message: string;
|
|
51
|
+
/** Absolute local paths to attach. */
|
|
52
|
+
files?: string[];
|
|
53
|
+
/** Subscribe to the discussion; Moodle's default when missing. */
|
|
54
|
+
subscribe?: boolean;
|
|
55
|
+
/** Other post form fields by name ({"pinned": true} for a teacher). */
|
|
56
|
+
fields?: Record<string, FieldValue>;
|
|
57
|
+
}
|
|
58
|
+
/** Replies to a post; returns the new post's id and its discussion's. */
|
|
59
|
+
export declare function replyToPost(context: BrowserContext, siteUrl: string, postId: number, input: PostInput & {
|
|
60
|
+
subject?: string;
|
|
61
|
+
}): Promise<{
|
|
62
|
+
changed: string;
|
|
63
|
+
postId: number | undefined;
|
|
64
|
+
discussionId: number | undefined;
|
|
65
|
+
}>;
|
|
66
|
+
/** Starts a discussion in a forum; returns its id. */
|
|
67
|
+
export declare function startDiscussion(context: BrowserContext, siteUrl: string, activityId: number, input: PostInput & {
|
|
68
|
+
subject: string;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
changed: string;
|
|
71
|
+
discussionId: number | undefined;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Pins or unpins, locks or unlocks a discussion, as the discussion list's menu does; what's already
|
|
75
|
+
* so is left alone. Returns the discussion as the forum lists it afterwards.
|
|
76
|
+
*/
|
|
77
|
+
export declare function setDiscussionState(context: BrowserContext, siteUrl: string, activityId: number, discussionId: number, { pinned, locked }: {
|
|
78
|
+
pinned?: boolean;
|
|
79
|
+
locked?: boolean;
|
|
80
|
+
}): Promise<{
|
|
81
|
+
changed: string;
|
|
82
|
+
discussion?: ForumDiscussion;
|
|
83
|
+
}>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { callService } from "../../core/ajax.js";
|
|
2
|
+
import { isoDate } from "../courses/list.js";
|
|
3
|
+
import { checkLocalFiles } from "../../core/files.js";
|
|
4
|
+
import { formValue, openForm, setFields, submitForm, uploadToDraft } from "../../core/forms.js";
|
|
5
|
+
import { NotAForumError, discussionPosts, forumInstanceId, htmlToTexts, listDiscussions } from "./discussions.js";
|
|
6
|
+
import { decodeEntities } from "../../core/text.js";
|
|
7
|
+
import { MoodleError } from "../../core/errors.js";
|
|
8
|
+
/** A forum's discussions, as its page lists them: who started each, replies, last post. */
|
|
9
|
+
export async function forumDiscussions(context, siteUrl, activityId) {
|
|
10
|
+
const rows = await listDiscussions(context, siteUrl, activityId);
|
|
11
|
+
return rows.map((row) => ({
|
|
12
|
+
id: row.id,
|
|
13
|
+
subject: row.subject,
|
|
14
|
+
author: row.author,
|
|
15
|
+
started: isoDate(row.started),
|
|
16
|
+
lastPost: { author: row.lastPost.author, time: isoDate(row.lastPost.time), postId: row.lastPost.postId },
|
|
17
|
+
replies: row.replies,
|
|
18
|
+
locked: row.locked,
|
|
19
|
+
pinned: row.pinned,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
/** A discussion's posts, oldest first, with their messages as text. */
|
|
23
|
+
export async function discussion(context, siteUrl, discussionId) {
|
|
24
|
+
const { posts, forumid, courseid } = await discussionPosts(context, siteUrl, discussionId);
|
|
25
|
+
const texts = await htmlToTexts(context, posts.map((post) => post.message));
|
|
26
|
+
return {
|
|
27
|
+
discussion: { id: discussionId, forumInstanceId: forumid, courseId: courseid },
|
|
28
|
+
posts: posts.map((post, i) => ({
|
|
29
|
+
id: post.id,
|
|
30
|
+
parentId: post.parentid ?? undefined,
|
|
31
|
+
subject: decodeEntities(post.subject),
|
|
32
|
+
author: { id: post.author.id, fullName: post.author.fullname },
|
|
33
|
+
created: isoDate(post.timecreated),
|
|
34
|
+
modified: post.timemodified && post.timemodified - post.timecreated > 60 ? isoDate(post.timemodified) : undefined,
|
|
35
|
+
text: texts[i] ?? "",
|
|
36
|
+
attachments: (post.attachments ?? []).map((file) => ({ name: file.filename, url: file.url ?? file.fileurl ?? "" })),
|
|
37
|
+
privateReply: post.isprivatereply ?? false,
|
|
38
|
+
unread: post.unread ?? undefined,
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Fills and submits the post form at `formPath`; returns the URL Moodle lands on. */
|
|
43
|
+
async function post(context, siteUrl, formPath, input) {
|
|
44
|
+
// Moodle takes "<p></p>" as a message and posts it blank (confirmed empirically, 5.2).
|
|
45
|
+
const text = input.message.replace(/<[^>]*>/g, "").replace(/ /g, " ").trim();
|
|
46
|
+
if (text === "" && !/<(img|video|audio|iframe)\b/i.test(input.message))
|
|
47
|
+
throw new MoodleError("The message is empty: write something before posting.", "invalid-input");
|
|
48
|
+
if (input.files)
|
|
49
|
+
await checkLocalFiles(input.files);
|
|
50
|
+
const page = await openForm(context, siteUrl, formPath);
|
|
51
|
+
try {
|
|
52
|
+
if (input.files && input.files.length > 0) {
|
|
53
|
+
const draft = await formValue(page, "attachments");
|
|
54
|
+
if (!draft)
|
|
55
|
+
throw new MoodleError("This forum takes no attachments.", "not-available");
|
|
56
|
+
await uploadToDraft(page, draft, input.files);
|
|
57
|
+
}
|
|
58
|
+
const values = { "message[text]": input.message, ...input.fields };
|
|
59
|
+
if (input.subject !== undefined)
|
|
60
|
+
values.subject = input.subject;
|
|
61
|
+
if (input.subscribe !== undefined)
|
|
62
|
+
values.discussionsubscribe = input.subscribe;
|
|
63
|
+
await setFields(page, values);
|
|
64
|
+
await submitForm(page, "submitbutton");
|
|
65
|
+
return page.url();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await page.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Replies to a post; returns the new post's id and its discussion's. */
|
|
72
|
+
export async function replyToPost(context, siteUrl, postId, input) {
|
|
73
|
+
const landed = new URL(await post(context, siteUrl, `mod/forum/post.php?reply=${postId}`, input));
|
|
74
|
+
// Moodle lands on the discussion, at the new post: discuss.php?d=<discussion>#p<post>.
|
|
75
|
+
const newPost = /^#p(\d+)$/.exec(landed.hash)?.[1];
|
|
76
|
+
const discussionId = landed.searchParams.get("d");
|
|
77
|
+
return {
|
|
78
|
+
changed: `Replied to post ${postId}${newPost ? ` with post ${newPost}` : ""}.`,
|
|
79
|
+
postId: newPost ? Number(newPost) : undefined,
|
|
80
|
+
discussionId: discussionId ? Number(discussionId) : undefined,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Starts a discussion in a forum; returns its id. */
|
|
84
|
+
export async function startDiscussion(context, siteUrl, activityId, input) {
|
|
85
|
+
const instance = await forumInstanceId(context, siteUrl, activityId);
|
|
86
|
+
const before = new Set((await listDiscussions(context, siteUrl, activityId)).map((d) => d.id));
|
|
87
|
+
await post(context, siteUrl, `mod/forum/post.php?forum=${instance}`, input);
|
|
88
|
+
const created = (await listDiscussions(context, siteUrl, activityId)).find((d) => !before.has(d.id));
|
|
89
|
+
return { changed: `Started discussion ${created?.id ?? "?"} "${input.subject}" in forum ${activityId}.`, discussionId: created?.id };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Pins or unpins, locks or unlocks a discussion, as the discussion list's menu does; what's already
|
|
93
|
+
* so is left alone. Returns the discussion as the forum lists it afterwards.
|
|
94
|
+
*/
|
|
95
|
+
export async function setDiscussionState(context, siteUrl, activityId, discussionId, { pinned, locked }) {
|
|
96
|
+
const find = async () => (await forumDiscussions(context, siteUrl, activityId)).find((d) => d.id === discussionId);
|
|
97
|
+
const row = (await listDiscussions(context, siteUrl, activityId)).find((d) => d.id === discussionId);
|
|
98
|
+
if (!row)
|
|
99
|
+
throw new NotAForumError(`Forum ${activityId} has no discussion ${discussionId}. Take its id from the forum's discussions.`);
|
|
100
|
+
const done = [];
|
|
101
|
+
if (pinned !== undefined && pinned !== row.pinned) {
|
|
102
|
+
await callService(context, siteUrl, "mod_forum_set_pin_state", { discussionid: discussionId, targetstate: pinned ? 1 : 0 });
|
|
103
|
+
done.push(pinned ? "pinned" : "unpinned");
|
|
104
|
+
}
|
|
105
|
+
if (locked !== undefined && locked !== row.locked) {
|
|
106
|
+
// Rows don't always carry the forum's id (confirmed empirically, 5.2): its page does.
|
|
107
|
+
const instance = row.forumInstanceId ?? (await forumInstanceId(context, siteUrl, activityId));
|
|
108
|
+
// The service takes the current lock and switches it: 0 locks now, anything else unlocks.
|
|
109
|
+
await callService(context, siteUrl, "mod_forum_set_lock_state", { forumid: instance, discussionid: discussionId, targetstate: locked ? 0 : 1 });
|
|
110
|
+
done.push(locked ? "locked" : "unlocked");
|
|
111
|
+
}
|
|
112
|
+
return { changed: done.length > 0 ? `Discussion ${discussionId} ${done.join(" and ")}.` : "Nothing to change.", discussion: await find() };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=posts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"posts.js","sourceRoot":"","sources":["../../../src/areas/forums/posts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import { MoodleError } from "../../core/errors.js";
|
|
3
|
+
/** The gradebook can't be read or changed: not allowed, recalculating, or a value Moodle refuses. */
|
|
4
|
+
export declare class GradebookError extends MoodleError {
|
|
5
|
+
name: string;
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/** Moodle's aggregations by their constant (GRADE_AGGREGATE_*). */
|
|
9
|
+
declare const AGGREGATION_CODES: {
|
|
10
|
+
readonly mean: 0;
|
|
11
|
+
readonly median: 2;
|
|
12
|
+
readonly lowest: 4;
|
|
13
|
+
readonly highest: 6;
|
|
14
|
+
readonly mode: 8;
|
|
15
|
+
readonly "weighted-mean": 10;
|
|
16
|
+
readonly "simple-weighted-mean": 11;
|
|
17
|
+
readonly "mean-with-extra-credits": 12;
|
|
18
|
+
readonly natural: 13;
|
|
19
|
+
};
|
|
20
|
+
export type GradeAggregation = keyof typeof AGGREGATION_CODES;
|
|
21
|
+
export declare const GRADE_AGGREGATIONS: GradeAggregation[];
|
|
22
|
+
export interface GradeColumn {
|
|
23
|
+
id: number;
|
|
24
|
+
name: string;
|
|
25
|
+
kind: "item" | "category" | "course";
|
|
26
|
+
activityId?: number;
|
|
27
|
+
activityType?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface GraderReport {
|
|
30
|
+
columns: GradeColumn[];
|
|
31
|
+
/** Grades as the report shows them ("-" left out). */
|
|
32
|
+
rows: {
|
|
33
|
+
userId: number;
|
|
34
|
+
fullName: string;
|
|
35
|
+
grades: {
|
|
36
|
+
itemId: number;
|
|
37
|
+
grade?: string;
|
|
38
|
+
}[];
|
|
39
|
+
}[];
|
|
40
|
+
}
|
|
41
|
+
/** One page of the grader report, by data attributes and links, in any language. */
|
|
42
|
+
export declare function readGraderReport(page: Page): Promise<GraderReport>;
|
|
43
|
+
/**
|
|
44
|
+
* Everyone's grades in a course as the grader report shows them, page by page, in one group when
|
|
45
|
+
* given. People come as user id and full name only.
|
|
46
|
+
*/
|
|
47
|
+
export declare function graderReport(context: BrowserContext, siteUrl: string, courseId: number, { group }?: {
|
|
48
|
+
group?: number;
|
|
49
|
+
}): Promise<GraderReport>;
|
|
50
|
+
export interface GradebookCategory {
|
|
51
|
+
id: number;
|
|
52
|
+
/** The category's own grade item (its total). */
|
|
53
|
+
itemId: number;
|
|
54
|
+
name: string;
|
|
55
|
+
parentId?: number;
|
|
56
|
+
aggregation?: GradeAggregation;
|
|
57
|
+
maxGrade?: number;
|
|
58
|
+
/** Its weight in its parent, in percent, where the parent aggregates with weights. */
|
|
59
|
+
weight?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface GradebookItem {
|
|
62
|
+
id: number;
|
|
63
|
+
name: string;
|
|
64
|
+
categoryId?: number;
|
|
65
|
+
/** The module ("assign", "quiz"…) for an activity's item, "manual" for one made in the gradebook. */
|
|
66
|
+
type: string;
|
|
67
|
+
maxGrade?: number;
|
|
68
|
+
/** In percent, where the category aggregates with weights. */
|
|
69
|
+
weight?: number;
|
|
70
|
+
weightOverridden: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** The gradebook setup page's rows, by data attributes and field names. */
|
|
73
|
+
export declare function readGradebookSetup(page: Page): Promise<{
|
|
74
|
+
categories: GradebookCategory[];
|
|
75
|
+
items: GradebookItem[];
|
|
76
|
+
}>;
|
|
77
|
+
/** A course's gradebook setup: its categories (aggregation, weights) and grade items (category, maximum, weight). */
|
|
78
|
+
export declare function gradebookSetup(context: BrowserContext, siteUrl: string, courseId: number): Promise<{
|
|
79
|
+
categories: GradebookCategory[];
|
|
80
|
+
items: GradebookItem[];
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Changes a grade item: moves it into a category, sets its weight (percent, overriding Natural's
|
|
84
|
+
* automatic one), or its maximum grade (a manual item's; an activity's is set in the activity).
|
|
85
|
+
* Returns the item afterwards.
|
|
86
|
+
*/
|
|
87
|
+
export declare function updateGradeItem(context: BrowserContext, siteUrl: string, courseId: number, itemId: number, { category, weight, maxGrade }: {
|
|
88
|
+
category?: number;
|
|
89
|
+
weight?: number;
|
|
90
|
+
maxGrade?: number;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
changed: string;
|
|
93
|
+
item?: GradebookItem;
|
|
94
|
+
}>;
|
|
95
|
+
/** Adds a grade category (at the course's top level, or inside `parentId`); returns it as the setup shows it. */
|
|
96
|
+
export declare function addGradeCategory(context: BrowserContext, siteUrl: string, courseId: number, { name, aggregation, parentId }: {
|
|
97
|
+
name: string;
|
|
98
|
+
aggregation?: GradeAggregation;
|
|
99
|
+
parentId?: number;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
changed: string;
|
|
102
|
+
category?: GradebookCategory;
|
|
103
|
+
}>;
|
|
104
|
+
export {};
|