Skip to content

Instantly share code, notes, and snippets.

@nicholastay
Last active June 21, 2024 13:40
Show Gist options
  • Save nicholastay/e6c108cc49bc297f50a1c417624e6601 to your computer and use it in GitHub Desktop.
Save nicholastay/e6c108cc49bc297f50a1c417624e6601 to your computer and use it in GitHub Desktop.
Schoology userscript - Quickly download files off folder browsers (press raw to install) - Be sure to allow popups!
// ==UserScript==
// @name Schoology quick-download files helper
// @namespace http://nicholastay.github.io/
// @version 0.3.0
// @author Nicholas Tay <nexerq@gmail.com>
// @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/bottleneck@2.13.0/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>`);
});
}
})();
@theg00s3
Copy link

This isn't working for me. How did you use this? I clicked raw and it just lead me to the raw text. When I download the file and run it, I get the error line 128, char 32 invalid character

@nicholastay
Copy link
Author

nicholastay commented Nov 6, 2018

This isn't working for me. How did you use this? I clicked raw and it just lead me to the raw text. When I download the file and run it, I get the error line 128, char 32 invalid character

@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.

@theg00s3
Copy link

theg00s3 commented Apr 4, 2019

Ok, I guess I abandoned this last year, oh well.
I have installed GreaseMonkey, but nothing seems to happen after visiting schoology

@jamestclin
Copy link

Hello, I installed ViolentMonkey on Chrome, but it seems not working for me. I saw there is another package requirement bottleneck@2.13.0. 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!

@soarn
Copy link

soarn commented Mar 1, 2021

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment