// ==UserScript== // @name npm_plus // @namespace npm_plus // @description 增强 npm 的搜索体验, 添加下载和 github 直链, 使用 npms.io 的数据替换 npm 评分 // @version 1.0.0 // @author roojay // @license http://opensource.org/licenses/MIT // @include *://*npmjs.com/search* // @run-at document-end // @grant GM_addStyle // @grant GM_xmlhttpRequest // @require https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js // @noframes // @connect * // @downloadURL https://update.greasyfork.icu/scripts/399821/npm_plus.user.js // @updateURL https://update.greasyfork.icu/scripts/399821/npm_plus.meta.js // ==/UserScript== GM_addStyle(` .github-icon { width: 12px; height: 12px; fill: rgba(0, 0, 0, 0.45); } .download-number { color: #d14; font-size: 12px; font-weight: normal; font-family: Arial; font-style: italic; } .download-icon { width: 8px; height: 12px; fill: rgba(0, 0, 0, 0.45); } .npm-plus-title-wrap { position: relative; flex: 1; display: inline-flex; justify-content: flex-end; align-items: center; } .npm-plus-title-wrap > span { margin-left: 8px; } .score-number-wrap { position: relative; } .score-icon { position: relative; width: 26px; height: 26px; float: left; cursor: default; fill: rgb(95, 149, 122); overflow: hidden; } .score-number { position: absolute; top: 50%; left: 50%; z-index: 2; transform: translate(-50%, -50%); color: #fff; font-size: 12px; font-weight: 900; align-items: center; font-family: Arial; } `); $(function() { // 包列表 const DOM_PACKAGE_LIST = 'div.ph3.pt2-ns'; // 单个包 const DOM_PACKAGE_ITEM = 'section.flex.pl1-ns'; // 包信息 const DOM_PACKAGE_INFO = 'div.w-80'; // 包评分 const DOM_PACKAGE_SCORE = 'div.w-20'; // 包名容器 const DOM_PACKAGE_TITLE_WRAP = '.flex.flex-row.items-end.pr3' // 包名链接 const DOM_PACKAGE_LINK = '.flex-row a'; const API_NPMS = 'https://api.npms.io/v2' const API_NPM_REGISTRY = 'https://registry.npmjs.org'; const ICON_SCORE = ' '; const ICON_DOWNLOAD = 'Downloads'; const ICON_GITHUB = ''; function initScript() { const observer = new MutationObserver((mutations, observer) => runAll()); observer.observe(document.querySelector(DOM_PACKAGE_LIST), { childList: true, // 子节点的变动 attributes: false, // 属性的变动 characterData: true, // 节点内容或节点文本的变动 subtree: false // 是否将该观察器应用于该节点的所有后代节点 }); runAll(); } const renderGithub = (_githubUrl) => ` ${ICON_GITHUB} `; const renderDownload = (_downloadUrl, _downloadNumber) => ` ${ICON_DOWNLOAD} ${_downloadNumber} `; const renderScore = (_score) => ` ${ICON_SCORE} ${_score} `; /** * 获取包的详细信息 * @param {string} packageUrl * @param {jQuery object} titleWrap */ async function getPackageDetails(packageUrl, titleWrap) { const fullName = packageUrl.replace('/package/', ''); const _url = `${API_NPMS}/package/${encodeURIComponent(fullName)}`; const name = fullName.split('/').slice(-1)[0]; GM_xmlhttpRequest({ url: _url, method: 'get', onload: function(xhr) { try { const res = JSON.parse(xhr.response); const downNum = res.collected.npm.downloads.slice(-1)[0].count; const scoreNum = ~~(res.score.final * 100); const version = res.collected.metadata.version; const githubUrl = res.collected.metadata.links.repository; const downLoadUrl = `${API_NPM_REGISTRY}/${fullName}/-/${name}-${version}.tgz`; const toolWrap = ` ${githubUrl ? renderGithub(githubUrl) : ''} ${renderDownload(downLoadUrl, downNum)} ${renderScore(scoreNum)} `; titleWrap.append(toolWrap); } catch (e) { console.log('getPackageDetails -> e', e); } } }); } // 执行所有替换方法 async function runAll() { const promises = $(DOM_PACKAGE_ITEM).map((index, val) => { const $this = $(val); // 去掉右侧原有评分 $this.find(DOM_PACKAGE_SCORE).remove(); $this.find(DOM_PACKAGE_INFO).removeClass('w-80').addClass('w-100'); const packageUrl = $this.find(DOM_PACKAGE_LINK).attr('href'); const titleWrap = $this.find(DOM_PACKAGE_TITLE_WRAP); return getPackageDetails(packageUrl, titleWrap); }); Promise.all(promises); } initScript(); });