- // ==UserScript==
- // @name IEEE Paper Downloader
- // @namespace https://ieeexplore.ieee.org/
- // @version 1.2
- // @description IEEE Paper Downloader is a Greasemonkey script that enables batch downloading of papers. It bypasses the IEEE Xplore's limit on downloading up to 10 papers at a time, allowing you to download more than 10 papers in one go.
- // @author OccDeser
- // @match https://ieeexplore.ieee.org/*/proceeding*
- // @grant GM_xmlhttpRequest
- // @grant GM_download
- // @license MIT
- // ==/UserScript==
-
- const DOWNLOAD_INTERVAL = 1000; // Download interval in milliseconds
-
- const CSS = `
- .paper-downloader {
- margin-left: 15px;
- margin-right: 15px;
- padding-top: 15px;
- padding-bottom: 15px;
- background-color: #f5f5f5;
- }
-
- .paper-downloader span {
- padding: 5px;
- }
-
- .ipd-modal-background {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5); /* Gray background with 50% transparency */
- display: flex;
- justify-content: center; /* Horizontal centering */
- align-items: center; /* Vertical centering */
- z-index: 999; /* Place modal on top */
- flex-direction: column;
- }
-
- .ipd-modal-body {
- width: 75%;
- padding: 20px;
- background: #fff;
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
- }
-
- .ipd-modal-content {
- height: 450px;
- overflow-y: auto;
- }
- `
-
- const HTML = `
- <div class="paper-downloader">
- <span>
- <xpl-conference-toc-dashboard>
- <strong class="text-base-md-lh">
- IEEE Paper Downloader
- </strong>
- </xpl-conference-toc-dashboard>
- </span>
- <span>
- <xpl-conference-toc-dashboard>
- <strong class="text-base-md-lh" id="ipd-strong-countdown">
- </strong>
- </xpl-conference-toc-dashboard>
- </span>
- <span>
- <xpl-export-search-results>
- <button class="xpl-toggle-btn" id="ipd-button-showall">
- <a class="text-white">
- Show All
- </a>
- </button>
- </xpl-export-search-results>
- </span>
- <span>
- <xpl-export-search-results>
- <button class="xpl-toggle-btn" id="ipd-button-select">
- <a class="text-white">
- Select
- </a>
- </button>
- </xpl-export-search-results>
- </span>
- <span>
- <xpl-export-search-results>
- <button class="xpl-toggle-btn" id="ipd-button-download">
- <a class="text-white">
- Download
- </a>
- </button>
- </xpl-export-search-results>
- </span>
- </div>
- `
-
- var allPapers = {};
- var selectedPapers = {};
-
- const createModal = (content, onClose) => {
- const modalHtml = `
- <div class="ipd-modal-background">
- <div class="ipd-modal-body">
- <div class="ipd-modal-content">
- ${content}
- </div>
- <xpl-export-search-results>
- <button class="xpl-toggle-btn" id="ipd-modal-close">
- <a class="text-white">Close</a>
- </button>
- </xpl-export-search-results>
- </div>
- </div>
- `
-
- let modalElement = document.createElement('div');
- modalElement.innerHTML = modalHtml;
- document.body.appendChild(modalElement);
-
- // Bind listener
- let modalCloseButton = document.getElementById('ipd-modal-close');
- modalCloseButton.addEventListener('click', () => {
- modalElement.remove();
- onClose();
- });
- }
-
- const downloadPapers = () => {
- const downloadLinks = [];
- for (let id in selectedPapers) {
- if (selectedPapers[id]) {
- let url = new URL("https://ieeexplore.ieee.org" + allPapers[id].href);
- const arnumber = url.searchParams.get('arnumber');
- downloadLinks.push(`https://ieeexplore.ieee.org/stampPDF/getPDF.jsp?tp=&arnumber=${arnumber}&isBulkDownload=true`);
- }
- }
- console.log(downloadLinks);
-
- // Show download progress
- createModal(
- '<div id="ipd-modal-progress"></div>',
- () => {
- console.log('Modal closed');
- }
- );
-
- let progress = document.getElementById('ipd-modal-progress');
- let progressCounter = 0;
-
- const progressInfo = (message) => {
- let messageBox = document.createElement('div');
- messageBox.innerHTML = message;
- messageBox.style = "margin-bottom: 5px;";
- progress.insertBefore(messageBox, progress.firstChild);
- }
-
- const progressTop = (message) => {
- let topMessageBox = document.getElementById('ipd-modal-progress-top');
- if (topMessageBox) {
- topMessageBox.remove();
- }
-
- let messageBox = document.createElement('div');
- messageBox.id = 'ipd-modal-progress-top';
- messageBox.innerHTML = message;
- messageBox.style = "margin-bottom: 5px; font-weight: bold;";
- progress.insertBefore(messageBox, progress.firstChild);
- }
- progressInfo(`Downloading ${downloadLinks.length} papers...`);
-
- // Store downloaded content
- const downloadedContent = [];
-
- // Use GM_xmlhttpRequest to fetch link content
- const fetchContent = (url) => {
- let filename = new URL(url).searchParams.get('arnumber') + '.pdf';
-
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: url,
- responseType: 'arraybuffer',
- onload: response => resolve(response.response),
- onerror: error => reject(error),
- onprogress: progressEvent => {
- let size = progressEvent.loaded / 1024 / 1024;
- progressTop(`${filename} Downloading: ${size.toFixed(2)}MB`);
- }
- });
- });
- }
-
- // Iterate through download links and fetch content
- const fetchAndDownload = async () => {
- for (const link of downloadLinks) {
- try {
- let filename = new URL(link).searchParams.get('arnumber') + '.pdf';
- const content = await fetchContent(link);
- downloadedContent.push({ filename: filename, content });
- progressCounter++;
- progressInfo(`[${progressCounter}/${downloadLinks.length}] Fetched content from: ${link}`);
- progressTop(`${filename} Downloaded, continuing in ${DOWNLOAD_INTERVAL / 1000} seconds...`);
- await new Promise(resolve => setTimeout(resolve, DOWNLOAD_INTERVAL));
- } catch (error) {
- console.error(`Error fetching content from link: ${error}`);
- }
- }
-
- // Compress all content into a single zip file and download
- const zip = new JSZip();
- for (const item of downloadedContent) {
- zip.file(item.filename, item.content);
- }
-
- zip.generateAsync({ type: 'blob' })
- .then(blob => {
- GM_download({
- url: URL.createObjectURL(blob),
- name: 'papers.zip'
- });
- });
- }
-
- fetchAndDownload();
- }
-
- const selectPapers = () => {
- const xpathExpression = '//div[@class="subid-container"]//xpl-access-type-icon/span[not(contains(@class, "locked"))]/../../../..';
- let paperResults = document.evaluate(xpathExpression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
- if (paperResults.snapshotLength > 0) {
- let paperElement = paperResults.snapshotItem(0);
- let paperHref = paperElement.getElementsByTagName('xpl-view-pdf')[0].getElementsByTagName('a')[0].getAttribute('href');
- let paperTitle = paperElement.getElementsByTagName('h2')[0].textContent;
- let id = 0 + '-' + paperTitle.slice(0, 16) + '-' + paperHref.slice(-8);
- let first_id = Object.keys(allPapers).filter((key) => key.startsWith('0'))[0];
-
- // Check if the page has been updated
- if (Object.keys(allPapers).length !== paperResults.snapshotLength || id !== first_id) {
- allPapers = {};
- selectedPapers = {};
- for (let i = 0; i < paperResults.snapshotLength; i++) {
- let paperElement = paperResults.snapshotItem(i);
- let paperHref = paperElement.getElementsByTagName('xpl-view-pdf')[0].getElementsByTagName('a')[0].getAttribute('href');
- let paperTitle = paperElement.getElementsByTagName('h2')[0].textContent;
- let id = i + '-' + paperTitle.slice(0, 16) + '-' + paperHref.slice(-8);
- allPapers[id] = {
- 'title': paperTitle,
- 'href': paperHref,
- 'element': paperElement
- };
- selectedPapers[id] = true;
- }
- }
- }
-
- // Create modal
- createModal(
- '<form id="ipd-select-dynamic-form"></form>',
- () => {
- let counter = 0;
- for (let id in selectedPapers) {
- if (selectedPapers[id]) {
- counter++;
- }
- }
- console.log(`Selected ${counter} papers`);
- }
- )
-
- // Create select form
- let dynamicForm = document.getElementById('ipd-select-dynamic-form');
- let items = []
- for (let id in allPapers) {
- items.push({ id, title: allPapers[id].title });
- }
- items.forEach(function (item, _) {
- var checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.name = item.title;
- checkbox.value = item.id;
- checkbox.checked = selectedPapers[item.id];
- checkbox.addEventListener('change', function () {
- selectedPapers[checkbox.value] = checkbox.checked;
- });
-
- var label = document.createElement('label');
- label.appendChild(checkbox);
-
- var titleBox = document.createElement('span');
- titleBox.appendChild(document.createTextNode(item.title));
- titleBox.style = "margin-left: 5px;";
- label.appendChild(titleBox);
-
- var itemDiv = document.createElement('div');
- itemDiv.style = "margin-bottom: 5px;";
- itemDiv.appendChild(label);
-
- dynamicForm.appendChild(itemDiv);
- });
- }
-
- const showAll = () => {
- console.log("showAll");
-
- // Get the current rowsPerPage
- const url = new URL(window.location.href);
- const current_rows = eval(url.searchParams.get('rowsPerPage'))
-
- // Get the total number of rows
- const xpathExpression = "//span[contains(., 'Showing')]/span[2]/text()";
- const result = document.evaluate(xpathExpression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
- if (result.snapshotLength == 0) {
- console.log("Cannot find total rows");
- return;
- }
-
- const total_rows = eval(result.snapshotItem(0).textContent);
-
- if (current_rows >= total_rows) {
- console.log("Already showing all");
- return;
- }
-
- url.searchParams.set('rowsPerPage', total_rows.toString());
- const modifiedURL = url.toString();
- window.location.href = modifiedURL;
- }
-
- const bindListener = () => {
- let buttonSelect = document.getElementById('ipd-button-select');
- let buttonDownload = document.getElementById('ipd-button-download');
- let buttonShowAll = document.getElementById('ipd-button-showall');
-
- buttonSelect.addEventListener('click', selectPapers);
- buttonDownload.addEventListener('click', downloadPapers);
- buttonShowAll.addEventListener('click', showAll);
- }
-
- (function () {
- 'use strict';
-
- let mainElement = document.getElementById('xplMainContentLandmark');
-
- if (mainElement) {
- // Insert CSS codes
- let style = document.createElement('style');
- style.appendChild(document.createTextNode(CSS));
- document.head.appendChild(style);
-
- // Insert HTML codes
- let optionHeader = document.createElement('div');
- optionHeader.innerHTML = HTML;
- let firstChild = mainElement.firstChild;
- mainElement.insertBefore(optionHeader, firstChild);
-
- // Add event listener
- bindListener();
-
- // Set the countdown
- let timerCounter = 0;
- let countDownTimer = setInterval(() => {
- let countDownElement = document.getElementById('ipd-strong-countdown');
- timerCounter = timerCounter + 100;
- countDownElement.innerHTML = "Loading... " + timerCounter / 1000 + "s";
- }, 100);
-
- // Wait for loading
- const loadPapers = () => {
- const xpathExpression = '//div[@class="subid-container"]//xpl-access-type-icon/span[not(contains(@class, "locked"))]/../../../..';
- let paperResults = document.evaluate(xpathExpression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
- if (paperResults.snapshotLength > 0) {
- clearInterval(countDownTimer);
- let countDownElement = document.getElementById('ipd-strong-countdown');
- countDownElement.innerHTML = "Loading complete!";
- } else {
- setTimeout(loadPapers, 100);
- return;
- }
- }
- setTimeout(loadPapers, 100);
- }
- })();