您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Get all content downloads of any nptel course. \n Adds a button on the nav bar to run the script.
// ==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 = false; /** @typedef {'assignment'|'material'|'book'|'transcript'|'lecture'|'video'|'notes'} 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 = "notes"; /**@type {Map<ITEM_TYPE, Boolean>}*/ const allowedTypes = new Map([ ["assignment", true], ["material", true], ["notes", true], ["book", true], ["transcript", true], ["lecture", true], ["video", false], ]); /** * @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; default: DEBUG && 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 { // 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) { 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 { 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(); })();