@josetra/dutic-mcp 0.1.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/dist/cli/format.js +69 -0
  4. package/dist/cli/format.js.map +1 -0
  5. package/dist/cli/index.js +426 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/cli/setup.js +121 -0
  8. package/dist/cli/setup.js.map +1 -0
  9. package/dist/cli/ui.js +111 -0
  10. package/dist/cli/ui.js.map +1 -0
  11. package/dist/core/auth.js +47 -0
  12. package/dist/core/auth.js.map +1 -0
  13. package/dist/core/browser.js +56 -0
  14. package/dist/core/browser.js.map +1 -0
  15. package/dist/core/config.js +34 -0
  16. package/dist/core/config.js.map +1 -0
  17. package/dist/core/dates.js +43 -0
  18. package/dist/core/dates.js.map +1 -0
  19. package/dist/core/errors.js +25 -0
  20. package/dist/core/errors.js.map +1 -0
  21. package/dist/core/http.js +43 -0
  22. package/dist/core/http.js.map +1 -0
  23. package/dist/core/login.js +193 -0
  24. package/dist/core/login.js.map +1 -0
  25. package/dist/core/models.js +79 -0
  26. package/dist/core/models.js.map +1 -0
  27. package/dist/core/moodleClient.js +94 -0
  28. package/dist/core/moodleClient.js.map +1 -0
  29. package/dist/core/session.js +64 -0
  30. package/dist/core/session.js.map +1 -0
  31. package/dist/domain/assign.js +112 -0
  32. package/dist/domain/assign.js.map +1 -0
  33. package/dist/domain/concurrency.js +17 -0
  34. package/dist/domain/concurrency.js.map +1 -0
  35. package/dist/domain/courses.js +70 -0
  36. package/dist/domain/courses.js.map +1 -0
  37. package/dist/domain/documents.js +209 -0
  38. package/dist/domain/documents.js.map +1 -0
  39. package/dist/domain/grades.js +99 -0
  40. package/dist/domain/grades.js.map +1 -0
  41. package/dist/domain/people.js +161 -0
  42. package/dist/domain/people.js.map +1 -0
  43. package/dist/domain/resources.js +242 -0
  44. package/dist/domain/resources.js.map +1 -0
  45. package/dist/domain/tasks.js +191 -0
  46. package/dist/domain/tasks.js.map +1 -0
  47. package/dist/mcp/server.js +261 -0
  48. package/dist/mcp/server.js.map +1 -0
  49. package/package.json +67 -0
  50. package/skills/dutic/SKILL.md +173 -0
@@ -0,0 +1,242 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { mkdir, stat } from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+ import { Readable } from "node:stream";
5
+ import { pipeline } from "node:stream/promises";
6
+ import * as cheerio from "cheerio";
7
+ import { CHROME_USER_AGENT } from "../core/config.js";
8
+ import { SessionExpiredError } from "../core/errors.js";
9
+ import { fetchUnsa } from "../core/http.js";
10
+ import { getCourseContents } from "./courses.js";
11
+ import { mapLimit } from "./concurrency.js";
12
+ function moodleHeaders(session, referer) {
13
+ const h = {
14
+ Cookie: `MoodleSession=${session.moodleSession}`,
15
+ "User-Agent": CHROME_USER_AGENT,
16
+ };
17
+ if (referer)
18
+ h.Referer = referer;
19
+ return h;
20
+ }
21
+ async function getHtml(session, url) {
22
+ const res = await fetchUnsa(url, { headers: moodleHeaders(session) });
23
+ if (res.status === 302 || res.status === 303)
24
+ throw new SessionExpiredError();
25
+ const text = await res.text();
26
+ if (/login\/index\.php|loginform/i.test(text) && /\/login\//.test(res.url)) {
27
+ throw new SessionExpiredError();
28
+ }
29
+ return text;
30
+ }
31
+ /**
32
+ * Lista los archivos descargables de un curso. Recorre los módulos de tipo `resource`,
33
+ * `folder` y `url`, quedándose con su URL de vista (la resolución al pluginfile.php real
34
+ * se hace en el momento de descargar).
35
+ */
36
+ export async function listCourseFiles(session, courseId) {
37
+ const sections = await getCourseContents(session, courseId);
38
+ const files = [];
39
+ for (const section of sections) {
40
+ for (const m of section.modules) {
41
+ if (!["resource", "folder", "url"].includes(m.modname))
42
+ continue;
43
+ if (!m.url)
44
+ continue;
45
+ files.push({
46
+ filename: m.name,
47
+ fileurl: m.url,
48
+ moduleName: m.name,
49
+ modname: m.modname,
50
+ mimetype: null,
51
+ filesize: null,
52
+ });
53
+ }
54
+ }
55
+ return files;
56
+ }
57
+ /** Normaliza para comparar secciones ignorando acentos y mayúsculas. */
58
+ function norm(s) {
59
+ return s
60
+ .toLowerCase()
61
+ .normalize("NFD")
62
+ .replace(/[̀-ͯ]/g, "");
63
+ }
64
+ export async function listCourseMaterials(session, courseId, opts = {}) {
65
+ const { concurrency = 8, section: sectionFilter } = opts;
66
+ const sections = await getCourseContents(session, courseId);
67
+ const filterNorm = sectionFilter ? norm(sectionFilter) : null;
68
+ const out = [];
69
+ const folderJobs = [];
70
+ for (const section of sections) {
71
+ if (filterNorm && !norm(section.name).includes(filterNorm))
72
+ continue;
73
+ for (const m of section.modules) {
74
+ if (!m.url)
75
+ continue;
76
+ if (m.modname === "resource") {
77
+ out.push({
78
+ filename: m.name,
79
+ url: m.url,
80
+ modname: "resource",
81
+ section: section.name,
82
+ folder: null,
83
+ });
84
+ }
85
+ else if (m.modname === "folder") {
86
+ folderJobs.push({ section: section.name, name: m.name, url: m.url });
87
+ }
88
+ }
89
+ }
90
+ const expanded = await mapLimit(folderJobs, concurrency, async (j) => {
91
+ const files = await listFolderFiles(session, j.url).catch(() => []);
92
+ return files.map((f) => ({
93
+ filename: f.filename,
94
+ url: f.url,
95
+ modname: "folder",
96
+ section: j.section,
97
+ folder: j.name,
98
+ }));
99
+ });
100
+ for (const arr of expanded)
101
+ out.push(...arr);
102
+ return out;
103
+ }
104
+ /**
105
+ * Resuelve la URL real de descarga (pluginfile.php) a partir de una URL de vista de módulo
106
+ * (mod/resource/view.php...). Muchos recursos redirigen o incrustan el archivo; se parsea el
107
+ * HTML buscando el enlace/objeto de pluginfile.
108
+ */
109
+ async function resolvePluginFileUrl(session, viewUrl) {
110
+ if (/pluginfile\.php/.test(viewUrl))
111
+ return viewUrl;
112
+ if (!/mod\/(resource|folder)\/view\.php/.test(viewUrl))
113
+ return viewUrl;
114
+ const html = await getHtml(session, viewUrl);
115
+ const $ = cheerio.load(html);
116
+ const candidates = [
117
+ $("object#resourceobject").attr("data"),
118
+ $('object[data*="pluginfile"]').attr("data"),
119
+ $(".resourceworkaround a[href]").attr("href"),
120
+ $('a[href*="pluginfile"]').attr("href"),
121
+ $('.resourcecontent a[href*="pluginfile"]').attr("href"),
122
+ ];
123
+ const found = candidates.find((c) => c && /pluginfile\.php/.test(c));
124
+ return found ?? viewUrl;
125
+ }
126
+ /**
127
+ * Descarga un archivo a disco. Acepta tanto una URL de vista de módulo como una URL directa
128
+ * de pluginfile.php; en el primer caso resuelve la URL real primero. Envía cookie, UA de
129
+ * Chrome y Referer (Moodle exige coherencia de origen para servir pluginfile).
130
+ */
131
+ export async function downloadFile(session, url, destPath) {
132
+ const realUrl = await resolvePluginFileUrl(session, url);
133
+ await mkdir(dirname(destPath), { recursive: true });
134
+ // Descarga con timeout más amplio (archivos grandes).
135
+ const res = await fetchUnsa(realUrl, { headers: moodleHeaders(session, url) }, 120_000);
136
+ if (res.status === 302 || res.status === 303)
137
+ throw new SessionExpiredError();
138
+ if (!res.ok || !res.body) {
139
+ throw new Error(`No se pudo descargar (HTTP ${res.status}): ${realUrl}`);
140
+ }
141
+ const nodeStream = Readable.fromWeb(res.body);
142
+ const out = createWriteStream(destPath);
143
+ await pipeline(nodeStream, out);
144
+ // El servidor a veces omite content-length (respuesta chunked); usar el tamaño real escrito.
145
+ const declared = Number(res.headers.get("content-length")) || 0;
146
+ const bytes = declared || (await stat(destPath)).size;
147
+ return {
148
+ path: destPath,
149
+ bytes,
150
+ contentType: res.headers.get("content-type"),
151
+ };
152
+ }
153
+ /** true si la URL es la vista de una carpeta (mod/folder). */
154
+ export function isFolderUrl(url) {
155
+ return /mod\/folder\/view\.php/.test(url);
156
+ }
157
+ /**
158
+ * Lista los archivos de una carpeta (mod/folder). Moodle renderiza el árbol de archivos
159
+ * server-side dentro de `#folder_tree0` como enlaces `<a href="pluginfile.php/...">`, así que
160
+ * basta HTTP + cheerio (rápido, sin navegador). Una carpeta vacía devuelve lista vacía.
161
+ */
162
+ export async function listFolderFiles(session, folderUrl) {
163
+ const html = await getHtml(session, folderUrl);
164
+ const $ = cheerio.load(html);
165
+ const files = [];
166
+ const seen = new Set();
167
+ $('#folder_tree0 a[href*="pluginfile.php"]').each((_, a) => {
168
+ const href = $(a).attr("href");
169
+ if (!href || seen.has(href))
170
+ return;
171
+ seen.add(href);
172
+ const text = $(a).text().trim();
173
+ const filename = text || decodeURIComponent(new URL(href).pathname.split("/").pop() ?? "archivo");
174
+ files.push({ filename, url: href });
175
+ });
176
+ return files;
177
+ }
178
+ /** Infiere un nombre de archivo desde Content-Disposition o el último segmento de la URL. */
179
+ function inferFilename(url, contentDisposition) {
180
+ if (contentDisposition) {
181
+ const m = /filename\*?=(?:UTF-8''|")?([^";]+)/i.exec(contentDisposition);
182
+ if (m)
183
+ return decodeURIComponent(m[1].replace(/"$/, ""));
184
+ }
185
+ try {
186
+ const p = new URL(url).pathname;
187
+ const last = p.split("/").filter(Boolean).pop() ?? "archivo";
188
+ return decodeURIComponent(last);
189
+ }
190
+ catch {
191
+ return "archivo";
192
+ }
193
+ }
194
+ /**
195
+ * Descarga un recurso a memoria (Buffer), resolviendo antes la URL real de pluginfile.php si
196
+ * hace falta. Útil para procesar el contenido (p. ej. convertir un PDF a Markdown) sin escribir
197
+ * a disco. Con `maxBytes` aborta descargas demasiado grandes.
198
+ */
199
+ export async function fetchResourceBuffer(session, url, maxBytes = 40 * 1024 * 1024) {
200
+ const resolvedUrl = await resolvePluginFileUrl(session, url);
201
+ const res = await fetchUnsa(resolvedUrl, { headers: moodleHeaders(session, url) }, 120_000);
202
+ if (res.status === 302 || res.status === 303)
203
+ throw new SessionExpiredError();
204
+ if (!res.ok)
205
+ throw new Error(`No se pudo descargar (HTTP ${res.status}): ${resolvedUrl}`);
206
+ const declared = Number(res.headers.get("content-length")) || 0;
207
+ if (declared > maxBytes) {
208
+ throw new Error(`Archivo demasiado grande (${declared} bytes > ${maxBytes}).`);
209
+ }
210
+ const arrayBuf = await res.arrayBuffer();
211
+ const buffer = Buffer.from(arrayBuf);
212
+ if (buffer.byteLength > maxBytes) {
213
+ throw new Error(`Archivo demasiado grande (${buffer.byteLength} bytes > ${maxBytes}).`);
214
+ }
215
+ return {
216
+ buffer,
217
+ contentType: res.headers.get("content-type"),
218
+ filename: inferFilename(resolvedUrl, res.headers.get("content-disposition")),
219
+ resolvedUrl,
220
+ };
221
+ }
222
+ const sanitize = (name) => name.replace(/[<>:"/\\|?*]+/g, "_").slice(0, 120);
223
+ export async function pullCourseFiles(session, courseId, destDir, opts = {}) {
224
+ const { concurrency = 6, onProgress } = opts;
225
+ const materials = await listCourseMaterials(session, courseId, opts);
226
+ let done = 0;
227
+ const results = await mapLimit(materials, concurrency, async (f) => {
228
+ const subDir = f.folder ? join(destDir, sanitize(f.folder)) : destDir;
229
+ const dest = join(subDir, sanitize(f.filename));
230
+ try {
231
+ return await downloadFile(session, f.url, dest);
232
+ }
233
+ catch {
234
+ return null;
235
+ }
236
+ finally {
237
+ onProgress?.(++done, materials.length, f.filename);
238
+ }
239
+ });
240
+ return results.filter((r) => r !== null);
241
+ }
242
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/domain/resources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ import { postAjax } from "../core/moodleClient.js";
2
+ import { getCourseState, getEnrolledCourses } from "./courses.js";
3
+ import { getAssignDetail } from "./assign.js";
4
+ import { mapLimit } from "./concurrency.js";
5
+ /** Extrae el course module id (cmid) de una URL tipo mod/assign/view.php?id=123. */
6
+ function cmidFromUrl(url) {
7
+ if (!url)
8
+ return null;
9
+ const m = /[?&]id=(\d+)/.exec(url);
10
+ return m ? Number(m[1]) : null;
11
+ }
12
+ /**
13
+ * Prioridad por estado de entrega: lo NO entregado es lo urgente y va primero, sin importar
14
+ * su fecha; lo ya entregado/calificado baja al fondo. Es el orden que evita perder entregas.
15
+ */
16
+ function urgencyRank(t) {
17
+ switch (t.submission) {
18
+ case "not-submitted":
19
+ return 0;
20
+ case "unknown":
21
+ return 1;
22
+ case "submitted":
23
+ return 2;
24
+ case "graded":
25
+ return 3;
26
+ default:
27
+ return 1;
28
+ }
29
+ }
30
+ /**
31
+ * Ordena por urgencia: primero las pendientes (SIN ENTREGAR), y dentro de cada grupo por
32
+ * fecha de entrega ascendente (vencidas y próximas arriba; sin fecha al final del grupo).
33
+ */
34
+ function sortByUrgency(a, b) {
35
+ const ra = urgencyRank(a);
36
+ const rb = urgencyRank(b);
37
+ if (ra !== rb)
38
+ return ra - rb;
39
+ if (a.dueDate == null && b.dueDate == null)
40
+ return 0;
41
+ if (a.dueDate == null)
42
+ return 1;
43
+ if (b.dueDate == null)
44
+ return -1;
45
+ return a.dueDate - b.dueDate;
46
+ }
47
+ /**
48
+ * Tareas del "timeline" del estudiante vía core_calendar_get_action_events_by_timesort.
49
+ * Es lo que la app previa mostraba: SÓLO tareas accionables (futuras y sin entregar). Las
50
+ * tareas ya entregadas, vencidas o sin fecha NO salen aquí — por eso "se esconden".
51
+ */
52
+ export async function getUpcomingTasks(session, limitNum = 50) {
53
+ const timesortFrom = Math.floor(Date.now() / 1000);
54
+ const data = (await postAjax(session, "core_calendar_get_action_events_by_timesort", { timesortfrom: timesortFrom, limitnum: limitNum }));
55
+ const events = data?.events ?? [];
56
+ return events
57
+ .filter((raw) => raw.modulename === "assign")
58
+ .map((raw) => {
59
+ const e = raw;
60
+ const url = e.url ?? e.action?.url ?? null;
61
+ return {
62
+ id: e.id,
63
+ name: e.name,
64
+ courseId: e.course?.id ?? 0,
65
+ courseName: e.course?.fullname ?? "",
66
+ dueDate: e.timesort ?? e.timestart ?? null,
67
+ url,
68
+ description: null,
69
+ source: "calendar",
70
+ hidden: false,
71
+ submission: "unknown",
72
+ grade: null,
73
+ timeRemaining: null,
74
+ attachments: [],
75
+ dateConflict: false,
76
+ datesInDescription: [],
77
+ cmid: cmidFromUrl(url),
78
+ };
79
+ });
80
+ }
81
+ /** Convierte un módulo assign del estado del curso en una Task preliminar. */
82
+ function assignToTask(m, course, timeline) {
83
+ const inTimeline = timeline.get(m.cmid);
84
+ return {
85
+ id: m.cmid,
86
+ name: m.name,
87
+ courseId: course.id,
88
+ courseName: course.fullname,
89
+ dueDate: inTimeline?.dueDate ?? null,
90
+ url: m.url,
91
+ description: null,
92
+ source: "course-scan",
93
+ hidden: !inTimeline, // no aparece en el timeline del estudiante → oculta
94
+ submission: "unknown",
95
+ grade: null,
96
+ timeRemaining: null,
97
+ attachments: [],
98
+ dateConflict: false,
99
+ datesInDescription: [],
100
+ cmid: m.cmid,
101
+ };
102
+ }
103
+ /** Enriquecer una tarea scrapeando su página (estado de entrega, nota, fecha, tiempo restante). */
104
+ async function enrichTask(session, task) {
105
+ if (!task.url)
106
+ return task;
107
+ try {
108
+ const d = await getAssignDetail(session, task.url);
109
+ return {
110
+ ...task,
111
+ submission: d.submission,
112
+ grade: d.grade ?? task.grade,
113
+ // La fecha oficial de la propia tarea manda sobre la del calendario.
114
+ dueDate: d.dueDate ?? task.dueDate,
115
+ timeRemaining: d.timeRemaining,
116
+ description: d.description ?? task.description,
117
+ attachments: d.attachments,
118
+ dateConflict: d.dateConflict,
119
+ datesInDescription: d.datesInDescription,
120
+ };
121
+ }
122
+ catch {
123
+ return task; // no romper el barrido por un fallo puntual
124
+ }
125
+ }
126
+ /**
127
+ * Obtiene TODAS las tareas de todos los cursos, incluidas las OCULTAS (las que no aparecen en
128
+ * el timeline del estudiante). Estrategia validada para el Moodle de la UNSA:
129
+ * 1. core_courseformat_get_state por curso → descubre TODOS los módulos assign (fuente fiable;
130
+ * core_course_get_contents y mod_assign_get_assignments están bloqueadas aquí).
131
+ * 2. core_calendar_get_action_events_by_timesort → marca cuáles están en el timeline (no ocultas)
132
+ * y aporta su fecha de entrega exacta.
133
+ * 3. (enrich) scraping de mod/assign/view.php por tarea → estado de entrega, nota, tiempo restante.
134
+ */
135
+ export async function getAllTasks(session, opts = {}) {
136
+ const { concurrency = 5, enrich = true } = opts;
137
+ const [timelineTasks, courses] = await Promise.all([
138
+ getUpcomingTasks(session).catch(() => []),
139
+ opts.courses ? Promise.resolve(opts.courses) : getEnrolledCourses(session),
140
+ ]);
141
+ const timeline = new Map();
142
+ for (const t of timelineTasks)
143
+ if (t.cmid != null)
144
+ timeline.set(t.cmid, t);
145
+ const scanErrors = [];
146
+ // 1) Descubrir assigns por curso (concurrencia acotada).
147
+ const perCourse = await mapLimit(courses, concurrency, async (course) => {
148
+ try {
149
+ const state = await getCourseState(session, course.id);
150
+ return state.modules
151
+ .filter((m) => m.module === "assign" && m.uservisible)
152
+ .map((m) => assignToTask(m, course, timeline));
153
+ }
154
+ catch (err) {
155
+ scanErrors.push({
156
+ courseId: course.id,
157
+ courseName: course.fullname,
158
+ reason: err.message,
159
+ });
160
+ return [];
161
+ }
162
+ });
163
+ let tasks = perCourse.flat();
164
+ // 2) Enriquecer con estado de entrega/nota (scraping), concurrencia acotada.
165
+ if (enrich) {
166
+ tasks = await mapLimit(tasks, Math.max(concurrency, 6), (t) => enrichTask(session, t));
167
+ }
168
+ tasks.sort(sortByUrgency);
169
+ return { tasks, scanErrors };
170
+ }
171
+ /** Tareas de un solo curso (incluye ocultas), con estado de entrega. */
172
+ export async function getCourseTasks(session, courseId, courseName = "", opts = {}) {
173
+ const { enrich = true } = opts;
174
+ const [state, timelineTasks] = await Promise.all([
175
+ getCourseState(session, courseId),
176
+ getUpcomingTasks(session).catch(() => []),
177
+ ]);
178
+ const timeline = new Map();
179
+ for (const t of timelineTasks)
180
+ if (t.cmid != null)
181
+ timeline.set(t.cmid, t);
182
+ let tasks = state.modules
183
+ .filter((m) => m.module === "assign" && m.uservisible)
184
+ .map((m) => assignToTask(m, { id: courseId, fullname: courseName }, timeline));
185
+ if (enrich) {
186
+ tasks = await mapLimit(tasks, 6, (t) => enrichTask(session, t));
187
+ }
188
+ tasks.sort(sortByUrgency);
189
+ return tasks;
190
+ }
191
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/domain/tasks.ts"],"names":[],"mappings":"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"}