下載北科i學員PDF

僅供PDF下載

目前为 2023-12-12 提交的版本。查看 最新版本

// ==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');