-
-
Save nicholastay/e6c108cc49bc297f50a1c417624e6601 to your computer and use it in GitHub Desktop.
// ==UserScript== | |
// @name Schoology quick-download files helper | |
// @namespace http://nicholastay.github.io/ | |
// @version 0.3.0 | |
// @author Nicholas Tay <[email protected]> | |
// @license MIT | |
// @icon https://i.imgur.com/QmmYqzZ.png | |
// @match *://app.schoology.com/* | |
// @match *://schoology.cgs.vic.edu.au/* | |
// @grant none | |
// @homepage https://gist.github.com/nicholastay/e6c108cc49bc297f50a1c417624e6601 | |
// @downloadURL https://gist.github.com/nicholastay/e6c108cc49bc297f50a1c417624e6601/raw/schoology-quickdl.user.js | |
// @noframes | |
// @require https://unpkg.com/[email protected]/es5.js | |
// ==/UserScript== | |
;(function() { | |
// lets try not to kill schoology: 10 per 5s | |
var limiter = new Bottleneck({ | |
reservoir: 10, | |
reservoirRefreshAmount: 100, | |
reservoirRefreshInterval: 5 * 1000 | |
}); | |
injectCSS(); | |
if (injectLabels()) | |
injectDownloadAll(); | |
injectLabelEventHandler(); | |
injectSubfolderHandler(); | |
injectGroupResources(); | |
console.log("[nexerq/quickDL] Initialized."); | |
function noop() {} | |
function injectCSS() { | |
$("head").append("<style type=\"text/css\">.nexerq-quickdl-style:hover { text-decoration: underline; cursor: pointer; }</style>"); | |
console.log("[nexerq/quickDL] CSS injected"); | |
} | |
function injectLabels(subfolder) { | |
var $contentFiles = $("#folder-contents-table"); | |
if (!$contentFiles || ($contentFiles.length < 1)) | |
return console.log("[nexerq/quickDL] Not a folder listing page, not injecting that."); | |
console.log("[nexerq/quickDL] Found folder listings."); | |
var $documents = $contentFiles.find(".type-document:not(.nexerq-quickdl-processed)"); | |
if (!$documents || ($documents.length < 1)) { | |
console.log("[nexerq/quickDL] No download documents found."); | |
return false; | |
} | |
console.log("[nexerq/quickDL] Found " + $documents.length + " documents to process"); | |
$documents.each(function() { | |
var $this = $(this); | |
var $attachment = $this.find(".attachments-file-name"); | |
var $link = $attachment.find("a"); | |
// mark as processed | |
$this.addClass("nexerq-quickdl-processed"); | |
// create dl btn | |
var $newBtn = $attachment.find(".attachments-file-size").clone().appendTo($attachment); | |
$newBtn.addClass("nexerq-quickdl-style"); | |
$newBtn.addClass("nexerq-quickdl-link"); | |
$newBtn.text("Download"); | |
$newBtn.attr("orig-link", $link.attr("href")); | |
// weird subfolder bullshit | |
var $subContent = $link.find(".infotip-content"); | |
$newBtn.attr("orig-name", ($subContent.length > 0 ? $subContent.text() : $link.text())); | |
if (subfolder) // detection for quickdl all - do not include these! | |
$newBtn.addClass("nexerq-quickdl-subfolder"); | |
}); | |
console.log("[nexerq/quickDL] Labels injected"); | |
return true; | |
} | |
function injectLabelEventHandler() { | |
$(document).on("click", ".nexerq-quickdl-link", function() { | |
var $this = $(this); | |
var origName = $this.attr("orig-name"); | |
var origLink = $this.attr("orig-link"); | |
// kinda hacky: ajax http get the other page | |
var req = function() { | |
console.log("[nexerq/quickDL] Attempting to ajax get the target page (" + origName + ")."); | |
limiter.submit($.ajax, origLink, { | |
success: function(data) { | |
var $dl = $(data).find(".attachments-file a:first"); | |
if (!$dl || ($dl.length < 1)) { | |
console.log("[nexerq/quickDL] File could not be found in success: " + origName); | |
return alert("[nexerq/quickDL] Sorry, this file (" + origName + ") cannot be quick-downloaded at this time. You may have to just open the page and download manually."); | |
} | |
// dl | |
invokeDownload(origName, $dl.attr("href")); | |
}, | |
error: function(jqXHR, textStatus, err) { | |
if (jqXHR.status === 429) {// 429 rate limited | |
console.log("[nexerq/quickDL] Rate limited 429 on " + origName + ". Retry in 10s"); | |
setTimeout(req, 30000); | |
} | |
} | |
}, noop); // noop cb | |
}; | |
req(); | |
}); | |
console.log("[nexerq/quickDL] Event handler registered"); | |
} | |
function invokeDownload(fileName, url) { | |
console.log("[nexerq/quickDL] Downloading " + fileName + "."); | |
// ugh - https://stackoverflow.com/questions/18451856/how-can-i-let-a-user-download-multiple-files-when-a-button-is-clicked | |
//var tmpLink = document.createElement("a"); | |
//tmpLink.style.display = "none"; | |
//document.body.appendChild(tmpLink); | |
//tmpLink.setAttribute("href", url); | |
//tmpLink.setAttribute("download", fileName); | |
//tmpLink.click(); | |
//document.body.removeChild(tmpLink); | |
window.open(url, fileName + " - nexerq/quickDL"); | |
} | |
function injectDownloadAll() { | |
$(".materials-top").append(`<span class="attachments-file-size gray nexerq-quickdl-all nexerq-quickdl-style">Quick-download all (non-recursive)</span>`); | |
$(".nexerq-quickdl-all").click(function() { | |
console.log("[nexerq/quickDL] Downloading all available files..."); | |
// oh dear... recursion | |
var $links = $(".nexerq-quickdl-link:not(.nexerq-quickdl-subfolder)"); | |
$links.each(function() { | |
$(this).click(); | |
}); | |
}); | |
console.log("[nexerq/quickDL] Download all injected"); | |
} | |
function injectSubfolderHandler() { | |
// attach to document | |
$(document).on("click", ".folder-contents-cell .folder-expander", function() { | |
var $this = $(this); | |
console.log("[nexerq/quickDL] Subfolder open detected"); | |
// wait for ajax to complete... | |
var waitCount = 0; | |
var $parentTd = $this.closest("td"); | |
var wait = setInterval(function() { | |
var $subtree = $parentTd.children(".folder-subtree"); | |
if ($subtree && $subtree.length > 0) { | |
clearInterval(wait); | |
if (injectLabels(true)) { | |
// inject dl all | |
$parentTd.find(".folder-title").first().append(`<span class="attachments-file-size gray nexerq-quickdl-all-subfolder nexerq-quickdl-style">Quick-download all</span>`); | |
$parentTd.find(".nexerq-quickdl-all-subfolder").click(function() { | |
$parentTd.find(".nexerq-quickdl-link").each(function() { | |
$(this).click(); | |
}); | |
}); | |
console.log("[nexerq/quickDL] Injected subfolder event and dl all"); | |
} | |
} | |
if (++waitCount > 5) { | |
console.log("[nexerq/quickDL] Subfolder open - something went wrong while waiting."); | |
clearInterval(wait); | |
} | |
}, 750); | |
}); | |
console.log("[nexerq/quickDL] Event handler for subfolder handling injected"); | |
} | |
// probably the worst implemented one | |
function injectGroupResources() { | |
if (!window.location.pathname.includes("/group") || !window.location.pathname.includes("/materials")) | |
return console.log("[nexerq/quickDL] Not a group resource page, not injecting that."); | |
// wait. AJAX PLS | |
var i = 0; | |
console.log("[nexerq/quickDL] Waiting for group page..."); | |
var wait = setInterval(function() { | |
var $groupResources = $("#collection-view"); | |
if (!$groupResources || ($groupResources.length < 1)) { | |
if (++i > 5) { | |
console.log("[nexerq/quickDL] Failed to find resources."); | |
clearInterval(wait); | |
} | |
return; | |
} | |
clearInterval(wait); | |
_injectGroupResources(); | |
}, 750); | |
} | |
function _injectGroupResources() { | |
var $groupResources = $("#collection-view"); | |
// event handler | |
$(document).on("click", ".nexerq-quickdl-group-link", function() { | |
var $resource = $(this).closest(".template-s-content-generic-post-docviewer"); | |
var resId = $resource.attr("id").replace("t-", ""); | |
var origName = $resource.find(".item-title").text(); | |
// ajax get template, then the ID docviewer and parse its config | |
// ... ughhhhhhhhhh | |
console.log("[nexerq/quickDL] Trying group resource: " + origName); | |
limiter.submit($.get, window.location.origin + "/template/" + resId, function(templateData) { | |
console.log("[nexerq/quickDL] Got template data: " + origName); | |
// time to docviewer | |
limiter.submit($.get, window.location.origin + $(templateData).find(".docviewer-iframe").attr("src"), function(docData) { | |
var $configFunc = $(docData).find("script:contains('pdfPath')"); | |
if (!$configFunc || ($configFunc.length < 1)) | |
return console.log("[nexerq/quickDL] Could not find pdfPath config: " + origName); | |
var _docConfig = $configFunc.text().match(/PdfTronWebViewer, ({.*"}"}})/); | |
if (!_docConfig) | |
return console.log("[nexerq/quickDL] Could not parse pdfConfig: " + origName); | |
var docConfig = JSON.parse(_docConfig[1]); | |
var customConfig = JSON.parse(docConfig.options.custom); | |
invokeDownload(origName, customConfig.downloadLink); | |
}); | |
}); | |
}); | |
var $resources = $groupResources.find(".template-s-content-generic-post-docviewer:not(.nexerq-quickdl-processed)"); | |
if (!$resources || ($resources.length < 1)) | |
return console.log("[nexerq/quickDL] No download-docviewers found."); | |
console.log("[nexerq/quickDL] Found " + $resources.length + " group resources to process"); | |
$resources.each(function() { | |
var $resource = $(this); | |
$resource.addClass("nexerq-quickdl-processed"); | |
$resource.find(".resource-details").append(`<span class="nexerq-quickdl-style nexerq-quickdl-group-link"> · Download</span>`); | |
}); | |
} | |
})(); |
Ok, I guess I abandoned this last year, oh well.
I have installed GreaseMonkey, but nothing seems to happen after visiting schoology
Hello, I installed ViolentMonkey on Chrome, but it seems not working for me. I saw there is another package requirement [email protected]. I'm not familiar with these .js scripts. would you mind sharing how to set up the whole flow to crawl materials in the Schoology? Thanks a lot!
@k2973363 if you still need to use the script, add this line in the ==UserScript==
section:
// @match *://*.schoology.com/*
This script was not originally made to run on all schoology.com domains, but that line will fix it.
This userscript is a great utility for anyone managing multiple resources on Schoology. Automating downloads from folders or group resources can seriously save time — especially for students in large districts like LAUSD, where Schoology is a primary platform. For those accessing it through LAUSD, schoologylausd.com serves as a central point for login and support, so tools like this script pair well with that ecosystem.
Thanks for putting this together, Nicholas — really thoughtful approach to enhancing the user experience.
@theg00s3 Apologies for the late reply - to use this script, you must have a userscript manager present in your browser. For example, you may use ViolentMonkey in Chrome.