Last active
February 25, 2025 08:19
-
-
Save wbxl2000/89a878a32fbdb690bdd02e392fa4768d to your computer and use it in GitHub Desktop.
npm-file-downloader: A Tampermonkey script that supports downloading single npm files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name npm-file-downloader | |
| // @link https://greasyfork.org/zh-CN/scripts/528009-npm-file-downloader | |
| // @video https://youtu.be/6BqphFJ69-g | |
| // @namespace http://tampermonkey.net/ | |
| // @version 2024-11-04 | |
| // @description A Tampermonkey script that supports downloading single npm files | |
| // @author qer | |
| // @match https://www.npmjs.com/* | |
| // @icon https://github.com/user-attachments/assets/7ccadb13-ee47-4206-88bf-050a087988d8 | |
| // @license MIT | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const mainButtonCssText = ` | |
| background-color: #0969da; | |
| color: white; | |
| padding: 8px 16px; | |
| border: none; | |
| border-radius: 6px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| margin-left: 10px; | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.12); | |
| `; | |
| const downloadButtonCssText = ` | |
| color: #0969da; | |
| text-decoration: none; | |
| font-size: 14px; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| transition: background-color 0.2s ease; | |
| `; | |
| const showDownloadLinksButton = document.createElement('button'); | |
| showDownloadLinksButton.innerHTML = 'Show Download Links'; | |
| showDownloadLinksButton.style.cssText = mainButtonCssText; | |
| addButtonClickEffect(showDownloadLinksButton); | |
| showDownloadLinksButton.onclick = showDownloadLinks; // download function | |
| addShowDownloadLinksButton(); // add button to the page | |
| function addButtonClickEffect(button) { | |
| button.addEventListener('mouseover', function () { | |
| this.style.backgroundColor = '#0557c5'; | |
| }); | |
| button.addEventListener('mouseout', function () { | |
| this.style.backgroundColor = '#0969da'; | |
| }); | |
| button.addEventListener('mousedown', function () { | |
| this.style.transform = 'scale(0.98)'; | |
| }); | |
| button.addEventListener('mouseup', function () { | |
| this.style.transform = 'scale(1)'; | |
| }); | |
| } | |
| function addShowDownloadLinksButton() { | |
| const tabListA = document.querySelector('ul[role="tablist"]').querySelectorAll('a'); | |
| if (!tabListA) return; | |
| for (const a of tabListA) { | |
| a.onclick = () => { | |
| if (a.getAttribute('href') === '?activeTab=code') { | |
| document.querySelector('#main div').childNodes[1].querySelectorAll('li')[1].appendChild(showDownloadLinksButton); | |
| } else showDownloadLinksButton.remove(); | |
| }; | |
| } | |
| const link = document.querySelector('a[href="?activeTab=code"]'); | |
| if (!link) return; | |
| const ariaSelected = link.getAttribute('aria-selected'); | |
| console.log('ariaSelected', ariaSelected); | |
| if (ariaSelected === 'true') { | |
| document.querySelector('#main div').childNodes[1].querySelectorAll('li')[1].appendChild(showDownloadLinksButton); | |
| } | |
| } | |
| function info() { | |
| const path = new URL(window.location.href).pathname.split('/'); | |
| return { packageName: path[2], version: path[4]?.replace(/^v\//, '') || 'latest' }; | |
| } | |
| function showDownloadLinks() { | |
| const { packageName, version } = info(); | |
| console.log('Package Name:', packageName); | |
| console.log('Version:', version); | |
| const domain = 'https://nfd.qer.im'; | |
| // const domain = 'http://localhost:8787'; | |
| let path = ''; let liElements = []; | |
| try { | |
| const section = document.querySelector('#main div').childNodes[2].querySelector('section:not([data-attribute="hidden"])'); | |
| console.log(section); | |
| path = section.querySelector('h2').innerText.split('/').slice(2) | |
| .join('/') || ''; | |
| liElements = section.querySelectorAll('ul li'); | |
| if (!liElements || liElements.length <= 0) { | |
| throw new Error(`Can't find elements: ${path}${liElements.length}`); | |
| } | |
| } catch (e) { | |
| alert(`Failed to add download links: ${e}`); | |
| } | |
| liElements.forEach((li, index) => { | |
| const buttonText = li.querySelector('button').textContent; | |
| const fileType = li.querySelectorAll('div')[2].textContent; | |
| // const fileSize = li.querySelectorAll('div')[3].textContent; | |
| const isFolder = !fileType || fileType.toLowerCase() === 'folder'; | |
| if (buttonText === '../') return; | |
| const downloadButton = document.createElement('a'); | |
| downloadButton.style.cssText = downloadButtonCssText; | |
| if (isFolder) { | |
| downloadButton.textContent = '(Folder)'; | |
| downloadButton.style.cssText += ` | |
| color: #666; | |
| cursor: not-allowed; | |
| opacity: 0.7; | |
| `; | |
| } else { | |
| downloadButton.textContent = 'Download'; | |
| downloadButton.style.cursor = 'pointer'; | |
| downloadButton.href = `${domain}/api/download?package=${packageName}&path=${path}&file=${encodeURIComponent(buttonText)}&version=${version}`; | |
| downloadButton.addEventListener('click', function (event) { | |
| event.preventDefault(); | |
| const loadingIndicator = document.createElement('span'); | |
| loadingIndicator.textContent = 'Downloading...'; | |
| loadingIndicator.style.marginLeft = '5px'; | |
| this.parentNode.insertBefore(loadingIndicator, this.nextSibling); | |
| this.style.pointerEvents = 'none'; | |
| this.style.opacity = '0.5'; | |
| setTimeout(() => { | |
| loadingIndicator.remove(); | |
| this.style.pointerEvents = 'auto'; | |
| this.style.opacity = '1'; | |
| window.location.href = this.href; | |
| }, 2000); | |
| }); | |
| } | |
| const fileNameButton = li.querySelector('button'); | |
| fileNameButton.parentNode.insertBefore(downloadButton, fileNameButton.nextSibling); | |
| fileNameButton.parentNode.style.display = 'flex'; | |
| fileNameButton.parentNode.style.alignItems = 'center'; | |
| }); | |
| } | |
| }()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment