// ==UserScript==
// @name 下載北科i學員PDF
// @namespace http://tampermonkey.net/
// @version 1.0.2
// @description 僅供PDF下載
// @author Umeow
// @match https://istudy.ntut.edu.tw/learn/index.php
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
const parser = new DOMParser();
const ErrorFileChar = [ "/" , "|" ,'\\',"?",'"' ,'*' ,":" ,"<" ,">" , "/" , ":"];
async function GM_fetch(url, {method = "get", headers} = {}) { //source: https://github.com/AlttiRi/gm_fetch
return new Promise((resolve, _reject) => {
const blobPromise = new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url,
method,
headers,
responseType: "blob",
onload: response => resolve(response.response),
onerror: reject,
ontimeout: reject,
onreadystatechange: onHeadersReceived
});
});
blobPromise.catch(_reject);
function onHeadersReceived(response) {
const {
readyState, responseHeaders, status, statusText
} = response;
if (readyState === 2) { // HEADERS_RECEIVED
const headers = parseHeaders(responseHeaders);
resolve({
headers,
status,
statusText,
ok: status.toString().startsWith("2"),
arrayBuffer: () => blobPromise.then(blob => blob.arrayBuffer()),
blob: () => blobPromise,
json: () => blobPromise.then(blob => blob.text()).then(text => JSON.parse(text)),
text: () => blobPromise.then(blob => blob.text()),
});
}
}
});
}
function parseHeaders(headersString) {
class Headers {
get(key) {
return this[key.toLowerCase()];
}
}
const headers = new Headers();
for (const line of headersString.trim().split("\n")) {
const [key, ...valueParts] = line.split(":"); // last-modified: Fri, 21 May 2021 14:46:56 GMT
headers[key.trim().toLowerCase()] = valueParts.join(":").trim();
}
return headers;
}
const saveData = (function () {
const a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
return function (url, fileName) {
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
};
}());
const GET = (url) => {
return new Promise((resolve, reject) => {
$.ajax({
url,
success: (data, status) => {
resolve({data, status});
}
});
});
}
const fetchGET = async (url, headers = {}, args = {}) => {
const res = await fetch(url, {
...args,
headers: {
...headers,
},
method: "GET"
});
return res;
}
const fetchPOST = async (url, data, headers) => {
const res = await fetch(url, {
body: new URLSearchParams(data).toString(),
headers: {
...headers,
"Content-Type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*"
},
method: "POST",
});
return res;
}
const get_cid = async () => {
const cidURL = "https://istudy.ntut.edu.tw/learn/path/launch.php";
const cidResponse = await GET(cidURL);
const cidHTML = parser.parseFromString(cidResponse['data'], 'text/html');
const cidScriptHTML = cidHTML.getElementsByTagName('script')[0].innerHTML;
const cidLine = cidScriptHTML.split("\n").filter(str => str.includes('cid'))[0];
const cidStartIndex = cidLine.indexOf('/learn');
const cidResultURL = 'https://istudy.ntut.edu.tw' + cidLine.slice(cidStartIndex).slice(0, -3);
const cidResultURLObj = new URL(cidResultURL);
const cid = cidResultURLObj.searchParams.get('cid');
return cid;
}
const getDownloadArguments = async (cid) => {
const URL = `https://istudy.ntut.edu.tw/learn/path/pathtree.php?cid=${cid}`
const DownloadData = {
'is_player' : false,
'href' : '',
'prev_href' : '',
'prev_node_id' : '',
'prev_node_title' : '',
'is_download' : false,
'begin_time' : '',
'course_id' : '',
'read_key' : ''
}
const Response = await GET(URL);
const HTML = parser.parseFromString(Response['data'], 'text/html');
const FormElement = HTML.getElementById('fetchResourceForm');
const InputList = [...FormElement.getElementsByTagName('input')];
InputList.forEach((InputElement) => {
const key = InputElement.getAttribute('name');
if(key === "is_download" || key === "is_player") return;
const value = InputElement.getAttribute('value') || '';
DownloadData[key] = value;
});
return DownloadData
}
const getFileList = async () => {
const FileList = [];
const FileListURL = 'https://istudy.ntut.edu.tw/learn/path/SCORM_loadCA.php';
const FileListResponse = await GET(FileListURL);
const FileListXML = FileListResponse['data'];
const FileListItems = [...FileListXML.getElementsByTagName('item')].filter((ele) => ele.getAttribute('identifierref'));
const FileListElements = [...FileListXML.getElementsByTagName('resource')].filter((ele) => ele.getAttribute('identifier'));
FileListElements.forEach((element) => {
const identifier = element.getAttribute('identifier');
const href = '@' + element.getAttribute('href');
const item = FileListItems.filter((ele) => ele.getAttribute('identifierref') === identifier)[0];
const name = item.getElementsByTagName('title')[0].innerHTML.split("\t")[0].replace("\n" , "");
const file = { href, name };
FileList.push(file);
});
return FileList;
}
const DownloadFile = async (DownloadData, file, btn) => {
btn.disabled = true;
try {
let filename = file['name'];
while(ErrorFileChar.map( c => filename.includes(c) ).filter(bool => bool).length) ErrorFileChar.forEach( c => filename = filename.replace(c, ' '));
DownloadData['href'] = file['href'];
const URL = "https://istudy.ntut.edu.tw/learn/path/SCORM_fetchResource.php";
const Response = await fetchPOST(URL, DownloadData);
const ResponseText = await Response.text();
const ResponseLocation = ResponseText.slice(26, -12);
console.log("Location: ", ResponseLocation);
if(!ResponseLocation.includes("viewPDF.php")) throw "not PDF";
const ViewPDFURL = 'https://istudy.ntut.edu.tw/learn/path/' + ResponseLocation;
const ViewPDF = await GET(ViewPDFURL);
const getPDFLine = ViewPDF['data'].split('\n').filter(str => str.includes('getPDF.php'))[0];
const StartIndex = getPDFLine.indexOf('"');
const EndIndex = getPDFLine.lastIndexOf('"');
const getPDFURL = getPDFLine.slice(StartIndex + 1, EndIndex);
const PDFResponse = await GM_fetch('https://istudy.ntut.edu.tw/learn/path/' + getPDFURL, {
headers: {
referer: ViewPDFURL
}
});
console.log(PDFResponse);
const PDFBlob = await PDFResponse.blob();
const PDFLink = window.URL.createObjectURL(PDFBlob);
saveData(PDFLink, filename);
} catch(err) { alert("此檔案並非 PDF") }
btn.disabled = false;
}
const main = async () => {
try {
const cid = await get_cid();
const DownloadData = await getDownloadArguments(cid);
const FileList = await getFileList();
const doc = document.createElement("html");
const head = document.createElement("head");
const meta = document.createElement("meta");
meta["charset"] = "utf-8";
head.appendChild(meta);
const body = document.createElement("body");
body.style = 'display: flex;flex-direction: column;'
doc.appendChild(head);
doc.appendChild(body);
FileList.forEach(file => {
const base = document.createElement("div");
const span = document.createElement("span");
span.innerHTML = file['name'];
span.style = "margin-right: 10px;";
const btn = document.createElement("button");
btn.innerHTML = '下載';
btn.addEventListener("click", () => {
DownloadFile(DownloadData, file, btn);
});
base.innerHTML += "</br>";
base.appendChild(span);
base.appendChild(btn);
body.appendChild(base);
});
const new_window = window.open('', 'i學員檔案列表', config='height=500,width=500');
new_window.document.body.appendChild(doc);
} catch(err) {
alert("請確認是否位於正確的頁面");
return;
}
}
GM_registerMenuCommand('擷取目前頁面的檔案', main, 'r');