@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.
Files changed (300) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +131 -2
  3. package/dist/areas/activities/build.d.ts +98 -0
  4. package/dist/areas/activities/build.js +248 -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/scorm/scorm.d.ts +60 -0
  73. package/dist/areas/scorm/scorm.js +171 -0
  74. package/dist/areas/scorm/scorm.js.map +1 -0
  75. package/dist/areas/site/info.d.ts +25 -0
  76. package/dist/areas/site/info.js +45 -0
  77. package/dist/areas/site/info.js.map +1 -0
  78. package/dist/areas/site/users.d.ts +56 -0
  79. package/dist/areas/site/users.js +89 -0
  80. package/dist/areas/site/users.js.map +1 -0
  81. package/dist/areas/surveys/choice.d.ts +38 -0
  82. package/dist/areas/surveys/choice.js +105 -0
  83. package/dist/areas/surveys/choice.js.map +1 -0
  84. package/dist/areas/surveys/surveys.d.ts +51 -0
  85. package/dist/areas/surveys/surveys.js +201 -0
  86. package/dist/areas/surveys/surveys.js.map +1 -0
  87. package/dist/browser/chromium.d.ts +15 -0
  88. package/dist/browser/chromium.js +53 -0
  89. package/dist/browser/chromium.js.map +1 -0
  90. package/dist/browser/cookies.d.ts +11 -0
  91. package/dist/browser/cookies.js +31 -0
  92. package/dist/browser/cookies.js.map +1 -0
  93. package/dist/browser/manager.d.ts +57 -0
  94. package/dist/browser/manager.js +177 -0
  95. package/dist/browser/manager.js.map +1 -0
  96. package/dist/browser.d.ts +53 -0
  97. package/dist/browser.js +167 -0
  98. package/dist/browser.js.map +1 -0
  99. package/dist/categories.d.ts +28 -0
  100. package/dist/categories.js +225 -0
  101. package/dist/categories.js.map +1 -0
  102. package/dist/chromiumInstaller.d.ts +15 -0
  103. package/dist/chromiumInstaller.js +52 -0
  104. package/dist/chromiumInstaller.js.map +1 -0
  105. package/dist/cli/args.d.ts +33 -0
  106. package/dist/cli/args.js +194 -0
  107. package/dist/cli/args.js.map +1 -0
  108. package/dist/cli/commands.generated.d.ts +4 -0
  109. package/dist/cli/commands.generated.js +2217 -0
  110. package/dist/cli/commands.generated.js.map +1 -0
  111. package/dist/cli/config.d.ts +46 -0
  112. package/dist/cli/config.js +107 -0
  113. package/dist/cli/config.js.map +1 -0
  114. package/dist/cli/main.d.ts +2 -0
  115. package/dist/cli/main.js +19 -0
  116. package/dist/cli/main.js.map +1 -0
  117. package/dist/cli/output.d.ts +4 -0
  118. package/dist/cli/output.js +71 -0
  119. package/dist/cli/output.js.map +1 -0
  120. package/dist/cli/run.d.ts +15 -0
  121. package/dist/cli/run.js +194 -0
  122. package/dist/cli/run.js.map +1 -0
  123. package/dist/cli/spec.d.ts +42 -0
  124. package/dist/cli/spec.js +4 -0
  125. package/dist/cli/spec.js.map +1 -0
  126. package/dist/client.d.ts +224 -0
  127. package/dist/client.js +182 -0
  128. package/dist/client.js.map +1 -0
  129. package/dist/core/ajax.d.ts +25 -0
  130. package/dist/core/ajax.js +85 -0
  131. package/dist/core/ajax.js.map +1 -0
  132. package/dist/core/editor.d.ts +18 -0
  133. package/dist/core/editor.js +51 -0
  134. package/dist/core/editor.js.map +1 -0
  135. package/dist/core/errors.d.ts +12 -0
  136. package/dist/core/errors.js +10 -0
  137. package/dist/core/errors.js.map +1 -0
  138. package/dist/core/files.d.ts +16 -0
  139. package/dist/core/files.js +58 -0
  140. package/dist/core/files.js.map +1 -0
  141. package/dist/core/forms.d.ts +45 -0
  142. package/dist/core/forms.js +200 -0
  143. package/dist/core/forms.js.map +1 -0
  144. package/dist/core/http.d.ts +18 -0
  145. package/dist/core/http.js +22 -0
  146. package/dist/core/http.js.map +1 -0
  147. package/dist/core/inPage.d.ts +8 -0
  148. package/dist/core/inPage.js +10 -0
  149. package/dist/core/inPage.js.map +1 -0
  150. package/dist/core/login.d.ts +40 -0
  151. package/dist/core/login.js +83 -0
  152. package/dist/core/login.js.map +1 -0
  153. package/dist/core/pace.d.ts +4 -0
  154. package/dist/core/pace.js +21 -0
  155. package/dist/core/pace.js.map +1 -0
  156. package/dist/core/session.d.ts +55 -0
  157. package/dist/core/session.js +157 -0
  158. package/dist/core/session.js.map +1 -0
  159. package/dist/core/site.d.ts +8 -0
  160. package/dist/core/site.js +36 -0
  161. package/dist/core/site.js.map +1 -0
  162. package/dist/core/text.d.ts +21 -0
  163. package/dist/core/text.js +113 -0
  164. package/dist/core/text.js.map +1 -0
  165. package/dist/home.d.ts +19 -0
  166. package/dist/home.js +54 -0
  167. package/dist/home.js.map +1 -0
  168. package/dist/index.d.ts +41 -0
  169. package/dist/index.js +52 -0
  170. package/dist/index.js.map +1 -0
  171. package/dist/internals.d.ts +11 -0
  172. package/dist/internals.js +15 -0
  173. package/dist/internals.js.map +1 -0
  174. package/dist/moodle/activities.d.ts +94 -0
  175. package/dist/moodle/activities.js +240 -0
  176. package/dist/moodle/activities.js.map +1 -0
  177. package/dist/moodle/ajax.d.ts +22 -0
  178. package/dist/moodle/ajax.js +91 -0
  179. package/dist/moodle/ajax.js.map +1 -0
  180. package/dist/moodle/assign.d.ts +65 -0
  181. package/dist/moodle/assign.js +162 -0
  182. package/dist/moodle/assign.js.map +1 -0
  183. package/dist/moodle/calendar.d.ts +41 -0
  184. package/dist/moodle/calendar.js +80 -0
  185. package/dist/moodle/calendar.js.map +1 -0
  186. package/dist/moodle/choice.d.ts +33 -0
  187. package/dist/moodle/choice.js +100 -0
  188. package/dist/moodle/choice.js.map +1 -0
  189. package/dist/moodle/completion.d.ts +48 -0
  190. package/dist/moodle/completion.js +108 -0
  191. package/dist/moodle/completion.js.map +1 -0
  192. package/dist/moodle/content.d.ts +43 -0
  193. package/dist/moodle/content.js +105 -0
  194. package/dist/moodle/content.js.map +1 -0
  195. package/dist/moodle/course.d.ts +51 -0
  196. package/dist/moodle/course.js +45 -0
  197. package/dist/moodle/course.js.map +1 -0
  198. package/dist/moodle/courses.d.ts +57 -0
  199. package/dist/moodle/courses.js +70 -0
  200. package/dist/moodle/courses.js.map +1 -0
  201. package/dist/moodle/editor.d.ts +18 -0
  202. package/dist/moodle/editor.js +50 -0
  203. package/dist/moodle/editor.js.map +1 -0
  204. package/dist/moodle/files.d.ts +16 -0
  205. package/dist/moodle/files.js +57 -0
  206. package/dist/moodle/files.js.map +1 -0
  207. package/dist/moodle/forms.d.ts +44 -0
  208. package/dist/moodle/forms.js +199 -0
  209. package/dist/moodle/forms.js.map +1 -0
  210. package/dist/moodle/forum.d.ts +55 -0
  211. package/dist/moodle/forum.js +115 -0
  212. package/dist/moodle/forum.js.map +1 -0
  213. package/dist/moodle/forumPosts.d.ts +83 -0
  214. package/dist/moodle/forumPosts.js +113 -0
  215. package/dist/moodle/forumPosts.js.map +1 -0
  216. package/dist/moodle/gradebook.d.ts +102 -0
  217. package/dist/moodle/gradebook.js +246 -0
  218. package/dist/moodle/gradebook.js.map +1 -0
  219. package/dist/moodle/grades.d.ts +24 -0
  220. package/dist/moodle/grades.js +67 -0
  221. package/dist/moodle/grades.js.map +1 -0
  222. package/dist/moodle/grading.d.ts +92 -0
  223. package/dist/moodle/grading.js +161 -0
  224. package/dist/moodle/grading.js.map +1 -0
  225. package/dist/moodle/h5p.d.ts +31 -0
  226. package/dist/moodle/h5p.js +100 -0
  227. package/dist/moodle/h5p.js.map +1 -0
  228. package/dist/moodle/inPage.d.ts +8 -0
  229. package/dist/moodle/inPage.js +10 -0
  230. package/dist/moodle/inPage.js.map +1 -0
  231. package/dist/moodle/lesson.d.ts +57 -0
  232. package/dist/moodle/lesson.js +140 -0
  233. package/dist/moodle/lesson.js.map +1 -0
  234. package/dist/moodle/login.d.ts +37 -0
  235. package/dist/moodle/login.js +77 -0
  236. package/dist/moodle/login.js.map +1 -0
  237. package/dist/moodle/messages.d.ts +66 -0
  238. package/dist/moodle/messages.js +80 -0
  239. package/dist/moodle/messages.js.map +1 -0
  240. package/dist/moodle/overview.d.ts +83 -0
  241. package/dist/moodle/overview.js +137 -0
  242. package/dist/moodle/overview.js.map +1 -0
  243. package/dist/moodle/pace.d.ts +4 -0
  244. package/dist/moodle/pace.js +19 -0
  245. package/dist/moodle/pace.js.map +1 -0
  246. package/dist/moodle/participants.d.ts +55 -0
  247. package/dist/moodle/participants.js +131 -0
  248. package/dist/moodle/participants.js.map +1 -0
  249. package/dist/moodle/questionBank.d.ts +81 -0
  250. package/dist/moodle/questionBank.js +235 -0
  251. package/dist/moodle/questionBank.js.map +1 -0
  252. package/dist/moodle/quiz.d.ts +117 -0
  253. package/dist/moodle/quiz.js +291 -0
  254. package/dist/moodle/quiz.js.map +1 -0
  255. package/dist/moodle/quizResults.d.ts +36 -0
  256. package/dist/moodle/quizResults.js +93 -0
  257. package/dist/moodle/quizResults.js.map +1 -0
  258. package/dist/moodle/quizStructure.d.ts +42 -0
  259. package/dist/moodle/quizStructure.js +100 -0
  260. package/dist/moodle/quizStructure.js.map +1 -0
  261. package/dist/moodle/session.d.ts +39 -0
  262. package/dist/moodle/session.js +99 -0
  263. package/dist/moodle/session.js.map +1 -0
  264. package/dist/moodle/site.d.ts +8 -0
  265. package/dist/moodle/site.js +36 -0
  266. package/dist/moodle/site.js.map +1 -0
  267. package/dist/moodle/siteInfo.d.ts +25 -0
  268. package/dist/moodle/siteInfo.js +45 -0
  269. package/dist/moodle/siteInfo.js.map +1 -0
  270. package/dist/moodle/structure.d.ts +94 -0
  271. package/dist/moodle/structure.js +103 -0
  272. package/dist/moodle/structure.js.map +1 -0
  273. package/dist/moodle/submission.d.ts +48 -0
  274. package/dist/moodle/submission.js +109 -0
  275. package/dist/moodle/submission.js.map +1 -0
  276. package/dist/moodle/survey.d.ts +47 -0
  277. package/dist/moodle/survey.js +197 -0
  278. package/dist/moodle/survey.js.map +1 -0
  279. package/dist/moodle/text.d.ts +21 -0
  280. package/dist/moodle/text.js +113 -0
  281. package/dist/moodle/text.js.map +1 -0
  282. package/dist/moodle/user.d.ts +54 -0
  283. package/dist/moodle/user.js +78 -0
  284. package/dist/moodle/user.js.map +1 -0
  285. package/dist/options.d.ts +30 -0
  286. package/dist/options.js +35 -0
  287. package/dist/options.js.map +1 -0
  288. package/dist/sessionCookies.d.ts +11 -0
  289. package/dist/sessionCookies.js +31 -0
  290. package/dist/sessionCookies.js.map +1 -0
  291. package/dist/testing/fakeMoodle.d.ts +55 -0
  292. package/dist/testing/fakeMoodle.js +103 -0
  293. package/dist/testing/fakeMoodle.js.map +1 -0
  294. package/dist/testing/gradingPanel.d.ts +6 -0
  295. package/dist/testing/gradingPanel.js +22 -0
  296. package/dist/testing/gradingPanel.js.map +1 -0
  297. package/dist/testing/index.d.ts +5 -0
  298. package/dist/testing/index.js +18 -0
  299. package/dist/testing/index.js.map +1 -0
  300. package/package.json +78 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Falkenslab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,132 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="docs/static/img/logo.svg" alt="moodle-driver logo: a terminal prompt, >m_" width="112"/>
4
+
5
+ # moodle-driver
6
+
7
+ **Everything a person does in Moodle, from your Node.js code.**<br/>
8
+ With their own login: no web services, no tokens, no admin.
9
+
10
+ [![Documentation](https://img.shields.io/badge/docs-falkenslab.github.io-f97316?logo=docusaurus&logoColor=white)](https://falkenslab.github.io/moodle-driver/)
11
+ [![npm](https://img.shields.io/npm/v/@falkenslab/moodle-driver?color=cb3837&logo=npm)](https://www.npmjs.com/package/@falkenslab/moodle-driver)
12
+ ![Node.js 20+](https://img.shields.io/badge/node-20%2B-339933?logo=node.js&logoColor=white)
13
+ ![TypeScript](https://img.shields.io/badge/types-included-3178c6?logo=typescript&logoColor=white)
14
+ ![Moodle 4.x and 5.x](https://img.shields.io/badge/Moodle-4.x%20%7C%205.x-f98012?logo=moodle&logoColor=white)
15
+ [![License: MIT](https://img.shields.io/badge/license-MIT-16a34a)](LICENSE)
16
+
17
+ <img src="docs/static/img/diagrams/at-a-glance.svg" alt="Your program calls moodle-driver, which works Moodle with the person's own login, and gets plain data back" width="900"/>
18
+
19
+ </div>
20
+
21
+ ## ✨ In a few lines
22
+
23
+ ```ts
24
+ import { createMoodle } from "@falkenslab/moodle-driver";
25
+
26
+ await using moodle = createMoodle({ site: "https://campus.example.org/", profileDir: "/home/ada/.moodle-profile", headless: true, downloadBrowser: true });
27
+ await moodle.logIn(); // 🔑 the first time a window opens and the person logs in; afterwards, nothing
28
+
29
+ for (const course of await moodle.listCourses()) {
30
+ const { pending } = await moodle.pendingActivities(course.id);
31
+ console.log(`📚 ${course.fullname}: ${pending.length} things to do`);
32
+ }
33
+ ```
34
+
35
+ That's it: no API keys, nothing to ask the administrator. The browser closes by itself at the end of the block.
36
+
37
+ ## 🧰 What it can do
38
+
39
+ <div align="center">
40
+ <img src="docs/static/img/diagrams/what-it-can-do.svg" alt="As a student: agenda, content, assignments, quizzes, surveys, lessons, grades, forums. As a teacher: build courses, grade with rubrics, extensions and overrides, quiz results, question banks, gradebook, participants, completion, calendar. For your program: typed methods, a moodle command, errors with codes, any AJAX service, data categories, a fake Moodle for tests." width="900"/>
41
+ </div>
42
+
43
+ ## 🚀 A taste
44
+
45
+ **🎒 A student's week**, deadlines first:
46
+
47
+ ```ts
48
+ for (const d of await moodle.deadlines({ days: 7 })) {
49
+ console.log(`${d.date.slice(0, 10)} ${d.activity}${d.overdue ? " ⚠️ overdue" : ""}`);
50
+ }
51
+ ```
52
+
53
+ **🧑‍🏫 A teacher grading** what's waiting, with feedback the student gets notified of:
54
+
55
+ ```ts
56
+ for (const s of await moodle.assignmentSubmissions(35)) {
57
+ if (s.needsGrading) await moodle.gradeSubmission(35, s.userId, { grade: 8.5, feedback: "<p>Great work!</p>", notify: true });
58
+ }
59
+ ```
60
+
61
+ **🏗️ Building a course**: every write tells you what it did:
62
+
63
+ ```ts
64
+ const { changed } = await moodle.createActivity({ courseId: 3, sectionId: 7, type: "page", name: "Loops", content: "<p>for, while…</p>" });
65
+ console.log(changed); // Created page 42 "Loops" in section 2.
66
+ ```
67
+
68
+ More, complete and commented: [📖 use-case examples](https://falkenslab.github.io/moodle-driver/examples).
69
+
70
+ ## 🖥️ Or straight from the terminal
71
+
72
+ Every method is also a command of `moodle`, which comes with the package: no project, no code.
73
+
74
+ ```bash
75
+ npm i -g @falkenslab/moodle-driver
76
+ moodle login --site https://campus.example.org/ # 🔑 once: a window opens, you log in
77
+ moodle list-courses
78
+ moodle deadlines --days 7
79
+ moodle grade-submission 35 7 --grade 8.5 --feedback "<p>Great work!</p>" --notify
80
+ moodle my-grades 3 --json | jq '.[] | select(.grade) | .name'
81
+ ```
82
+
83
+ Tables at a terminal, JSON for scripts, errors with their code and an exit status. `moodle help` lists every command, generated from the API so none is missing; `--allow own,course-content` refuses anything touching other people's data. See [the command line](https://falkenslab.github.io/moodle-driver/getting-started/command-line).
84
+
85
+ ## 💡 Why it's different
86
+
87
+ - 🔑 **The person's own login.** They log in once in a window (SSO and two-factor included); the driver never sees a password.
88
+ - 🚫 **No web services.** Many campuses don't enable them. The driver uses the same pages and AJAX services Moodle's own screens use: the services over plain HTTP with the person's session, the browser to log in, fill forms and read pages.
89
+ - 🔐 **Moodle decides.** It can do what the person can do, and nothing more: every permission is Moodle's.
90
+ - 🌍 **Any language.** Pages are read by structure (ids, data attributes, field names), never by their text.
91
+ - 🐢 **At a person's pace** when acting as a student, so Moodle's logs and completion look like a person's; or 🐇 fast when time matters.
92
+ - 🛡️ **Safe to hand to an agent.** Every function declares the data it touches (own, course content, others' work, communication…), so a program can allow only some.
93
+ - 📦 **Plain data, typed.** JSON results, ISO dates, errors with stable codes, TypeScript types included.
94
+ - 🖥️ **A library and a command.** The same operations from your code or from a terminal.
95
+
96
+ ## 📦 Install
97
+
98
+ ```bash
99
+ npm install @falkenslab/moodle-driver playwright-core # the library, in your project
100
+ npm i -g @falkenslab/moodle-driver # the moodle command, everywhere
101
+ ```
102
+
103
+ You need Node.js 20+, a Moodle 4.x or 5.x site, and Edge or Chrome (or let it download Chromium). See [installation](https://falkenslab.github.io/moodle-driver/getting-started/installation).
104
+
105
+ ## 📚 Documentation
106
+
107
+ | | |
108
+ | --- | --- |
109
+ | 🏁 [Getting started](https://falkenslab.github.io/moodle-driver/getting-started/installation) | Install, log in, your first program |
110
+ | 💡 [Concepts](https://falkenslab.github.io/moodle-driver/concepts/how-it-works) | How it works, the browser, errors, pace, data categories |
111
+ | 🖥️ [Command line](https://falkenslab.github.io/moodle-driver/getting-started/command-line) | The `moodle` command: log in, run any operation, script it |
112
+ | 🗺️ [Moodle areas](https://falkenslab.github.io/moodle-driver/areas/courses) | Every function, by area: courses, quizzes, grading, forums… |
113
+ | 📖 [Examples](https://falkenslab.github.io/moodle-driver/examples) | Complete programs for real tasks, commented |
114
+ | 📘 [Glossary](https://falkenslab.github.io/moodle-driver/reference/glossary) | Moodle's terms, explained for programmers |
115
+ | ⌨️ [Commands](https://falkenslab.github.io/moodle-driver/reference/commands) | Every command, its arguments and options |
116
+ | 🔎 [API reference](https://falkenslab.github.io/moodle-driver/api) | Generated from the code |
117
+
118
+ ## 🛠️ Development
119
+
120
+ ```bash
121
+ npm install
122
+ npm run typecheck && npm run lint
123
+ npm run test:docker # the tests open browsers: always in Docker, never on the desktop
124
+ npm run build # dist/, with declarations
125
+ npm run docs:check # every example in the docs compiles against the code
126
+ ```
127
+
128
+ The design lives in [`.minispec/`](.minispec/) (start with `core/project.md` and the decisions). Extracted from moodle-mcp, its first consumer.
129
+
130
+ ## 📄 License
131
+
132
+ MIT © Falkenslab
@@ -0,0 +1,98 @@
1
+ import type { BrowserContext, Page } from "playwright-core";
2
+ import { type FieldValue } from "../../core/forms.js";
3
+ import { type CourseSection } from "../courses/structure.js";
4
+ import { MoodleError } from "../../core/errors.js";
5
+ /** "stealth": hidden from the course page but available by link, where the site allows it. */
6
+ export declare const ACTIVITY_VISIBILITIES: readonly ["shown", "hidden", "stealth"];
7
+ export type ActivityVisibility = (typeof ACTIVITY_VISIBILITIES)[number];
8
+ /** The common inputs of an activity's form; anything else goes in `settings` by its form field name. */
9
+ export interface ActivityFields {
10
+ name?: string;
11
+ /** The description (HTML), shown on the activity's page. */
12
+ description?: string;
13
+ /** A page's content, or a label's text (HTML). */
14
+ content?: string;
15
+ /** A URL resource's target. */
16
+ url?: string;
17
+ visibility?: ActivityVisibility;
18
+ settings?: Record<string, FieldValue>;
19
+ }
20
+ /** What a change did, its new id when it created something, and the course afterwards. */
21
+ export interface CourseChange {
22
+ changed: string;
23
+ activityId?: number;
24
+ sectionId?: number;
25
+ url?: string;
26
+ sections: CourseSection[];
27
+ }
28
+ /**
29
+ * Creates an activity or resource of any type at the end of a section, through Moodle's own form,
30
+ * hidden from students unless `visibility` says otherwise. `files` are local paths uploaded to a file
31
+ * resource or folder; a SCORM (`type: "scorm"`) takes the first as its package.
32
+ */
33
+ export declare function createActivity(context: BrowserContext, siteUrl: string, input: ActivityFields & {
34
+ courseId: number;
35
+ sectionId: number;
36
+ type: string;
37
+ files?: string[];
38
+ }): Promise<CourseChange>;
39
+ /** Changes an existing activity through its form: only what's given changes; files stay as they are. */
40
+ export declare function updateActivity(context: BrowserContext, siteUrl: string, activityId: number, input: ActivityFields): Promise<CourseChange>;
41
+ /** Renames, shows or hides, or moves an activity (to a section's end, or before another one). */
42
+ export declare function editActivity(context: BrowserContext, siteUrl: string, courseId: number, activityId: number, { name, visibility, sectionId, beforeActivityId }: {
43
+ name?: string;
44
+ visibility?: ActivityVisibility;
45
+ sectionId?: number;
46
+ beforeActivityId?: number;
47
+ }): Promise<CourseChange>;
48
+ /** Duplicates an activity as the course page's "Duplicate" does (right after it, named with "(copy)"); gives the copy's id and the course afterwards. */
49
+ export declare function duplicateActivity(context: BrowserContext, siteUrl: string, courseId: number, activityId: number): Promise<CourseChange>;
50
+ /** Deletes an activity, with its participants' data (submissions, posts, attempts); gives the course afterwards. */
51
+ export declare function deleteActivity(context: BrowserContext, siteUrl: string, courseId: number, activityId: number): Promise<CourseChange>;
52
+ /** Adds a section after another one (at the end without it), named when a name is given. */
53
+ export declare function addSection(context: BrowserContext, siteUrl: string, courseId: number, { name, afterSectionId }?: {
54
+ name?: string;
55
+ afterSectionId?: number;
56
+ }): Promise<CourseChange>;
57
+ /** Renames, shows or hides, or moves a section after another one. */
58
+ export declare function editSection(context: BrowserContext, siteUrl: string, courseId: number, sectionId: number, { name, visible, afterSectionId }: {
59
+ name?: string;
60
+ visible?: boolean;
61
+ afterSectionId?: number;
62
+ }): Promise<CourseChange>;
63
+ /** Deletes a section and every activity in it, with their participants' data. */
64
+ export declare function deleteSection(context: BrowserContext, siteUrl: string, courseId: number, sectionId: number): Promise<CourseChange>;
65
+ /** One field of a Moodle form, as it is now: its name (what `settings` takes), label, value and options. */
66
+ export interface FormField {
67
+ name: string;
68
+ type: "text" | "textarea" | "select" | "checkbox" | "radio" | "number" | "date" | "other";
69
+ label?: string;
70
+ /** Text, the selected option (several for a multiple select), the checked radio's value. */
71
+ value?: string | string[];
72
+ checked?: boolean;
73
+ options?: {
74
+ value: string;
75
+ label: string;
76
+ }[];
77
+ /** Disabled until another field enables it; Moodle doesn't save it as it is. */
78
+ disabled?: boolean;
79
+ }
80
+ /** The fields of a Moodle form in `page`, internal ones (sesskey, ids, buttons) left out, editors' HTML included. */
81
+ export declare function readFormFields(page: Page): Promise<FormField[]>;
82
+ /** Nobody can see that activity's settings: not an activity, or not the person's to edit. */
83
+ export declare class ActivitySettingsError extends MoodleError {
84
+ name: string;
85
+ constructor(message: string);
86
+ }
87
+ /**
88
+ * An activity's settings as its form holds them, without submitting it: its module, name and
89
+ * description, and every field by the name `updateActivity` takes in `settings`. Teachers only.
90
+ */
91
+ export declare function activitySettings(context: BrowserContext, siteUrl: string, activityId: number): Promise<{
92
+ id: number;
93
+ module: string;
94
+ courseId: number;
95
+ name: string;
96
+ description: string;
97
+ fields: FormField[];
98
+ }>;
@@ -0,0 +1,248 @@
1
+ import { courseState } from "../courses/state.js";
2
+ import { editCourse, renameActivity, renameSection } from "../../core/editor.js";
3
+ import { checkLocalFiles } from "../../core/files.js";
4
+ import { formValue, openForm, setFields, submitForm, uploadToDraft } from "../../core/forms.js";
5
+ import { inPage } from "../../core/inPage.js";
6
+ import { moodleErrorMessage, openSitePage } from "../../core/session.js";
7
+ import { structureOf } from "../courses/structure.js";
8
+ import { MoodleError } from "../../core/errors.js";
9
+ /** "stealth": hidden from the course page but available by link, where the site allows it. */
10
+ export const ACTIVITY_VISIBILITIES = ["shown", "hidden", "stealth"];
11
+ const FORM_VISIBILITY = { shown: 1, hidden: 0, stealth: -1 };
12
+ const VISIBILITY_ACTIONS = { shown: "cm_show", hidden: "cm_hide", stealth: "cm_stealth" };
13
+ /** The form values the common inputs stand for, given the activity's type. */
14
+ function fieldsFor(type, input) {
15
+ const values = {};
16
+ if (input.name !== undefined)
17
+ values.name = input.name;
18
+ if (input.description !== undefined)
19
+ values["introeditor[text]"] = input.description;
20
+ if (input.content !== undefined) {
21
+ // A label's text is its description field; a page has its own content editor.
22
+ if (type === "label")
23
+ values["introeditor[text]"] = input.content;
24
+ else if (type === "page")
25
+ values["page[text]"] = input.content;
26
+ else
27
+ throw new MoodleError(`content is for pages and labels; for a ${type}, use description or settings.`, "invalid-input");
28
+ }
29
+ if (input.url !== undefined)
30
+ values.externalurl = input.url;
31
+ if (input.visibility !== undefined)
32
+ values.visible = FORM_VISIBILITY[input.visibility];
33
+ return { ...values, ...input.settings };
34
+ }
35
+ async function afterChange(context, siteUrl, courseId, changed, extra = {}) {
36
+ return { changed, ...extra, sections: structureOf(await courseState(context, siteUrl, courseId)) };
37
+ }
38
+ /**
39
+ * Creates an activity or resource of any type at the end of a section, through Moodle's own form,
40
+ * hidden from students unless `visibility` says otherwise. `files` are local paths uploaded to a file
41
+ * resource or folder; a SCORM (`type: "scorm"`) takes the first as its package.
42
+ */
43
+ export async function createActivity(context, siteUrl, input) {
44
+ if (input.files)
45
+ await checkLocalFiles(input.files);
46
+ const before = await courseState(context, siteUrl, input.courseId);
47
+ const target = before.section.find((s) => Number(s.id) === input.sectionId);
48
+ if (!target)
49
+ throw new MoodleError(`Course ${input.courseId} has no section ${input.sectionId}. Take its id from the course structure.`, "not-found");
50
+ const page = await openForm(context, siteUrl, `course/modedit.php?add=${input.type}&course=${input.courseId}&section=${target.number}&return=0`);
51
+ try {
52
+ if (input.files && input.files.length > 0) {
53
+ // A SCORM takes one package, in a field of its own.
54
+ const scorm = input.type === "scorm";
55
+ const draft = await formValue(page, scorm ? "packagefile" : "files");
56
+ if (!draft)
57
+ throw new MoodleError(`A ${input.type} takes no files in its form.`, "not-available");
58
+ await uploadToDraft(page, draft, scorm ? input.files.slice(0, 1) : input.files);
59
+ }
60
+ // Hidden unless asked: a teacher publishes after checking.
61
+ await setFields(page, fieldsFor(input.type, { ...input, visibility: input.visibility ?? "hidden" }));
62
+ await submitForm(page, "submitbutton2");
63
+ }
64
+ finally {
65
+ await page.close().catch(() => undefined);
66
+ }
67
+ const after = await courseState(context, siteUrl, input.courseId);
68
+ const created = after.cm.find((cm) => !before.cm.some((old) => old.id === cm.id));
69
+ return {
70
+ changed: created ? `Created ${input.type} ${created.id} "${created.name}" in section ${target.number}.` : `The form was saved, but no new activity appears in course ${input.courseId}.`,
71
+ activityId: created ? Number(created.id) : undefined,
72
+ url: created?.url,
73
+ sections: structureOf(after),
74
+ };
75
+ }
76
+ /** Changes an existing activity through its form: only what's given changes; files stay as they are. */
77
+ export async function updateActivity(context, siteUrl, activityId, input) {
78
+ const page = await openForm(context, siteUrl, `course/modedit.php?update=${activityId}&return=0`);
79
+ let courseId;
80
+ try {
81
+ courseId = Number(await formValue(page, "course"));
82
+ const type = (await formValue(page, "modulename")) ?? "";
83
+ await setFields(page, fieldsFor(type, input));
84
+ await submitForm(page, "submitbutton2");
85
+ }
86
+ finally {
87
+ await page.close().catch(() => undefined);
88
+ }
89
+ const after = await courseState(context, siteUrl, courseId);
90
+ const cm = after.cm.find((c) => Number(c.id) === activityId);
91
+ return { changed: `Updated activity ${activityId}${cm ? ` "${cm.name}"` : ""}.`, activityId: activityId, url: cm?.url, sections: structureOf(after) };
92
+ }
93
+ /** Renames, shows or hides, or moves an activity (to a section's end, or before another one). */
94
+ export async function editActivity(context, siteUrl, courseId, activityId, { name, visibility, sectionId, beforeActivityId }) {
95
+ const done = [];
96
+ if (name !== undefined) {
97
+ await renameActivity(context, siteUrl, activityId, name);
98
+ done.push(`renamed to "${name}"`);
99
+ }
100
+ if (visibility !== undefined) {
101
+ await editCourse(context, siteUrl, courseId, VISIBILITY_ACTIONS[visibility], [activityId]);
102
+ done.push(`made ${visibility}`);
103
+ }
104
+ if (sectionId !== undefined || beforeActivityId !== undefined) {
105
+ await editCourse(context, siteUrl, courseId, "cm_move", [activityId], { sectionId, activityId: beforeActivityId });
106
+ done.push(beforeActivityId !== undefined ? `moved before activity ${beforeActivityId}` : `moved to the end of section ${sectionId}`);
107
+ }
108
+ return afterChange(context, siteUrl, courseId, done.length > 0 ? `Activity ${activityId} ${done.join(", ")}.` : "Nothing to change.");
109
+ }
110
+ /** Duplicates an activity as the course page's "Duplicate" does (right after it, named with "(copy)"); gives the copy's id and the course afterwards. */
111
+ export async function duplicateActivity(context, siteUrl, courseId, activityId) {
112
+ const before = new Set((await courseState(context, siteUrl, courseId)).cm.map((cm) => cm.id));
113
+ await editCourse(context, siteUrl, courseId, "cm_duplicate", [activityId]);
114
+ const copy = (await courseState(context, siteUrl, courseId)).cm.find((cm) => !before.has(cm.id));
115
+ return afterChange(context, siteUrl, courseId, `Activity ${activityId} duplicated${copy ? ` as ${copy.id}` : ""}.`, { activityId: copy ? Number(copy.id) : undefined });
116
+ }
117
+ /** Deletes an activity, with its participants' data (submissions, posts, attempts); gives the course afterwards. */
118
+ export async function deleteActivity(context, siteUrl, courseId, activityId) {
119
+ await editCourse(context, siteUrl, courseId, "cm_delete", [activityId]);
120
+ return afterChange(context, siteUrl, courseId, `Activity ${activityId} deleted.`);
121
+ }
122
+ /** Adds a section after another one (at the end without it), named when a name is given. */
123
+ export async function addSection(context, siteUrl, courseId, { name, afterSectionId } = {}) {
124
+ const before = new Set((await courseState(context, siteUrl, courseId)).section.map((s) => s.id));
125
+ await editCourse(context, siteUrl, courseId, "section_add", [], { sectionId: afterSectionId });
126
+ const added = (await courseState(context, siteUrl, courseId)).section.find((s) => !before.has(s.id));
127
+ if (added && name !== undefined)
128
+ await renameSection(context, siteUrl, courseId, Number(added.id), name);
129
+ return afterChange(context, siteUrl, courseId, `Section ${added?.id ?? "?"} added${name !== undefined ? ` as "${name}"` : ""}.`, { sectionId: added ? Number(added.id) : undefined });
130
+ }
131
+ /** Renames, shows or hides, or moves a section after another one. */
132
+ export async function editSection(context, siteUrl, courseId, sectionId, { name, visible, afterSectionId }) {
133
+ const done = [];
134
+ if (name !== undefined) {
135
+ await renameSection(context, siteUrl, courseId, sectionId, name);
136
+ done.push(`renamed to "${name}"`);
137
+ }
138
+ if (visible !== undefined) {
139
+ await editCourse(context, siteUrl, courseId, visible ? "section_show" : "section_hide", [sectionId]);
140
+ done.push(visible ? "shown" : "hidden");
141
+ }
142
+ if (afterSectionId !== undefined) {
143
+ await editCourse(context, siteUrl, courseId, "section_move_after", [sectionId], { sectionId: afterSectionId });
144
+ done.push(`moved after section ${afterSectionId}`);
145
+ }
146
+ return afterChange(context, siteUrl, courseId, done.length > 0 ? `Section ${sectionId} ${done.join(", ")}.` : "Nothing to change.");
147
+ }
148
+ /** Deletes a section and every activity in it, with their participants' data. */
149
+ export async function deleteSection(context, siteUrl, courseId, sectionId) {
150
+ await editCourse(context, siteUrl, courseId, "section_delete", [sectionId]);
151
+ return afterChange(context, siteUrl, courseId, `Section ${sectionId} deleted, with its activities.`);
152
+ }
153
+ /** The fields of a Moodle form in `page`, internal ones (sesskey, ids, buttons) left out, editors' HTML included. */
154
+ export function readFormFields(page) {
155
+ return inPage(page, () => {
156
+ const doc = globalThis.document;
157
+ const clean = (s) => (s ?? "").replace(/\s+/g, " ").trim();
158
+ // A field's label: its own <label for>, or its row's (a date selector's parts, a radio group).
159
+ const labelOf = (field) => {
160
+ const own = field.id ? doc.querySelector(`label[for="${field.id}"]`) : null;
161
+ const row = field.closest(".fitem")?.querySelector(".col-form-label label, .col-form-label p, legend");
162
+ return clean(own?.textContent) || clean(row?.textContent) || undefined;
163
+ };
164
+ const fields = [];
165
+ const internal = /^(sesskey|_qf__|mform_isexpanded_|mform_showmore_|course$|coursemodule$|section$|module$|modulename$|instance$|add$|update$|return$|sr$|beforemod$)/;
166
+ for (const field of Array.from(doc.querySelectorAll("form.mform [name]"))) {
167
+ const name = field.name;
168
+ if (!name || internal.test(name) || ["hidden", "submit", "button", "fieldset"].includes(field.type) || field.tagName === "FIELDSET")
169
+ continue;
170
+ if (field.type === "radio") {
171
+ let group = fields.find((f) => f.name === name);
172
+ if (!group) {
173
+ group = { name, type: "radio", label: clean(field.closest(".fitem")?.querySelector(".col-form-label, legend")?.textContent) || undefined, options: [] };
174
+ fields.push(group);
175
+ }
176
+ group.options.push({ value: field.value, label: clean((doc.querySelector(`label[for="${field.id}"]`) ?? field.closest("label"))?.textContent) || field.value });
177
+ if (field.checked)
178
+ group.value = field.value;
179
+ }
180
+ else if (field.type === "checkbox") {
181
+ fields.push({ name, type: "checkbox", label: labelOf(field), value: field.value, checked: field.checked });
182
+ }
183
+ else if (field.tagName === "SELECT") {
184
+ const options = Array.from(field.options);
185
+ const selected = options.filter((o) => o.selected).map((o) => o.value);
186
+ // A date selector's parts (duedate[day]…) are numbers, named by their row ("Due date (day)").
187
+ const part = /\[(day|month|year|hour|minute)\]$/.exec(name)?.[1];
188
+ if (part) {
189
+ // The group's label is id_<group>_label (id_duedate_label for duedate[day]).
190
+ const row = clean(doc.querySelector(`#id_${name.slice(0, name.indexOf("["))}_label`)?.textContent);
191
+ fields.push({ name, type: "date", label: row ? `${row} (${part})` : part, value: selected[0] });
192
+ }
193
+ else {
194
+ fields.push({ name, type: "select", label: labelOf(field), value: field.multiple ? selected : selected[0], options: options.map((o) => ({ value: o.value, label: clean(o.text) })) });
195
+ }
196
+ }
197
+ else {
198
+ const type = field.tagName === "TEXTAREA" ? "textarea" : field.type === "number" ? "number" : field.type === "text" || field.type === "url" || field.type === "email" ? "text" : "other";
199
+ fields.push({ name, type, label: labelOf(field), value: field.value });
200
+ }
201
+ }
202
+ // Moodle disables fields until another enables them (a date until its "enable", a maximum grade until rescaling is chosen).
203
+ for (const f of fields) {
204
+ const elements = Array.from(doc.querySelectorAll(`form.mform [name="${f.name}"]`)).filter((e) => e.type !== "hidden");
205
+ if (elements.length > 0 && elements.every((e) => e.disabled))
206
+ f.disabled = true;
207
+ // No label, no key: plain data for the program.
208
+ for (const key of Object.keys(f))
209
+ if (f[key] === undefined)
210
+ delete f[key];
211
+ }
212
+ return fields;
213
+ }, null);
214
+ }
215
+ /** Nobody can see that activity's settings: not an activity, or not the person's to edit. */
216
+ export class ActivitySettingsError extends MoodleError {
217
+ name = "ActivitySettingsError";
218
+ constructor(message) {
219
+ super(message, "activity-settings");
220
+ }
221
+ }
222
+ /**
223
+ * An activity's settings as its form holds them, without submitting it: its module, name and
224
+ * description, and every field by the name `updateActivity` takes in `settings`. Teachers only.
225
+ */
226
+ export async function activitySettings(context, siteUrl, activityId) {
227
+ const page = await openSitePage(context, siteUrl, `course/modedit.php?update=${activityId}&return=0`);
228
+ try {
229
+ if ((await page.locator("form.mform").count()) === 0) {
230
+ throw new ActivitySettingsError(`You can't see activity ${activityId}'s settings: ${(await moodleErrorMessage(page)) ?? "it doesn't exist, or you can't edit it"}`);
231
+ }
232
+ await page.waitForLoadState("load");
233
+ const fields = await readFormFields(page);
234
+ const value = (name) => fields.find((f) => f.name === name)?.value;
235
+ return {
236
+ id: activityId,
237
+ module: (await formValue(page, "modulename")) ?? "",
238
+ courseId: Number(await formValue(page, "course")),
239
+ name: value("name") ?? "",
240
+ description: value("introeditor[text]") ?? "",
241
+ fields,
242
+ };
243
+ }
244
+ finally {
245
+ await page.close().catch(() => undefined);
246
+ }
247
+ }
248
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/areas/activities/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { BrowserContext } from "playwright-core";
2
+ import type { Trail } from "../../options.js";
3
+ import { type MediaItem } from "../../core/text.js";
4
+ import { MoodleError } from "../../core/errors.js";
5
+ export declare const CONTENT_TYPES: readonly ["page", "book", "label", "url", "resource", "folder"];
6
+ export type ContentType = (typeof CONTENT_TYPES)[number];
7
+ /** The activity isn't of the given type, or the person can't view it. */
8
+ export declare class NotViewableError extends MoodleError {
9
+ name: string;
10
+ constructor(message: string);
11
+ }
12
+ export interface ActivityContent {
13
+ activity: {
14
+ id: number;
15
+ type: ContentType;
16
+ };
17
+ /** Page and label content, as plain text with paragraphs and lists kept. */
18
+ text?: string;
19
+ /** A book's chapters, in order. */
20
+ chapters?: {
21
+ title: string;
22
+ text: string;
23
+ }[];
24
+ /** Videos, audio and players in a page's, label's or book's content. */
25
+ media?: MediaItem[];
26
+ /** A URL resource's target. */
27
+ url?: string;
28
+ /** A file resource's or a folder's files. */
29
+ files?: {
30
+ name: string;
31
+ url: string;
32
+ }[];
33
+ trail: Trail;
34
+ /** With the realistic trail, the time spent reading. */
35
+ readingSeconds?: number;
36
+ }
37
+ /**
38
+ * Reads a page's or label's text, a book's chapters, a URL resource's target, or a file resource's
39
+ * or folder's files. With the realistic trail, page by page at a reader's pace, calling `onProgress`
40
+ * with the seconds read so far while it pauses. Takes an activity as the course structure gives it
41
+ * (`{ id, type }`), or its id and module.
42
+ */
43
+ export declare function activityContent(context: BrowserContext, siteUrl: string, activity: {
44
+ id: number;
45
+ type: string;
46
+ }, options?: ContentOptions): Promise<ActivityContent>;
47
+ export declare function activityContent(context: BrowserContext, siteUrl: string, activityId: number, type: ContentType, options?: ContentOptions): Promise<ActivityContent>;
48
+ /** How `activityContent` reads: at a person's pace (`realistic`, the default) or at once. */
49
+ export interface ContentOptions {
50
+ trail?: Trail;
51
+ /** While pausing on the realistic trail, the seconds read so far. */
52
+ onProgress?: (secondsRead: number) => void;
53
+ }