您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds Sci-Hub, LibGen, Anna's Archive, Sci-net, LibSTC buttons Spacefrontiers to Google Scholar results
当前为
// ==UserScript== // @name Google Scholar to free PDFs // @namespace ScholarToSciHub // @version 1.15 // @description Adds Sci-Hub, LibGen, Anna's Archive, Sci-net, LibSTC buttons Spacefrontiers to Google Scholar results // @author Bui Quoc Dung // @match https://scholar.google.*/* // @license AGPL-3.0-or-later // @grant GM_xmlhttpRequest // ==/UserScript== const SCIHUB_URL = 'https://www.tesble.com/'; const LIBGEN_URL = 'https://libgen.li/index.php?req='; const ANNA_URL = 'https://annas-archive.org/scidb/'; const ANNA_CHECK_URL = 'https://annas-archive.org/search?index=journals&q='; const LIBSTC_BASE_URL = 'https://hub.libstc.cc/'; const SCINET_URL = 'https://sci-net.xyz/'; const SPACEFRONTIERS_BASE_URL = 'https://spacefrontiers.org/'; const CROSSREF_URL = 'https://api.crossref.org/works?query.title='; const DOI_REGEX = /\b(10\.\d{4,}(?:\.\d+)*\/(?:(?!["&'<>])\S)+)\b/gi; function updateLink(span, textContent, href, isNo = false) { const link = document.createElement('a'); link.textContent = textContent; link.href = href; link.target = '_blank'; link.rel = 'noreferrer noopener'; link.style.fontSize = '15px'; if (isNo) link.style.color = 'gray'; link.innerHTML = textContent.replace('[PDF]', '<b>[PDF]</b>').replace('[Chat]', '<b>[Chat]</b>'); span.replaceWith(link); } function fetchDOI(titleLink, callback) { GM_xmlhttpRequest({ method: 'GET', url: titleLink.href, onload: res => { const matches = res.responseText.match(DOI_REGEX); if (matches && matches.length) { callback(matches[0]); } else { const title = encodeURIComponent(titleLink.textContent.trim()); const crossrefUrl = `${CROSSREF_URL}${title}&rows=1`; GM_xmlhttpRequest({ method: 'GET', url: crossrefUrl, onload: crRes => { try { const data = JSON.parse(crRes.responseText); const items = data.message.items; if (items && items.length && items[0].DOI) { callback(items[0].DOI); } else { callback(null); } } catch (e) { callback(null); } }, onerror: () => callback(null) }); } }, onerror: () => callback(null) }); } function addLoadingIndicator(container) { const span = document.createElement('div'); span.textContent = 'Loading...'; span.style.marginBottom = '4px'; span.style.color = 'gray'; span.style.fontSize = '15px'; container.appendChild(span); return span; } function checkLibGen(title, doi, span) { const encodedTitle = encodeURIComponent(title); const encodedDOI = doi ? encodeURIComponent(doi) : null; const trySearch = (query, fallback) => { GM_xmlhttpRequest({ method: 'GET', url: LIBGEN_URL + query, onload: res => { const doc = new DOMParser().parseFromString(res.responseText, 'text/html'); const table = doc.querySelector('.table.table-striped'); const hasPDF = table && table.querySelector('tbody') && table.querySelector('tbody').children.length > 0; if (hasPDF) { updateLink(span, '[PDF] LibGen', LIBGEN_URL + query, false); } else { fallback(); } }, onerror: fallback }); }; trySearch(encodedTitle, () => { if (encodedDOI) { trySearch(encodedDOI, () => { updateLink(span, '[No] LibGen', LIBGEN_URL + encodedDOI, true); }); } else { updateLink(span, '[No] LibGen', LIBGEN_URL + encodedTitle, true); } }); } function checkSciHub(href, doi, span) { const tryURL = (url, fallback) => { GM_xmlhttpRequest({ method: 'GET', url: url, onload: res => { const hasPDF = /iframe|pdf|embed/i.test(res.responseText); if (hasPDF) { updateLink(span, '[PDF] Sci-Hub', url, false); } else { fallback(); } }, onerror: fallback }); }; tryURL(SCIHUB_URL + href, () => { if (doi) { tryURL(SCIHUB_URL + doi, () => { tryURL(SCIHUB_URL + doi, () => { updateLink(span, '[No] Sci-Hub', SCIHUB_URL + doi, true); }); }); } else { updateLink(span, '[No] Sci-Hub', SCIHUB_URL + href, true); } }); } function checkAnna(doi, span) { const checkUrl = ANNA_CHECK_URL + encodeURIComponent(doi); GM_xmlhttpRequest({ method: 'GET', url: checkUrl, onload: res => { const doc = new DOMParser().parseFromString(res.responseText, 'text/html'); const hasPDF = !!doc.querySelector('.mt-4.uppercase.text-xs.text-gray-500'); updateLink(span, hasPDF ? '[PDF] Anna' : '[No] Anna', ANNA_URL + doi, !hasPDF); }, onerror: () => updateLink(span, '[No] Anna', ANNA_URL + doi, true) }); } function checkSpaceFrontiers(doi, span) { if (!doi) { updateLink(span, '[No] Spacefrontiers', '#', true); return; } const checkUrl = SPACEFRONTIERS_BASE_URL + 'r/' + doi; const context = encodeURIComponent(JSON.stringify({ uris: [`doi://${doi}`] })); const chatLink = SPACEFRONTIERS_BASE_URL + 'c?context=' + context + '&no-auto-search=1'; GM_xmlhttpRequest({ method: 'GET', url: checkUrl, onload: res => { const doc = new DOMParser().parseFromString(res.responseText, 'text/html'); const chatElement = doc.querySelector('span.relative.flex.items-center.gap-2'); const hasPDF = chatElement && chatElement.textContent.includes('Chat with the Research'); if (hasPDF) { updateLink(span, '[Chat] Spacefrontiers', chatLink, false); } else { updateLink(span, '[No] Spacefrontiers', checkUrl, true); } }, onerror: () => updateLink(span, '[No] Spacefrontiers', checkUrl, true) }); } function checkLibSTC(doi, span) { const url = LIBSTC_BASE_URL + doi + '.pdf'; GM_xmlhttpRequest({ method: 'HEAD', url: url, onload: res => { const isPDF = res.status === 200 && res.responseHeaders.toLowerCase().includes('application/pdf'); updateLink(span, isPDF ? '[PDF] LibSTC' : '[No] LibSTC', url, !isPDF); }, onerror: () => updateLink(span, '[No] LibSTC', url, true) }); } function checkSciNet(doi, span) { const sciNetUrl = SCINET_URL + doi; GM_xmlhttpRequest({ method: 'GET', url: sciNetUrl, onload: function(response) { const hasPDF = /iframe|pdf|embed/.test(response.responseText); updateLink(span, hasPDF ? '[PDF] Sci-net' : '[No] Sci-net', sciNetUrl, !hasPDF); }, onerror: function() { updateLink(span, '[No] Sci-net', sciNetUrl, true); } }); } function addButtons() { document.querySelectorAll('#gs_res_ccl_mid .gs_r.gs_or.gs_scl').forEach(result => { const titleLink = result.querySelector('.gs_rt a'); const yearElement = result.querySelector('.gs_a'); if (!titleLink || !yearElement) return; let buttonContainer = result.querySelector('.gs_or_ggsm'); if (!buttonContainer) { const div = document.createElement('div'); div.className = 'gs_ggs gs_fl'; div.innerHTML = '<div class="gs_ggsd"><div class="gs_or_ggsm"></div></div>'; result.insertBefore(div, result.firstChild); buttonContainer = div.querySelector('.gs_or_ggsm'); } if (buttonContainer.classList.contains('scihub-processed')) return; buttonContainer.classList.add('scihub-processed'); const row1 = document.createElement('span'); row1.style.display = 'inline-flex'; row1.style.gap = '6px'; const scihubSpan = addLoadingIndicator(row1); const libgenSpan = addLoadingIndicator(row1); buttonContainer.appendChild(row1); const row2 = document.createElement('span'); row2.style.display = 'inline-flex'; row2.style.gap = '6px'; const annaSpan = addLoadingIndicator(row2); const scinetSpan = addLoadingIndicator(row2); buttonContainer.appendChild(row2); const row3 = document.createElement('span'); row3.style.display = 'flex'; row3.style.gap = '6px'; const libstcSpan = addLoadingIndicator(row3); buttonContainer.appendChild(row3); const row4 = document.createElement('span'); row4.style.display = 'flex'; row4.style.gap = '6px'; const spacefrontiersSpan = addLoadingIndicator(row4); buttonContainer.appendChild(row4); fetchDOI(titleLink, (doi) => { checkLibGen(titleLink.textContent, doi, libgenSpan); checkSciHub(titleLink.href, doi, scihubSpan); if (doi) { checkAnna(doi, annaSpan); checkSciNet(doi, scinetSpan); checkLibSTC(doi, libstcSpan); checkSpaceFrontiers(doi, spacefrontiersSpan); } else { updateLink(annaSpan, '[No] Anna', '#', true); updateLink(scinetSpan, '[No] Sci-net', '#', true); updateLink(libstcSpan, '[No] LibSTC', '#', true); updateLink(spacefrontiersSpan, '[No] Spacefrontiers', '#', true); } }); }); } addButtons(); new MutationObserver((mutations) => { mutations.forEach((mutation) => mutation.addedNodes.length && addButtons()); }).observe(document.body, {childList: true, subtree: true});