Saltar al contenido principal

El objeto Moodle y las funciones

Hay dos formas de llamar al driver, y son las mismas operaciones:

Términos: id del módulo del curso, changed, sitio.

  • El objeto Moodle: createMoodle() guarda el sitio y el navegador, y tiene cada operación como un método. La forma habitual.
  • Las funciones: cada operación es también una función exportada que recibe primero el contexto del navegador y el sitio. Para programas que gestionan el navegador por su cuenta, trabajan con varios sitios o bajan a un nivel más bajo.
import { createMoodle, listCourses } from "@falkenslab/moodle-driver";

const moodle = createMoodle({ site: "https://campus.example.org/", profileDir: "/home/ada/.moodle-profile", headless: true, downloadBrowser: true });

// La misma llamada, de las dos formas.
const a = await moodle.listCourses("inprogress");
const b = await listCourses(await moodle.context(), moodle.site, "inprogress");

createMoodle​

function createMoodle(options: MoodleDefaults & (({ site: string } & BrowserOptions) | { site: string; browser: BrowserManager })): Moodle;

Dale el sitio y las opciones del navegador (se crea un gestor del navegador nuevo para él), o un gestor del navegador que ya tengas. El sitio se normaliza (parseSiteUrl): una barra final, sin query; algo que no es una URL http(s) lanza MoodleUrlError. No se lanza nada hasta la primera llamada que necesita el navegador.

Valores predeterminados​

Los ajustes que un programa repetiría en cada llamada se le dan al objeto una vez; cualquier llamada puede seguir dando los suyos.

OpciónValor predeterminadoLo usan
trail"realistic"activityContent, answerQuizQuestions, answerLessonPage: el ritmo del estudiante
onProgress(elapsed, total?)ningunoLas mismas tres, mientras esperan al ritmo de un lector
loginTimeoutMs5 minutoslogIn(), cuánto espera a la persona
downloadDirningunodownloadFile(url) sin carpeta, exportGrades(courseId, options) sin destDir
import { createMoodle } from "@falkenslab/moodle-driver";

const moodle = createMoodle({
site: "https://campus.example.org/",
profileDir: "/home/ada/.moodle-profile",
headless: true,
downloadBrowser: true,
trail: "fast",
downloadDir: "/home/ada/moodle-files",
});
await moodle.logIn();
const book = await moodle.activityContent(6, "book"); // rápido
const slow = await moodle.activityContent(6, "book", { trail: "realistic" }); // solo esta llamada

moodle.defaults los devuelve.

Los métodos del objeto​

Cada operación cuyos primeros parámetros son el contexto del navegador y el sitio es un método del mismo nombre sin ellos: gradeSubmission(context, site, 35, 7, input) es moodle.gradeSubmission(35, 7, input), con las mismas opciones, resultado y errores. Las páginas de las áreas muestran las firmas de las funciones; quita los dos primeros parámetros para el método. Las primitivas con las que se construyen las operaciones (los servicios, páginas y formularios de Moodle, el estado en bruto del curso, las acciones del editor) son funciones de @falkenslab/moodle-driver/internals, para limitar la entrada principal y el objeto a lo que llaman los programas; consulta Bajar de nivel. Los analizadores y pasos sueltos propios de las áreas no se exportan.

El objeto también tiene lo suyo:

MiembroQué es
siteLa URL base del sitio, normalizada
browserEl gestor del navegador que hay debajo
context()El contexto del navegador, lanzándolo si hace falta: lo que reciben las funciones
logIn({ timeoutMs?, onWait? })Se asegura de que hay una sesión, esperando loginTimeoutMs por defecto; consulta Iniciar sesión
logOut()Cierra la sesión de Moodle
publicSiteInfo()Lo que el sitio cuenta a cualquiera antes de iniciar sesión
callService<T>(methodname, args)Uno de los servicios AJAX de Moodle, tipado; consulta Bajar de nivel
tab(), visible(), setVisible(visible)La pestaña de trabajo y mostrar el navegador; consulta El navegador
saveSession(), close()Guardar la sesión y cerrar el navegador
[Symbol.asyncDispose]()close(), para que await using cierre el navegador cuando termina el bloque
defaultsLos valores predeterminados con los que se creó

Con await using (TypeScript 5.2 o posterior, en un entorno de ejecución que lo tenga o mediante un transpilador), el navegador se cierra solo cuando termina el bloque, también si hay un error:

import { createMoodle } from "@falkenslab/moodle-driver";

await using moodle = createMoodle({ site: "https://campus.example.org/", profileDir: "/home/ada/.moodle-profile", headless: true, downloadBrowser: true });
await moodle.logIn();
console.log(await moodle.listCourses());
// moodle.close() se ejecuta aquí

Argumentos​

Después del contexto y el sitio, cada operación recibe los ids con los que trabaja y luego un objeto de opciones cuando tiene ajustes opcionales, con el comportamiento anterior como valor predeterminado.

const discussions = await moodle.forumDiscussions(37);
await moodle.gradeSubmission(35, 7, { grade: "8.5", feedback: "<p>Bien argumentado.</p>", notify: true });

Ids​

Moodle tiene varios tipos de ids, y el driver recibe los que muestran sus páginas:

IdQué esDónde obtenerlo
Id del cursocourse/view.php?id=3listCourses()
Id de la actividad (id del módulo del curso, "cmid")mod/quiz/view.php?id=27courseStructure(), el id de cada actividad
Id de la secciónNo es su número: un id de la base de datoscourseStructure(), el id de cada sección
Id del usuariouser/view.php?id=4participants(), whoAmI(), cualquier listado de personas
Ids del debate y del mensajemod/forum/discuss.php?d=4, #p8forumDiscussions(), discussion()
Id del intentomod/quiz/attempt.php?attempt=9startQuizAttempt(), quizInfo(), quizResults()
Ids de la pregunta, la categoría y el bancoLos del banco de preguntasquestionBanks(), questions()
Ids del elemento de calificación y de la categoríaLos del libro de calificacionesgradebookSetup(), graderReport()

Las operaciones sobre una actividad reciben siempre su id del módulo del curso (el id de su URL), como un parámetro llamado activityId sea cual sea el módulo (un cuestionario, un foro, un banco de preguntas), nunca su id de instancia: el driver encuentra la instancia por sí mismo cuando Moodle la necesita.

Resultados​

Los resultados son datos simples que puedes serializar como JSON:

  • fechas como cadenas ISO 8601 en UTC (2026-10-23T12:30:00.000Z), salvo donde Moodle solo muestra una fecha como texto (entonces llega tal como se muestra, en el idioma del sitio);
  • nombres y textos decodificados, y el HTML convertido en texto plano conservando párrafos y listas donde una operación lee contenido;
  • el id propio de una cosa es id, y los ids de otras a las que hace referencia son <thing>Id (courseId, userId, activityId): un intento es { id, userId, … }, una calificación { itemId, grade };
  • ningún objeto de Playwright, nada que cerrar.

Listas largas​

Las funciones cuyas listas crecen sin límite en un sitio real (notificaciones, conversaciones y sus mensajes, los debates de un foro y los mensajes de un debate, las entregas de una tarea, los intentos de un cuestionario, las preguntas de un banco, los participantes y los registros de un curso) las leen página a página, todas de la misma forma:

  • limit: cuántos elementos devolver (1 o más), y offset: cuántos saltarse antes (0 si no se indica), contados en los elementos propios de la función y en su propio orden (los más recientes primero para notificaciones y mensajes, y el resto como los lista Moodle);
  • sin limit, la función lo devuelve todo, como una lista simple, como siempre ha hecho (offset solo se salta los primeros);
  • con limit, la lista llega como una ListPage, { items, more }: los elementos de la página y si hay más después. Pide la página siguiente con offset desplazado en limit hasta que more sea false.
interface ListPage<T> {
items: T[];
more: boolean;
}

Los tipos siguen a la llamada: TypeScript da ListPage<Notification> para notifications({ limit: 20 }) y Notification[] para notifications(). Donde la lista forma parte de un resultado mayor, la propia lista se convierte en la página: discussion(id, { limit }) da { discussion, posts: { items, more } }.

let offset = 0;
for (;;) {
const page = await moodle.notifications({ unreadOnly: true, limit: 20, offset });
for (const notification of page.items) console.log(notification.subject);
if (!page.more) break;
offset += 20;
}

Una página cuesta menos que la lista entera allí donde Moodle lo permite: el driver pide al servicio de Moodle solo la página, o abre solo la página de la paginación propia de Moodle que la contiene. Donde Moodle no pagina (los mensajes de un debate, las preguntas de un banco dentro de una categoría), el driver lee la lista y la recorta, pero aun así solo convierte los elementos de la página; la sección de cada función indica cuáles. Un limit menor que 1 o un offset menor que 0 es un MoodleError con el código invalid-input. Las páginas se leen cuando se piden: si mientras tanto se añade algo, una página posterior puede repetir un elemento o saltarse uno.

El comando moodle las recibe como --limit y --offset, muestra una página como sus elementos e indica cuándo hay más.

Las escrituras responden con el estado posterior​

Toda operación que cambia algo devuelve un objeto con una frase corta changed que dice lo que ha hecho, junto al estado posterior cuando es barato de leer: la estructura del curso después de crear una actividad, la tarea después de hacer la entrega, la consulta después de responderla, el debate después de fijarlo, los ids de lo que ha creado. Compruébalo en lugar de volver a leer.

const result = await moodle.createActivity({ courseId: 3, sectionId: 7, type: "page", name: "Bucles", content: "<p>...</p>" });
console.log(result.changed); // Created page 42 "Bucles" in section 2.
console.log(result.activityId); // 42

Muchas escrituras son idempotentes allí donde lo son las de Moodle: marcar una actividad como hecha dos veces, fijar un debate ya fijado, marcar notificaciones como leídas. Otras crean algo cada vez (un mensaje, una actividad, una pregunta).

Una cosa cada vez​

Las operaciones comparten el navegador y la sesión de la persona. Llámalas una tras otra (con await en cada una) en lugar de en paralelo sobre el mismo objeto: la sesión y los tokens de formulario de Moodle son por persona, y un sitio se comporta mejor con una persona haciendo una cosa cada vez. Varios sitios, o varias personas, llevan un objeto (y un perfil) cada uno.

Los permisos son los de Moodle​

El driver puede hacer todo lo que la persona puede hacer en Moodle, y nada más. Un estudiante que llama a una operación de profesor recibe como error el rechazo de Moodle, normalmente con el propio mensaje de Moodle ("Sorry, but you do not currently have permissions to do that (View grader report)", en el idioma del sitio). No hay un modelo de permisos aparte en el driver; un programa que quiera uno puede construirlo sobre las categorías de datos.