@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.
Files changed (297) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -2
  3. package/dist/areas/activities/build.d.ts +98 -0
  4. package/dist/areas/activities/build.js +246 -0
  5. package/dist/areas/activities/build.js.map +1 -0
  6. package/dist/areas/activities/content.d.ts +53 -0
  7. package/dist/areas/activities/content.js +113 -0
  8. package/dist/areas/activities/content.js.map +1 -0
  9. package/dist/areas/assignments/grader.d.ts +68 -0
  10. package/dist/areas/assignments/grader.js +169 -0
  11. package/dist/areas/assignments/grader.js.map +1 -0
  12. package/dist/areas/assignments/grading.d.ts +86 -0
  13. package/dist/areas/assignments/grading.js +162 -0
  14. package/dist/areas/assignments/grading.js.map +1 -0
  15. package/dist/areas/assignments/student.d.ts +57 -0
  16. package/dist/areas/assignments/student.js +115 -0
  17. package/dist/areas/assignments/student.js.map +1 -0
  18. package/dist/areas/calendar/calendar.d.ts +43 -0
  19. package/dist/areas/calendar/calendar.js +84 -0
  20. package/dist/areas/calendar/calendar.js.map +1 -0
  21. package/dist/areas/completion/completion.d.ts +50 -0
  22. package/dist/areas/completion/completion.js +112 -0
  23. package/dist/areas/completion/completion.js.map +1 -0
  24. package/dist/areas/courses/list.d.ts +59 -0
  25. package/dist/areas/courses/list.js +74 -0
  26. package/dist/areas/courses/list.js.map +1 -0
  27. package/dist/areas/courses/overview.d.ts +83 -0
  28. package/dist/areas/courses/overview.js +137 -0
  29. package/dist/areas/courses/overview.js.map +1 -0
  30. package/dist/areas/courses/state.d.ts +51 -0
  31. package/dist/areas/courses/state.js +45 -0
  32. package/dist/areas/courses/state.js.map +1 -0
  33. package/dist/areas/courses/structure.d.ts +89 -0
  34. package/dist/areas/courses/structure.js +104 -0
  35. package/dist/areas/courses/structure.js.map +1 -0
  36. package/dist/areas/forums/discussions.d.ts +57 -0
  37. package/dist/areas/forums/discussions.js +119 -0
  38. package/dist/areas/forums/discussions.js.map +1 -0
  39. package/dist/areas/forums/posts.d.ts +83 -0
  40. package/dist/areas/forums/posts.js +114 -0
  41. package/dist/areas/forums/posts.js.map +1 -0
  42. package/dist/areas/grades/gradebook.d.ts +104 -0
  43. package/dist/areas/grades/gradebook.js +250 -0
  44. package/dist/areas/grades/gradebook.js.map +1 -0
  45. package/dist/areas/grades/user-report.d.ts +26 -0
  46. package/dist/areas/grades/user-report.js +71 -0
  47. package/dist/areas/grades/user-report.js.map +1 -0
  48. package/dist/areas/h5p/h5p.d.ts +33 -0
  49. package/dist/areas/h5p/h5p.js +104 -0
  50. package/dist/areas/h5p/h5p.js.map +1 -0
  51. package/dist/areas/lessons/lessons.d.ts +60 -0
  52. package/dist/areas/lessons/lessons.js +145 -0
  53. package/dist/areas/lessons/lessons.js.map +1 -0
  54. package/dist/areas/messages/messages.d.ts +68 -0
  55. package/dist/areas/messages/messages.js +84 -0
  56. package/dist/areas/messages/messages.js.map +1 -0
  57. package/dist/areas/participants/participants.d.ts +57 -0
  58. package/dist/areas/participants/participants.js +135 -0
  59. package/dist/areas/participants/participants.js.map +1 -0
  60. package/dist/areas/questions/banks.d.ts +83 -0
  61. package/dist/areas/questions/banks.js +239 -0
  62. package/dist/areas/questions/banks.js.map +1 -0
  63. package/dist/areas/quizzes/attempts.d.ts +123 -0
  64. package/dist/areas/quizzes/attempts.js +300 -0
  65. package/dist/areas/quizzes/attempts.js.map +1 -0
  66. package/dist/areas/quizzes/results.d.ts +36 -0
  67. package/dist/areas/quizzes/results.js +93 -0
  68. package/dist/areas/quizzes/results.js.map +1 -0
  69. package/dist/areas/quizzes/structure.d.ts +42 -0
  70. package/dist/areas/quizzes/structure.js +100 -0
  71. package/dist/areas/quizzes/structure.js.map +1 -0
  72. package/dist/areas/site/info.d.ts +25 -0
  73. package/dist/areas/site/info.js +45 -0
  74. package/dist/areas/site/info.js.map +1 -0
  75. package/dist/areas/site/users.d.ts +56 -0
  76. package/dist/areas/site/users.js +89 -0
  77. package/dist/areas/site/users.js.map +1 -0
  78. package/dist/areas/surveys/choice.d.ts +38 -0
  79. package/dist/areas/surveys/choice.js +105 -0
  80. package/dist/areas/surveys/choice.js.map +1 -0
  81. package/dist/areas/surveys/surveys.d.ts +51 -0
  82. package/dist/areas/surveys/surveys.js +201 -0
  83. package/dist/areas/surveys/surveys.js.map +1 -0
  84. package/dist/browser/chromium.d.ts +15 -0
  85. package/dist/browser/chromium.js +53 -0
  86. package/dist/browser/chromium.js.map +1 -0
  87. package/dist/browser/cookies.d.ts +11 -0
  88. package/dist/browser/cookies.js +31 -0
  89. package/dist/browser/cookies.js.map +1 -0
  90. package/dist/browser/manager.d.ts +57 -0
  91. package/dist/browser/manager.js +177 -0
  92. package/dist/browser/manager.js.map +1 -0
  93. package/dist/browser.d.ts +53 -0
  94. package/dist/browser.js +167 -0
  95. package/dist/browser.js.map +1 -0
  96. package/dist/categories.d.ts +28 -0
  97. package/dist/categories.js +222 -0
  98. package/dist/categories.js.map +1 -0
  99. package/dist/chromiumInstaller.d.ts +15 -0
  100. package/dist/chromiumInstaller.js +52 -0
  101. package/dist/chromiumInstaller.js.map +1 -0
  102. package/dist/cli/args.d.ts +33 -0
  103. package/dist/cli/args.js +194 -0
  104. package/dist/cli/args.js.map +1 -0
  105. package/dist/cli/commands.generated.d.ts +4 -0
  106. package/dist/cli/commands.generated.js +2203 -0
  107. package/dist/cli/commands.generated.js.map +1 -0
  108. package/dist/cli/config.d.ts +46 -0
  109. package/dist/cli/config.js +107 -0
  110. package/dist/cli/config.js.map +1 -0
  111. package/dist/cli/main.d.ts +2 -0
  112. package/dist/cli/main.js +19 -0
  113. package/dist/cli/main.js.map +1 -0
  114. package/dist/cli/output.d.ts +4 -0
  115. package/dist/cli/output.js +71 -0
  116. package/dist/cli/output.js.map +1 -0
  117. package/dist/cli/run.d.ts +15 -0
  118. package/dist/cli/run.js +194 -0
  119. package/dist/cli/run.js.map +1 -0
  120. package/dist/cli/spec.d.ts +42 -0
  121. package/dist/cli/spec.js +4 -0
  122. package/dist/cli/spec.js.map +1 -0
  123. package/dist/client.d.ts +217 -0
  124. package/dist/client.js +179 -0
  125. package/dist/client.js.map +1 -0
  126. package/dist/core/ajax.d.ts +25 -0
  127. package/dist/core/ajax.js +85 -0
  128. package/dist/core/ajax.js.map +1 -0
  129. package/dist/core/editor.d.ts +18 -0
  130. package/dist/core/editor.js +51 -0
  131. package/dist/core/editor.js.map +1 -0
  132. package/dist/core/errors.d.ts +12 -0
  133. package/dist/core/errors.js +10 -0
  134. package/dist/core/errors.js.map +1 -0
  135. package/dist/core/files.d.ts +16 -0
  136. package/dist/core/files.js +58 -0
  137. package/dist/core/files.js.map +1 -0
  138. package/dist/core/forms.d.ts +45 -0
  139. package/dist/core/forms.js +200 -0
  140. package/dist/core/forms.js.map +1 -0
  141. package/dist/core/http.d.ts +18 -0
  142. package/dist/core/http.js +22 -0
  143. package/dist/core/http.js.map +1 -0
  144. package/dist/core/inPage.d.ts +8 -0
  145. package/dist/core/inPage.js +10 -0
  146. package/dist/core/inPage.js.map +1 -0
  147. package/dist/core/login.d.ts +40 -0
  148. package/dist/core/login.js +83 -0
  149. package/dist/core/login.js.map +1 -0
  150. package/dist/core/pace.d.ts +4 -0
  151. package/dist/core/pace.js +21 -0
  152. package/dist/core/pace.js.map +1 -0
  153. package/dist/core/session.d.ts +55 -0
  154. package/dist/core/session.js +157 -0
  155. package/dist/core/session.js.map +1 -0
  156. package/dist/core/site.d.ts +8 -0
  157. package/dist/core/site.js +36 -0
  158. package/dist/core/site.js.map +1 -0
  159. package/dist/core/text.d.ts +21 -0
  160. package/dist/core/text.js +113 -0
  161. package/dist/core/text.js.map +1 -0
  162. package/dist/home.d.ts +19 -0
  163. package/dist/home.js +54 -0
  164. package/dist/home.js.map +1 -0
  165. package/dist/index.d.ts +40 -0
  166. package/dist/index.js +51 -0
  167. package/dist/index.js.map +1 -0
  168. package/dist/internals.d.ts +11 -0
  169. package/dist/internals.js +15 -0
  170. package/dist/internals.js.map +1 -0
  171. package/dist/moodle/activities.d.ts +94 -0
  172. package/dist/moodle/activities.js +240 -0
  173. package/dist/moodle/activities.js.map +1 -0
  174. package/dist/moodle/ajax.d.ts +22 -0
  175. package/dist/moodle/ajax.js +91 -0
  176. package/dist/moodle/ajax.js.map +1 -0
  177. package/dist/moodle/assign.d.ts +65 -0
  178. package/dist/moodle/assign.js +162 -0
  179. package/dist/moodle/assign.js.map +1 -0
  180. package/dist/moodle/calendar.d.ts +41 -0
  181. package/dist/moodle/calendar.js +80 -0
  182. package/dist/moodle/calendar.js.map +1 -0
  183. package/dist/moodle/choice.d.ts +33 -0
  184. package/dist/moodle/choice.js +100 -0
  185. package/dist/moodle/choice.js.map +1 -0
  186. package/dist/moodle/completion.d.ts +48 -0
  187. package/dist/moodle/completion.js +108 -0
  188. package/dist/moodle/completion.js.map +1 -0
  189. package/dist/moodle/content.d.ts +43 -0
  190. package/dist/moodle/content.js +105 -0
  191. package/dist/moodle/content.js.map +1 -0
  192. package/dist/moodle/course.d.ts +51 -0
  193. package/dist/moodle/course.js +45 -0
  194. package/dist/moodle/course.js.map +1 -0
  195. package/dist/moodle/courses.d.ts +57 -0
  196. package/dist/moodle/courses.js +70 -0
  197. package/dist/moodle/courses.js.map +1 -0
  198. package/dist/moodle/editor.d.ts +18 -0
  199. package/dist/moodle/editor.js +50 -0
  200. package/dist/moodle/editor.js.map +1 -0
  201. package/dist/moodle/files.d.ts +16 -0
  202. package/dist/moodle/files.js +57 -0
  203. package/dist/moodle/files.js.map +1 -0
  204. package/dist/moodle/forms.d.ts +44 -0
  205. package/dist/moodle/forms.js +199 -0
  206. package/dist/moodle/forms.js.map +1 -0
  207. package/dist/moodle/forum.d.ts +55 -0
  208. package/dist/moodle/forum.js +115 -0
  209. package/dist/moodle/forum.js.map +1 -0
  210. package/dist/moodle/forumPosts.d.ts +83 -0
  211. package/dist/moodle/forumPosts.js +113 -0
  212. package/dist/moodle/forumPosts.js.map +1 -0
  213. package/dist/moodle/gradebook.d.ts +102 -0
  214. package/dist/moodle/gradebook.js +246 -0
  215. package/dist/moodle/gradebook.js.map +1 -0
  216. package/dist/moodle/grades.d.ts +24 -0
  217. package/dist/moodle/grades.js +67 -0
  218. package/dist/moodle/grades.js.map +1 -0
  219. package/dist/moodle/grading.d.ts +92 -0
  220. package/dist/moodle/grading.js +161 -0
  221. package/dist/moodle/grading.js.map +1 -0
  222. package/dist/moodle/h5p.d.ts +31 -0
  223. package/dist/moodle/h5p.js +100 -0
  224. package/dist/moodle/h5p.js.map +1 -0
  225. package/dist/moodle/inPage.d.ts +8 -0
  226. package/dist/moodle/inPage.js +10 -0
  227. package/dist/moodle/inPage.js.map +1 -0
  228. package/dist/moodle/lesson.d.ts +57 -0
  229. package/dist/moodle/lesson.js +140 -0
  230. package/dist/moodle/lesson.js.map +1 -0
  231. package/dist/moodle/login.d.ts +37 -0
  232. package/dist/moodle/login.js +77 -0
  233. package/dist/moodle/login.js.map +1 -0
  234. package/dist/moodle/messages.d.ts +66 -0
  235. package/dist/moodle/messages.js +80 -0
  236. package/dist/moodle/messages.js.map +1 -0
  237. package/dist/moodle/overview.d.ts +83 -0
  238. package/dist/moodle/overview.js +137 -0
  239. package/dist/moodle/overview.js.map +1 -0
  240. package/dist/moodle/pace.d.ts +4 -0
  241. package/dist/moodle/pace.js +19 -0
  242. package/dist/moodle/pace.js.map +1 -0
  243. package/dist/moodle/participants.d.ts +55 -0
  244. package/dist/moodle/participants.js +131 -0
  245. package/dist/moodle/participants.js.map +1 -0
  246. package/dist/moodle/questionBank.d.ts +81 -0
  247. package/dist/moodle/questionBank.js +235 -0
  248. package/dist/moodle/questionBank.js.map +1 -0
  249. package/dist/moodle/quiz.d.ts +117 -0
  250. package/dist/moodle/quiz.js +291 -0
  251. package/dist/moodle/quiz.js.map +1 -0
  252. package/dist/moodle/quizResults.d.ts +36 -0
  253. package/dist/moodle/quizResults.js +93 -0
  254. package/dist/moodle/quizResults.js.map +1 -0
  255. package/dist/moodle/quizStructure.d.ts +42 -0
  256. package/dist/moodle/quizStructure.js +100 -0
  257. package/dist/moodle/quizStructure.js.map +1 -0
  258. package/dist/moodle/session.d.ts +39 -0
  259. package/dist/moodle/session.js +99 -0
  260. package/dist/moodle/session.js.map +1 -0
  261. package/dist/moodle/site.d.ts +8 -0
  262. package/dist/moodle/site.js +36 -0
  263. package/dist/moodle/site.js.map +1 -0
  264. package/dist/moodle/siteInfo.d.ts +25 -0
  265. package/dist/moodle/siteInfo.js +45 -0
  266. package/dist/moodle/siteInfo.js.map +1 -0
  267. package/dist/moodle/structure.d.ts +94 -0
  268. package/dist/moodle/structure.js +103 -0
  269. package/dist/moodle/structure.js.map +1 -0
  270. package/dist/moodle/submission.d.ts +48 -0
  271. package/dist/moodle/submission.js +109 -0
  272. package/dist/moodle/submission.js.map +1 -0
  273. package/dist/moodle/survey.d.ts +47 -0
  274. package/dist/moodle/survey.js +197 -0
  275. package/dist/moodle/survey.js.map +1 -0
  276. package/dist/moodle/text.d.ts +21 -0
  277. package/dist/moodle/text.js +113 -0
  278. package/dist/moodle/text.js.map +1 -0
  279. package/dist/moodle/user.d.ts +54 -0
  280. package/dist/moodle/user.js +78 -0
  281. package/dist/moodle/user.js.map +1 -0
  282. package/dist/options.d.ts +30 -0
  283. package/dist/options.js +35 -0
  284. package/dist/options.js.map +1 -0
  285. package/dist/sessionCookies.d.ts +11 -0
  286. package/dist/sessionCookies.js +31 -0
  287. package/dist/sessionCookies.js.map +1 -0
  288. package/dist/testing/fakeMoodle.d.ts +55 -0
  289. package/dist/testing/fakeMoodle.js +103 -0
  290. package/dist/testing/fakeMoodle.js.map +1 -0
  291. package/dist/testing/gradingPanel.d.ts +6 -0
  292. package/dist/testing/gradingPanel.js +22 -0
  293. package/dist/testing/gradingPanel.js.map +1 -0
  294. package/dist/testing/index.d.ts +5 -0
  295. package/dist/testing/index.js +18 -0
  296. package/dist/testing/index.js.map +1 -0
  297. package/package.json +77 -4
@@ -0,0 +1,145 @@
1
+ import { setFields } from "../../core/forms.js";
2
+ import { inPage } from "../../core/inPage.js";
3
+ import { pause, readingSeconds } from "../../core/pace.js";
4
+ import { openSitePage } from "../../core/session.js";
5
+ import { MoodleError } from "../../core/errors.js";
6
+ /** The lesson isn't there, or the page can't take that answer. */
7
+ export class LessonError extends MoodleError {
8
+ name = "LessonError";
9
+ constructor(message) {
10
+ super(message, "lesson");
11
+ }
12
+ }
13
+ /** The lesson page on screen, read by its forms and blocks. */
14
+ export function readLessonPage(page) {
15
+ return inPage(page, () => {
16
+ const doc = globalThis.document;
17
+ const main = doc.querySelector('#region-main [role="main"]') ?? doc.querySelector("#region-main");
18
+ const text = (el) => (el?.innerText ?? "").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
19
+ const title = text(main?.querySelector("h2")) || undefined;
20
+ const question = main?.querySelector('form.mform[action*="/mod/lesson/continue.php"]');
21
+ const pageIdOf = (form) => Number(form?.querySelector('input[name="pageid"]')?.getAttribute("value")) || undefined;
22
+ if (question) {
23
+ const fields = [];
24
+ for (const input of Array.from(question.querySelectorAll("input:not([type=hidden]), select, textarea"))) {
25
+ if (!input.name || input.type === "submit")
26
+ continue;
27
+ const label = text(input.closest("label")) || undefined;
28
+ if (input.type === "radio") {
29
+ let field = fields.find((f) => f.name === input.name);
30
+ if (!field) {
31
+ field = { name: input.name, kind: "choice", options: [] };
32
+ fields.push(field);
33
+ }
34
+ field.options.push({ value: input.value, label: label ?? input.value });
35
+ }
36
+ else if (input.type === "checkbox")
37
+ fields.push({ name: input.name, kind: "checkbox", ...(label ? { label } : {}) });
38
+ else if (input.tagName === "SELECT") {
39
+ fields.push({ name: input.name, kind: "select", ...(label ? { label } : {}), options: Array.from(input.options).map((o) => ({ value: o.value, label: o.text.trim() })) });
40
+ }
41
+ else
42
+ fields.push({ name: input.name, kind: "text", ...(label ? { label } : {}) });
43
+ }
44
+ return { kind: "question", pageId: pageIdOf(question), ...(title ? { title } : {}), text: text(question.querySelector(".contents")), fields };
45
+ }
46
+ const buttons = Array.from(main?.querySelectorAll('.branchbuttoncontainer form[action*="/mod/lesson/continue.php"]') ?? []);
47
+ if (buttons.length > 0) {
48
+ return {
49
+ kind: "content",
50
+ pageId: pageIdOf(buttons[0]),
51
+ ...(title ? { title } : {}),
52
+ text: text(main?.querySelector(".contents")),
53
+ buttons: buttons.map((form) => ({ label: text(form.querySelector("button, [type=submit]")), jumpTo: Number(form.querySelector('input[name="jumpto"]')?.getAttribute("value")) })),
54
+ };
55
+ }
56
+ const next = main?.querySelector('form[action*="/mod/lesson/view.php"]');
57
+ if (next) {
58
+ const response = main?.querySelector(".response");
59
+ const correct = response ? (/\bcorrect\b/.test(response.className) ? true : /\bincorrect\b/.test(response.className) ? false : undefined) : undefined;
60
+ return {
61
+ kind: "feedback",
62
+ pageId: pageIdOf(next),
63
+ text: text(main).replace(text(next), "").trim(),
64
+ feedback: { ...(correct !== undefined ? { correct } : {}), ...(response ? { response: text(response).replace(/^[^:\n]*:\s*/, "") } : {}) },
65
+ };
66
+ }
67
+ return { kind: "end", ...(title ? { title } : {}), text: text(main) };
68
+ }, null);
69
+ }
70
+ /** Opens the lesson where the person is: Moodle asks whether to go on from the last page seen, and they do. */
71
+ async function openLesson(context, siteUrl, activityId) {
72
+ const page = await openSitePage(context, siteUrl, `mod/lesson/view.php?id=${activityId}`);
73
+ try {
74
+ if ((await page.locator("body#page-mod-lesson-view").count()) === 0) {
75
+ throw new LessonError(`Activity ${activityId} isn't a lesson you can open. Take the id of a lesson (module "lesson") from the course structure.`);
76
+ }
77
+ const resume = page.locator('#region-main a[href*="startlastseen=yes"]');
78
+ if ((await resume.count()) > 0)
79
+ await page.goto((await resume.first().getAttribute("href")), { waitUntil: "domcontentloaded" });
80
+ return page;
81
+ }
82
+ catch (error) {
83
+ await page.close().catch(() => undefined);
84
+ throw error;
85
+ }
86
+ }
87
+ /** The lesson page the person is on (starting the lesson the first time). */
88
+ export async function lessonPage(context, siteUrl, activityId) {
89
+ const page = await openLesson(context, siteUrl, activityId);
90
+ try {
91
+ return await readLessonPage(page);
92
+ }
93
+ finally {
94
+ await page.close().catch(() => undefined);
95
+ }
96
+ }
97
+ /**
98
+ * Answers the page the person is on: a content page's button (its label or position), or a
99
+ * question's fields by name. Returns Moodle's feedback when it gives one, and the page that follows,
100
+ * going on past the feedback as its "Continue" does; reaching the end ends the attempt. With the
101
+ * realistic trail it first takes the time a reader needs for the page (ADR-005).
102
+ */
103
+ export async function answerLessonPage(context, siteUrl, activityId, answer, { trail = "realistic", onProgress } = {}) {
104
+ const page = await openLesson(context, siteUrl, activityId);
105
+ try {
106
+ const current = await readLessonPage(page);
107
+ // What the answer can't be is told before the reading pause, not after it.
108
+ const buttons = current.buttons ?? [];
109
+ const index = typeof answer.button === "number" ? answer.button : buttons.findIndex((b) => b.label === answer.button);
110
+ if (current.kind === "content" && (answer.button === undefined || index < 0 || index >= buttons.length)) {
111
+ throw new LessonError(`This content page's buttons are: ${buttons.map((b, i) => `${i}: "${b.label}"`).join(", ")}. Give one by label or position.`);
112
+ }
113
+ if (current.kind === "question" && (!answer.responses || Object.keys(answer.responses).length === 0)) {
114
+ throw new LessonError("This page is a question: answer it with its fields' values (responses).");
115
+ }
116
+ if (current.kind === "end" || current.kind === "feedback") {
117
+ throw new LessonError(current.kind === "end" ? "The lesson has ended: there's nothing more to answer." : "This page only goes on: answer the page it leads to.");
118
+ }
119
+ if (trail === "realistic") {
120
+ const seconds = readingSeconds([current.text, ...(current.fields ?? []).flatMap((f) => [f.label ?? "", ...(f.options ?? []).map((o) => o.label)])].join(" "));
121
+ await pause(seconds, (elapsed) => onProgress?.(elapsed, seconds));
122
+ }
123
+ if (current.kind === "content") {
124
+ await Promise.all([page.waitForNavigation({ waitUntil: "domcontentloaded" }), page.locator('.branchbuttoncontainer form[action*="/mod/lesson/continue.php"] [type=submit], .branchbuttoncontainer form button').nth(index).click()]);
125
+ }
126
+ else {
127
+ const form = 'form.mform[action*="/mod/lesson/continue.php"]';
128
+ await setFields(page, answer.responses, form);
129
+ await Promise.all([page.waitForNavigation({ waitUntil: "domcontentloaded" }), page.click(`${form} [name="submitbutton"]`)]);
130
+ }
131
+ let next = await readLessonPage(page);
132
+ let feedback;
133
+ if (next.kind === "feedback") {
134
+ feedback = next;
135
+ await Promise.all([page.waitForNavigation({ waitUntil: "domcontentloaded" }), page.click('#region-main form[action*="/mod/lesson/view.php"] [type=submit], #region-main form[action*="/mod/lesson/view.php"] button')]);
136
+ next = await readLessonPage(page);
137
+ }
138
+ const changed = current.kind === "content" ? `Went on from lesson ${activityId}'s page with "${buttons[index].label}".` : `Answered lesson ${activityId}'s question.`;
139
+ return { changed, ...(feedback ? { feedback } : {}), page: next };
140
+ }
141
+ finally {
142
+ await page.close().catch(() => undefined);
143
+ }
144
+ }
145
+ //# sourceMappingURL=lessons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lessons.js","sourceRoot":"","sources":["../../../src/areas/lessons/lessons.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { BrowserContext } from "playwright-core";
2
+ import { MoodleError } from "../../core/errors.js";
3
+ /** Moodle didn't deliver a message. */
4
+ export declare class MessageError extends MoodleError {
5
+ name: string;
6
+ constructor(message: string);
7
+ }
8
+ export interface Notification {
9
+ id: number;
10
+ subject: string;
11
+ /** The notification's text, plain. */
12
+ text: string;
13
+ /** Who it's from: a user id, or a negative one for the site itself (noreply, support). */
14
+ fromUserId: number;
15
+ time?: string;
16
+ read: boolean;
17
+ /** Where it leads (the post, the assignment…). */
18
+ url?: string;
19
+ /** What sent it: "mod_forum", "mod_assign"… and its kind. */
20
+ component?: string;
21
+ eventType?: string;
22
+ }
23
+ /** The person's notifications, newest first, only the unread ones when asked. */
24
+ export declare function notifications(context: BrowserContext, siteUrl: string, { unreadOnly }?: {
25
+ unreadOnly?: boolean;
26
+ }): Promise<Notification[]>;
27
+ /** Marks all the person's notifications read, as the popover's "Mark all as read" does; doing it twice changes nothing. */
28
+ export declare function markNotificationsRead(context: BrowserContext, siteUrl: string): Promise<{
29
+ changed: string;
30
+ }>;
31
+ export interface ConversationSummary {
32
+ id: number;
33
+ type: "private" | "group" | "self";
34
+ /** A group conversation's name (its course group). */
35
+ name?: string;
36
+ members: {
37
+ userId: number;
38
+ fullName: string;
39
+ }[];
40
+ unread: number;
41
+ lastMessage?: {
42
+ fromUserId: number;
43
+ text: string;
44
+ time?: string;
45
+ };
46
+ }
47
+ /** The person's conversations, latest first, with their last message as text. */
48
+ export declare function conversations(context: BrowserContext, siteUrl: string): Promise<ConversationSummary[]>;
49
+ /** A conversation's members and messages, oldest first, as text. */
50
+ export declare function conversation(context: BrowserContext, siteUrl: string, conversationId: number): Promise<{
51
+ id: number;
52
+ members: {
53
+ userId: number;
54
+ fullName: string;
55
+ }[];
56
+ messages: {
57
+ id: number;
58
+ fromUserId: number;
59
+ text: string;
60
+ time: string | undefined;
61
+ }[];
62
+ }>;
63
+ /** Sends a private message to a user, as the messaging drawer does; returns the message and its conversation. */
64
+ export declare function sendMessage(context: BrowserContext, siteUrl: string, userId: number, text: string): Promise<{
65
+ changed: string;
66
+ messageId: number;
67
+ conversationId?: number;
68
+ }>;
@@ -0,0 +1,84 @@
1
+ import { callService, readPageConfig } from "../../core/ajax.js";
2
+ import { isoDate } from "../courses/list.js";
3
+ import { htmlToTexts } from "../forums/discussions.js";
4
+ import { decodeEntities } from "../../core/text.js";
5
+ import { MoodleError } from "../../core/errors.js";
6
+ /** Moodle didn't deliver a message. */
7
+ export class MessageError extends MoodleError {
8
+ name = "MessageError";
9
+ constructor(message) {
10
+ super(message, "message");
11
+ }
12
+ }
13
+ /** The person's notifications, newest first, only the unread ones when asked. */
14
+ export async function notifications(context, siteUrl, { unreadOnly = false } = {}) {
15
+ const { notifications: found } = await callService(context, siteUrl, "message_popup_get_popup_notifications", { useridto: 0, limit: 0, offset: 0 });
16
+ return found
17
+ .filter((n) => !unreadOnly || !n.read)
18
+ .map((n) => ({
19
+ id: n.id,
20
+ subject: decodeEntities(n.subject),
21
+ text: decodeEntities(n.fullmessage || n.smallmessage).trim(),
22
+ fromUserId: n.useridfrom,
23
+ time: isoDate(n.timecreated),
24
+ read: n.read,
25
+ ...(n.contexturl ? { url: n.contexturl } : {}),
26
+ ...(n.component ? { component: n.component } : {}),
27
+ ...(n.eventtype ? { eventType: n.eventtype } : {}),
28
+ }));
29
+ }
30
+ /** Marks all the person's notifications read, as the popover's "Mark all as read" does; doing it twice changes nothing. */
31
+ export async function markNotificationsRead(context, siteUrl) {
32
+ const { userId } = await readPageConfig(context, siteUrl);
33
+ await callService(context, siteUrl, "core_message_mark_all_notifications_as_read", { useridto: userId });
34
+ return { changed: "All notifications marked read." };
35
+ }
36
+ /** A conversation's types, by Moodle's MESSAGE_CONVERSATION_TYPE_* values. */
37
+ const CONVERSATION_TYPES = { 1: "private", 2: "group", 3: "self" };
38
+ /** The person's conversations, latest first, with their last message as text. */
39
+ export async function conversations(context, siteUrl) {
40
+ const { userId } = await readPageConfig(context, siteUrl);
41
+ const { conversations: found } = await callService(context, siteUrl, "core_message_get_conversations", { userid: userId, limitfrom: 0, limitnum: 0 });
42
+ const texts = await htmlToTexts(context, found.map((c) => c.messages[0]?.text ?? ""));
43
+ return found.map((c, i) => {
44
+ const last = c.messages[0];
45
+ return {
46
+ id: c.id,
47
+ type: CONVERSATION_TYPES[c.type] ?? "private",
48
+ ...(c.name ? { name: decodeEntities(c.name) } : {}),
49
+ members: c.members.map((m) => ({ userId: m.id, fullName: m.fullname })),
50
+ unread: c.unreadcount ?? 0,
51
+ ...(last ? { lastMessage: { fromUserId: last.useridfrom, text: texts[i] ?? "", time: isoDate(last.timecreated) } } : {}),
52
+ };
53
+ });
54
+ }
55
+ /** A conversation's members and messages, oldest first, as text. */
56
+ export async function conversation(context, siteUrl, conversationId) {
57
+ const { userId } = await readPageConfig(context, siteUrl);
58
+ const found = await callService(context, siteUrl, "core_message_get_conversation_messages", {
59
+ currentuserid: userId,
60
+ convid: conversationId,
61
+ limitfrom: 0,
62
+ limitnum: 0,
63
+ newest: false,
64
+ });
65
+ const texts = await htmlToTexts(context, found.messages.map((m) => m.text));
66
+ return {
67
+ id: found.id,
68
+ members: found.members.map((m) => ({ userId: m.id, fullName: m.fullname })),
69
+ messages: found.messages.map((m, i) => ({ id: m.id, fromUserId: m.useridfrom, text: texts[i] ?? "", time: isoDate(m.timecreated) })),
70
+ };
71
+ }
72
+ /** Sends a private message to a user, as the messaging drawer does; returns the message and its conversation. */
73
+ export async function sendMessage(context, siteUrl, userId, text) {
74
+ if (text.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").trim() === "")
75
+ throw new MessageError("The message is empty: write something before sending it.");
76
+ const [sent] = await callService(context, siteUrl, "core_message_send_instant_messages", {
77
+ messages: [{ touserid: userId, text, textformat: 1 }],
78
+ });
79
+ // A refused message comes back with id -1 and Moodle's reason (blocked, not a contact…).
80
+ if (!sent || sent.msgid < 0)
81
+ throw new MessageError(`Moodle didn't send the message to user ${userId}: ${sent?.errormessage ?? "no reason given"}`);
82
+ return { changed: `Message ${sent.msgid} sent to user ${userId}.`, messageId: sent.msgid, ...(sent.conversationid ? { conversationId: sent.conversationid } : {}) };
83
+ }
84
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/areas/messages/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { BrowserContext, Page } from "playwright-core";
2
+ import { MoodleError } from "../../core/errors.js";
3
+ export interface Participant {
4
+ userId: number;
5
+ fullName: string;
6
+ roles: {
7
+ id: number;
8
+ name: string;
9
+ }[];
10
+ groups: {
11
+ id: number;
12
+ name: string;
13
+ }[];
14
+ }
15
+ export interface CourseGroup {
16
+ id: number;
17
+ name: string;
18
+ /** The members' user ids. */
19
+ members: number[];
20
+ }
21
+ /** The person can't see the course's participants. */
22
+ export declare class NoParticipantsError extends MoodleError {
23
+ name: string;
24
+ constructor(message: string);
25
+ }
26
+ /** The rows of a participants table fragment: user id and name, read by link and markup, in any language. */
27
+ export declare function readParticipantRows(page: Page): Promise<{
28
+ userId: number;
29
+ fullName: string;
30
+ }[]>;
31
+ /**
32
+ * The roles and groups the participants page filters by in a course (its filters' options), as the
33
+ * person may use them: every group, or only their own in a course with separate groups. "No roles"
34
+ * and "no group" left out.
35
+ */
36
+ export declare function participantFilters(context: BrowserContext, siteUrl: string, courseId: number): Promise<{
37
+ roles: {
38
+ id: number;
39
+ name: string;
40
+ }[];
41
+ groups: {
42
+ id: number;
43
+ name: string;
44
+ }[];
45
+ }>;
46
+ /** A course's groups the person can see, with their members' user ids. */
47
+ export declare function groups(context: BrowserContext, siteUrl: string, courseId: number): Promise<CourseGroup[]>;
48
+ /**
49
+ * A course's participants with their roles and groups, as the participants page lists them: only
50
+ * those with a role (its id), in a group, or (`inactiveSince`) who haven't accessed the course since
51
+ * that date, when asked. Each role and group is a filter of its own, so ids come in any language.
52
+ */
53
+ export declare function participants(context: BrowserContext, siteUrl: string, courseId: number, { role, group, inactiveSince }?: {
54
+ role?: number;
55
+ group?: number;
56
+ inactiveSince?: Date;
57
+ }): Promise<Participant[]>;
@@ -0,0 +1,135 @@
1
+ import { callService } from "../../core/ajax.js";
2
+ import { openSitePage } from "../../core/session.js";
3
+ import { MoodleError } from "../../core/errors.js";
4
+ /** The person can't see the course's participants. */
5
+ export class NoParticipantsError extends MoodleError {
6
+ name = "NoParticipantsError";
7
+ constructor(message) {
8
+ super(message, "no-participants");
9
+ }
10
+ }
11
+ // The table's join types (core\output\datafilter): a filter's values match any; filters all apply.
12
+ const JOIN_ANY = 1;
13
+ const JOIN_ALL = 2;
14
+ const PAGE_SIZE = 100;
15
+ const MAX_PAGES = 100;
16
+ /** The rows of a participants table fragment: user id and name, read by link and markup, in any language. */
17
+ export function readParticipantRows(page) {
18
+ return page.evaluate(() => {
19
+ const doc = globalThis.document;
20
+ // Moodle pads a page with empty rows up to its size.
21
+ return Array.from(doc.querySelectorAll("tbody tr:not(.emptyrow)")).flatMap((row) => {
22
+ const link = row.querySelector('a[href*="/user/view.php?id="]');
23
+ const userId = Number(new URL(link?.getAttribute("href") ?? "", "http://x/").searchParams.get("id"));
24
+ if (!link || !userId)
25
+ return [];
26
+ // The link holds the picture or initials before the name.
27
+ const copy = link.cloneNode(true);
28
+ for (const extra of Array.from(copy.querySelectorAll(".userinitials, img, .visually-hidden")))
29
+ extra.remove();
30
+ return [{ userId, fullName: (copy.textContent ?? "").replace(/\s+/g, " ").trim() }];
31
+ });
32
+ });
33
+ }
34
+ /** Every participant matching `filters`, page by page, parsed in `parser` (a blank tab). */
35
+ async function fetchAll(context, siteUrl, parser, courseId, filters) {
36
+ const all = new Map();
37
+ for (let pagenumber = 1; pagenumber <= MAX_PAGES; pagenumber++) {
38
+ const { html } = await callService(context, siteUrl, "core_table_get_dynamic_table_content", {
39
+ component: "core_user",
40
+ handler: "participants",
41
+ uniqueid: `user-index-participants-${courseId}`,
42
+ sortdata: [{ sortby: "lastname", sortorder: 4 }],
43
+ // Integer filters take numbers: a string id fails (confirmed empirically, Moodle 5.2).
44
+ filters: [{ name: "courseid", jointype: JOIN_ANY, values: [courseId] }, ...filters],
45
+ jointype: JOIN_ALL,
46
+ firstinitial: "",
47
+ lastinitial: "",
48
+ pagenumber,
49
+ pagesize: PAGE_SIZE,
50
+ hiddencolumns: [],
51
+ resetpreferences: false,
52
+ });
53
+ await parser.setContent(html);
54
+ const rows = await readParticipantRows(parser);
55
+ for (const row of rows)
56
+ all.set(row.userId, row);
57
+ if (rows.length < PAGE_SIZE)
58
+ break;
59
+ }
60
+ return Array.from(all.values());
61
+ }
62
+ /**
63
+ * The roles and groups the participants page filters by in a course (its filters' options), as the
64
+ * person may use them: every group, or only their own in a course with separate groups. "No roles"
65
+ * and "no group" left out.
66
+ */
67
+ export async function participantFilters(context, siteUrl, courseId) {
68
+ const page = await openSitePage(context, siteUrl, `user/index.php?id=${courseId}`);
69
+ try {
70
+ if ((await page.locator("body#page-course-view-participants").count()) === 0) {
71
+ throw new NoParticipantsError(`You can't see the participants of course ${courseId}: it doesn't exist, or you aren't allowed.`);
72
+ }
73
+ return await page.evaluate(() => {
74
+ const doc = globalThis.document;
75
+ // Anonymous callbacks only: see the conventions on page.evaluate.
76
+ const [roles, groups] = ["roles", "groups"].map((field) => Array.from(doc.querySelectorAll(`select[data-field-name="${field}"] option`))
77
+ .map((option) => ({ id: Number(option.value), name: (option.textContent ?? "").trim() }))
78
+ .filter((option) => option.id > 0));
79
+ return { roles: roles ?? [], groups: groups ?? [] };
80
+ });
81
+ }
82
+ finally {
83
+ await page.close().catch(() => undefined);
84
+ }
85
+ }
86
+ /** A course's groups the person can see, with their members' user ids. */
87
+ export async function groups(context, siteUrl, courseId) {
88
+ const { groups: found } = await participantFilters(context, siteUrl, courseId);
89
+ const parser = await context.newPage();
90
+ try {
91
+ const result = [];
92
+ for (const group of found) {
93
+ const members = await fetchAll(context, siteUrl, parser, courseId, [{ name: "groups", jointype: JOIN_ANY, values: [group.id] }]);
94
+ result.push({ ...group, members: members.map((m) => m.userId) });
95
+ }
96
+ return result;
97
+ }
98
+ finally {
99
+ await parser.close().catch(() => undefined);
100
+ }
101
+ }
102
+ /**
103
+ * A course's participants with their roles and groups, as the participants page lists them: only
104
+ * those with a role (its id), in a group, or (`inactiveSince`) who haven't accessed the course since
105
+ * that date, when asked. Each role and group is a filter of its own, so ids come in any language.
106
+ */
107
+ export async function participants(context, siteUrl, courseId, { role, group, inactiveSince } = {}) {
108
+ const { roles, groups: courseGroupList } = await participantFilters(context, siteUrl, courseId);
109
+ const filters = [];
110
+ if (role !== undefined)
111
+ filters.push({ name: "roles", jointype: JOIN_ANY, values: [role] });
112
+ if (group !== undefined)
113
+ filters.push({ name: "groups", jointype: JOIN_ANY, values: [group] });
114
+ // "Inactive for more than": no access to the course since this time (the filter's "any" join).
115
+ if (inactiveSince !== undefined)
116
+ filters.push({ name: "accesssince", jointype: JOIN_ANY, values: [Math.floor(inactiveSince.getTime() / 1000)] });
117
+ const parser = await context.newPage();
118
+ try {
119
+ const people = await fetchAll(context, siteUrl, parser, courseId, filters);
120
+ const byUser = new Map(people.map((p) => [p.userId, { ...p, roles: [], groups: [] }]));
121
+ for (const r of roles) {
122
+ for (const member of await fetchAll(context, siteUrl, parser, courseId, [{ name: "roles", jointype: JOIN_ANY, values: [r.id] }]))
123
+ byUser.get(member.userId)?.roles.push(r);
124
+ }
125
+ for (const g of courseGroupList) {
126
+ for (const member of await fetchAll(context, siteUrl, parser, courseId, [{ name: "groups", jointype: JOIN_ANY, values: [g.id] }]))
127
+ byUser.get(member.userId)?.groups.push(g);
128
+ }
129
+ return Array.from(byUser.values());
130
+ }
131
+ finally {
132
+ await parser.close().catch(() => undefined);
133
+ }
134
+ }
135
+ //# sourceMappingURL=participants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"participants.js","sourceRoot":"","sources":["../../../src/areas/participants/participants.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import type { BrowserContext, Page } from "playwright-core";
2
+ import { type FieldValue } from "../../core/forms.js";
3
+ import { MoodleError } from "../../core/errors.js";
4
+ /** The bank isn't there, or Moodle refused the questions. */
5
+ export declare class QuestionBankError extends MoodleError {
6
+ name: string;
7
+ constructor(message: string);
8
+ }
9
+ export interface QuestionBank {
10
+ /** The bank's course module id: a qbank activity's, or the quiz's whose private bank it is. */
11
+ id: number;
12
+ name: string;
13
+ /** The module: "qbank" for a shared bank, "quiz"… for an activity's own. */
14
+ type: string;
15
+ /** Shared banks' questions can be used anywhere in the course; activities' only there. */
16
+ shared: boolean;
17
+ }
18
+ /** A course's question banks: its shared ones and the activities' own, as the banks page lists them. */
19
+ export declare function questionBanks(context: BrowserContext, siteUrl: string, courseId: number): Promise<QuestionBank[]>;
20
+ export interface BankQuestion {
21
+ id: number;
22
+ name: string;
23
+ /** The question type: "multichoice", "truefalse", "shortanswer", "essay"… */
24
+ type: string;
25
+ /** "ready" or "draft". */
26
+ status?: string;
27
+ version?: string;
28
+ categoryId: number;
29
+ }
30
+ export interface BankCategory {
31
+ id: number;
32
+ name: string;
33
+ }
34
+ /** The questions on a bank page: id from their checkbox, type from their icon, name from the in-place editor. */
35
+ export declare function readBankQuestions(page: Page, categoryId: number): Promise<BankQuestion[]>;
36
+ /** A bank's categories and every question in them, category by category. */
37
+ export declare function questions(context: BrowserContext, siteUrl: string, activityId: number): Promise<{
38
+ categories: BankCategory[];
39
+ questions: BankQuestion[];
40
+ }>;
41
+ /**
42
+ * Imports questions into a bank from a GIFT or Moodle XML file (a local path) or text, through the
43
+ * bank's import form; returns the questions created. A file Moodle can't parse comes back as a
44
+ * `QuestionBankError` with its messages, and nothing is imported.
45
+ */
46
+ export declare function importQuestions(context: BrowserContext, siteUrl: string, activityId: number, { format, file, text, categoryId }: {
47
+ format: "gift" | "xml";
48
+ file?: string;
49
+ text?: string;
50
+ categoryId?: number;
51
+ }): Promise<{
52
+ changed: string;
53
+ questions: BankQuestion[];
54
+ }>;
55
+ export declare const QUESTION_TYPES: readonly ["multichoice", "truefalse", "shortanswer", "numerical", "essay"];
56
+ export type QuestionType = (typeof QUESTION_TYPES)[number];
57
+ export interface QuestionInput {
58
+ type: QuestionType;
59
+ name: string;
60
+ /** The question's text (HTML). */
61
+ text: string;
62
+ /** Multichoice, short answer and numerical answers: a fraction of the mark (1 right, 0 wrong, negatives for penalties), the nearest one Moodle offers. */
63
+ answers?: {
64
+ text: string;
65
+ fraction: number;
66
+ feedback?: string;
67
+ tolerance?: number;
68
+ }[];
69
+ /** True/false: the right answer. */
70
+ correct?: boolean;
71
+ categoryId?: number;
72
+ defaultMark?: number;
73
+ /** Any other field of the type's form, by name. */
74
+ fields?: Record<string, FieldValue>;
75
+ }
76
+ /**
77
+ * Creates one question in a bank through its type's edit form; returns its id. Imports are the way
78
+ * for several; this is for one-offs.
79
+ */
80
+ export declare function createQuestion(context: BrowserContext, siteUrl: string, activityId: number, input: QuestionInput): Promise<{
81
+ changed: string;
82
+ questionId?: number;
83
+ }>;