Skip to content

Instantly share code, notes, and snippets.

@KorigamiK
Last active February 5, 2025 15:20
Show Gist options
  • Save KorigamiK/51ec55038265725b9d7cc9921f047064 to your computer and use it in GitHub Desktop.
Save KorigamiK/51ec55038265725b9d7cc9921f047064 to your computer and use it in GitHub Desktop.
A user script for tamper/violent monkey to get all the course content of any course on nptel.ac.in
# %%
import json
# %%
PREAMBLE = '''---
title: Organizational Behaviour
papersize: a4
numbersections: false
documentclass: scrartcl
toc: false
toc-title: 'Contents'
geometry:
- margin=0.5in
colorlinks: true
toccolor: Mahogany
linkcolor: BrickRed
urlcolor: NavyBlue
header-includes:
- |
```{=latex}
\\raggedbottom % or \\flushbottom
```
- |
```{=latex}
\\usepackage{fvextra}
\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\}, breaklines, breaknonspaceingroup, breakanywhere}
\\fvset{breaklines}
\\fvset{breaknonspaceingroup}
\\fvset{breakanywhere}
```
---
''';
with open("quiz.md", "w") as w:
w.write(PREAMBLE)
with open("./quiz.json", "r") as f:
data = json.load(f)
for week in data:
for quiz in week:
md = quiz["data"]
w.writelines(md)
# %%
// ==UserScript==
// @name NPTEL-Save-Sourse-Content
// @match https://onlinecourses.nptel.ac.in/*
// @grant GM_setValue
// @license MIT
// @description Get all content downloads of any nptel course. \n Adds a button on the nav bar to run the script.
// @version 0.4.4
// @namespace https://greasyfork.org/users/941655
// ==/UserScript==
const courseContentWeeks = [];
const DEBUG = true;
/** @typedef {'assignment'|'material'|'book'|'transcript'|'lecture'|'video'|'notes'|'quiz'} ITEM_TYPE */
/**
* @typedef {Object} Course
* @property {string} title
* @property {ITEM_TYPE} type
* @property {string} href
* @property {string} data
* @property {string} meta
*/
/**@type {ITEM_TYPE}*/
const DEBUG_TYPE = "quiz";
/**@type {Map<ITEM_TYPE, Boolean>}*/
const allowedTypes = new Map([
["assignment", true],
["material", true],
["notes", true],
["book", true],
["transcript", true],
["lecture", true],
["quiz", false],
["video", false],
]);
function generateStudyGuide(questions, answers, title) {
let markdown = `\n# ${title}\n\n`;
markdown += `## Questions and Answers\n\n`;
questions.forEach((question, index) => {
// Find matching answer data
const answerData = answers.find((a) => {
const quidParts = question.quid?.toString().split(".");
const questionId = quidParts?.[quidParts.length - 1];
return a.quid?.toString() === questionId;
});
if (!answerData) {
console.log("No answer data found for question:", question);
return;
}
markdown += `### Question ${index + 1}\n\n`;
if (question.imageUrl) {
markdown += `![Question Image](${encodeURI(question.imageUrl)})\n\n`;
}
markdown += `${question.questionText}\n\n`;
if (question.choices) {
markdown += `**Options:**\n\n`;
question.choices.forEach((choice) => {
const answerChoice = answerData.choices?.find(
(c) => c.id === choice.dataIndex,
);
const isCorrect = answerChoice?.isCorrect || false;
markdown += `${isCorrect ? "✓" : "✗"} ${choice.text}\n\n`;
});
const correctAnswers = answerData.choices
?.filter((c) => c.isCorrect)
.map((c) => c.text);
if (correctAnswers?.length > 0) {
markdown += `**Correct Answer${correctAnswers.length > 1 ? "s" : ""}:** ${correctAnswers.join(", ")}\n\n`;
}
}
if (answerData.graders) {
const grader = answerData.graders[0];
if (grader) {
let answer = "";
if (grader.matcher === "numeric") {
answer = grader.response;
} else if (grader.matcher === "range_match") {
answer = `Range: ${grader.response}`;
} else {
answer = grader.response;
}
if (answer) {
markdown += `**Correct Answer:** ${answer}\n\n`;
}
}
}
if (question.points) {
markdown += `**Points:** ${question.points}\n\n`;
}
if (answerData.feedback) {
markdown += `**Feedback:** ${answerData.feedback}\n\n`;
}
markdown += `---\n\n`;
});
return markdown;
}
function parseQuestionData(rawData) {
const parsedQuestions = [];
for (const key in rawData) {
const item = rawData[key];
if (typeof item !== "object" || !item.quid) continue;
const question = {
quid: item.quid,
hint: item.hint,
weight: item.weight,
defaultFeedback: item.defaultFeedback,
};
if (item.choices) {
// For multiple choice questions
question.choices = item.choices.map((choice, index) => ({
id: index,
text: choice.text,
score: choice.score,
isCorrect: choice.score > 0,
feedback: choice.feedback,
}));
question.type = "multiple_choice";
} else if (item.graders) {
// For numeric/text questions
question.graders = item.graders.map((g) => ({
matcher: g.matcher,
response: g.response,
score: g.score,
feedback: g.feedback,
}));
question.type = "graded";
}
parsedQuestions.push(question);
}
return parsedQuestions;
}
// Rest of the code remains unchanged
/**
* @param {Course} course
*/
const getContent = async (course) => {
let ret = null;
let meta = null;
switch (course.type) {
case "assignment":
case "material":
{
const body = await (await fetch(course.href)).text();
const dom = new DOMParser().parseFromString(body, "text/html");
ret = dom
.querySelector(".gcb-lesson-content a[target]")
?.getAttribute("href");
if (!ret) {
ret = dom
.querySelector(
'iframe[src^="https://drive"], iframe[src^="https://docs.google.com"]',
)
?.getAttribute("src");
!ret && DEBUG && console.log("[Error] No link found");
}
}
break;
case "transcript":
case "lecture":
case "book":
case "notes":
{
const body = await (await fetch(course.href)).text();
const dom = new DOMParser().parseFromString(body, "text/html");
const a = dom.querySelector(".gcb-lesson-content a[target]");
if (a) {
course.title = a.parentElement.textContent || course.type;
ret = a.href;
} else DEBUG && console.log("[Error] No link found");
}
break;
case "video":
{
const body = await (await fetch(course.href)).text();
const videoId = body.match(/loadIFramePlayer\(['"](.+)['"],/)?.[1];
const dom = new DOMParser().parseFromString(body, "text/html");
meta = dom.querySelector("div.gcb-lesson-content > span")?.innerText;
!videoId && DEBUG && console.log("[Error] No link found");
ret = `https://www.youtube.com/watch?v=${videoId}`;
}
break;
case "quiz":
{
console.log("Processing quiz:", course.title);
const body = await (await fetch(course.href)).text();
const dom = new DOMParser().parseFromString(body, "text/html");
console.log(
"Raw DOM questions:",
dom.querySelectorAll(
".qt-mc-question, .qt-nm-question, .qt-sa-question",
),
);
// Extract question data from scripts
const scripts = Array.from(dom.querySelectorAll("script:not([src])"))
.map((s) => s.textContent)
.filter((s) => s.includes("questionData"));
const sandbox = {
window: {
atob: atob,
history: { replaceState: () => {} },
location: { href: "" },
MathJax: { Hub: { Queue: () => {} } },
},
questionData: {},
assessmentTagMessages: {},
JSON: JSON,
console: console,
is_practice: false,
$: window.jQuery, // If jQuery is needed
};
scripts.forEach((script) => {
try {
const fn = new Function(
"window",
"questionData",
"assessmentTagMessages",
"JSON",
"console",
"is_practice",
script,
);
fn(
sandbox.window,
sandbox.questionData,
sandbox.assessmentTagMessages,
JSON,
console,
sandbox.is_practice,
);
} catch (error) {
console.error("Error executing script:", error);
}
});
// Parse question data
const questionData = parseQuestionData(sandbox.questionData);
console.log("Parsed question data:", questionData);
// Extract questions from DOM
const domQuestions = Array.from(
dom.querySelectorAll(
".qt-mc-question, .qt-nm-question, .qt-sa-question",
),
);
const parsedDomQuestions = domQuestions.map((q) => {
const questionRow = q.closest(".gcb-question-row");
const points = parseInt(
questionRow
?.querySelector(".qt-points")
?.textContent?.match(/\d+/)?.[0] || "0",
);
const questionDiv = q.querySelector(".qt-question");
const questionText = questionDiv?.textContent?.trim();
const questionImage = questionDiv?.querySelector("img");
const imageUrl = questionImage?.src;
const quid = q.id;
if (q.classList.contains("qt-mc-question")) {
const choices = Array.from(
q.querySelectorAll(".qt-choices div"),
).map((c) => ({
text: c.textContent?.trim(),
dataIndex: parseInt(
c.querySelector("input")?.getAttribute("data-index") || "0",
),
}));
return { quid, points, questionText, imageUrl, choices };
}
return { quid, points, questionText, imageUrl };
});
console.log("Parsed DOM questions:", parsedDomQuestions);
// Generate study guide
ret = generateStudyGuide(
parsedDomQuestions,
questionData,
course.title,
);
}
break;
default:
console.log("UNIMPLEMENTED type", course);
break;
}
DEBUG && console.log(course.title);
return { ...course, data: ret, meta };
};
/**
* @param {ITEM_TYPE} type
* @param {Array} week
* @param {Object} content
*/
const appendContent = (type, week, content) => {
if (DEBUG) {
if (type === DEBUG_TYPE) {
week.push(content);
}
} else if (allowedTypes.get(type)) {
week.push(content);
}
};
const fetchContent = async () => {
const nodes = document.querySelectorAll(
"div[id^=unit_navbar] > ul[id^=subunit_navbar]",
);
nodes.forEach((el) => {
const week = [];
const tags = el.querySelectorAll("li > div > a");
tags.forEach((tag) => {
const thing = tag.textContent.toLowerCase();
/**@type {ITEM_TYPE}*/
let type;
if (thing.includes("solution")) {
type = "assignment";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (thing.includes("lecture notes")) {
type = "notes";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (thing.includes("course material")) {
type = "material";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (thing.includes("book")) {
type = "book";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (thing.includes("transcript")) {
type = "transcript";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (thing.includes("lecture material")) {
type = "lecture";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (
thing.match(/lec(ture)? ?\d+ ?:/) !== null ||
thing.match(/part \d/) !== null
) {
type = "video";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else if (thing.match(/quiz|practice:/) !== null) {
type = "quiz";
appendContent(type, week, {
href: tag.getAttribute("href"),
title: tag.textContent,
type,
});
} else {
// DEBUG && console.log("UNKNOWN thing", thing);
}
});
week.length && courseContentWeeks.push(week);
});
await courseContents();
};
const courseContents = async () => {
const promises = [];
const total = courseContentWeeks.flat().length;
for (let weekIdx = 0; weekIdx < courseContentWeeks.length; ++weekIdx) {
for (let i = 0; i < courseContentWeeks[weekIdx].length; ++i) {
// if (weekIdx === 1) {
promises.push(updateContent(weekIdx, i, total));
// }
}
}
await Promise.all(promises);
};
const updateContent = async (weekIdx, i, total) => {
const el = courseContentWeeks[weekIdx][i];
courseContentWeeks[weekIdx][i] = await getContent(el);
incrementProgress(total);
};
/**
* Builds a table from the given objects.
* @param {Array<String>} labels
* @param {Array} objects
* @param {HTMLElement} container
*/
function buildTable(labels, objects, container) {
container.querySelector("table")?.remove();
const table = document.createElement("table");
table.style.textAlign = "center";
table.style.width = "100%";
const thead = document.createElement("thead");
const tbody = document.createElement("tbody");
const theadTr = document.createElement("tr");
for (let i = 0; i < labels.length; ++i) {
const theadTh = document.createElement("th");
theadTh.innerHTML = labels[i].toUpperCase();
theadTr.appendChild(theadTh);
}
thead.appendChild(theadTr);
table.appendChild(thead);
for (let j = 0; j < objects.length; ++j) {
const tbodyTr = document.createElement("tr");
for (let k = 0; k < labels.length; ++k) {
const tbodyTd = document.createElement("td");
tbodyTd.style.padding = "10px";
if (objects[j][labels[k]]?.startsWith("http")) {
const a = document.createElement("a");
a.href = objects[j][labels[k]];
a.target = "_blank";
a.innerHTML = "Open Link";
if (objects[j]["meta"]) {
const p = document.createElement("p");
p.innerHTML = objects[j]["meta"];
tbodyTd.appendChild(p);
}
tbodyTd.appendChild(a);
} else if (objects[j].type === "quiz" && labels[k] === "data") {
// Format quiz data for better display
const pre = document.createElement("pre");
pre.style.textAlign = "left";
pre.style.whiteSpace = "pre-wrap";
pre.innerHTML = objects[j][labels[k]];
tbodyTd.appendChild(pre);
} else {
tbodyTd.innerHTML = objects[j][labels[k]];
}
tbodyTr.appendChild(tbodyTd);
}
tbody.appendChild(tbodyTr);
}
table.appendChild(tbody);
container.prepend(table);
}
const nav = document.querySelector(".gcb-aux");
const button = document.createElement("button");
const progressDiv = document.createElement("div");
progressDiv.appendChild(document.createTextNode("Please wait..."));
const progress = document.createElement("span");
progressDiv.appendChild(progress);
const itemListDiv = document.createElement("div");
Array.from(allowedTypes.keys()).forEach((item) => {
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.id = item;
checkbox.checked = allowedTypes.get(item);
checkbox.addEventListener("change", () => {
allowedTypes.set(item, checkbox.checked);
});
const label = document.createElement("label");
label.htmlFor = item;
label.textContent = item;
itemListDiv.appendChild(checkbox);
itemListDiv.appendChild(label);
itemListDiv.appendChild(document.createElement("br"));
});
const setProgress = (val) => {
progress.innerText = `${val.toPrecision(2)}%`;
};
const incrementProgress = (total) => {
const val = parseFloat(progress.innerText.replace("%", ""));
setProgress(val + 100 / total);
};
button.innerHTML = "Run Script";
progressDiv.style.display = "none";
button.onclick = async () => {
console.log("scriptMain");
setProgress(0);
courseContentWeeks.length = 0;
progressDiv.style.display = "block";
await fetchContent();
progressDiv.style.display = "none";
console.table(courseContentWeeks.flat());
buildTable(
["title", "type", "data"],
courseContentWeeks.flat(),
document.getElementById("gcb-main-body"),
);
console.log(courseContentWeeks);
};
nav.appendChild(button);
nav.appendChild(progressDiv);
nav.appendChild(itemListDiv);
(async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.clear();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment