Greasy Fork

Google Scholar to free PDF

Adds Sci-Hub, LibGen, LibSTC and Anna's Archive buttons to Google Scholar results

目前为 2025-02-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         Google Scholar to free PDF
// @namespace    ScholarToSciHub
// @version      1.4
// @description  Adds Sci-Hub, LibGen, LibSTC and Anna's Archive buttons to Google Scholar results
// @author       Bui Quoc Dung
// @include      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 LIBSTC_BASE_URL = 'https://hub.libstc.cc/';
const ANNA_URL = 'https://annas-archive.org/scidb/';
const ANNA_CHECK_URL = 'https://annas-archive.org/search?index=journals&q=';

const DOI_REGEX = /\b(10\.\d{4,}(?:\.\d+)*\/(?:(?!["&'<>])\S)+)\b/gi;

function addLink(textContent, href, buttonContainer) {
    const link = document.createElement('a');
    link.textContent = textContent;
    link.href = href;
    link.target = '_blank';
    buttonContainer.appendChild(link);
    return link;
}

function extractYear(element) {
    const yearMatch = element.textContent.match(/\d{4}/);
    return yearMatch ? parseInt(yearMatch[0]) : 0;
}

function checkLibGenPDF(title, buttonContainer) {
    GM_xmlhttpRequest({
        method: 'GET',
        url: LIBGEN_URL + encodeURIComponent(title),
        onload: function(response) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, 'text/html');
            const hasTable = doc.querySelector('.table.table-striped') !== null;
            addLink(hasTable ? '[PDF] LibGen' : '[No] LibGen', LIBGEN_URL + encodeURIComponent(title), buttonContainer);
        },
        onerror: function(error) {
            console.error('Error checking LibGen:', error);
            addLink('[No] LibGen', LIBGEN_URL + encodeURIComponent(title), buttonContainer);
        }
    });
}

function checkSciHubPDF(url, buttonContainer) {
    GM_xmlhttpRequest({
        method: 'GET',
        url: SCIHUB_URL + url,
        onload: function(response) {
            const hasPDF = /iframe|pdf|embed/.test(response.responseText);
            addLink(hasPDF ? '[PDF] Sci-Hub' : '[No] Sci-Hub', SCIHUB_URL + url, buttonContainer);
        },
        onerror: function(error) {
            console.error('Error checking Sci-Hub:', error);
            addLink('[No] Sci-Hub', SCIHUB_URL + url, buttonContainer);
        }
    });
}



function checkAnnaPDF(doi, buttonContainer) {
    const checkUrl = ANNA_CHECK_URL + encodeURIComponent(doi);

    GM_xmlhttpRequest({
        method: 'GET',
        url: checkUrl,
        onload: function(response) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(response.responseText, 'text/html');
            // Check for the new class combination
            const hasPDF = doc.querySelector('.mt-4.uppercase.text-xs.text-gray-500') !== null;

            addLink(
                hasPDF ? '[PDF] Anna' : '[No] Anna',
                ANNA_URL + doi,
                buttonContainer
            );
        },
        onerror: function(error) {
            console.error('Error checking Anna’s Archive:', error);
            addLink('[No] Anna', ANNA_URL + doi, buttonContainer);
        }
    });
}


function checkLibSTCPDF(firstDOI, buttonContainer) {
    const pdfURL = LIBSTC_BASE_URL + firstDOI + '.pdf';
    GM_xmlhttpRequest({
        method: 'HEAD',
        url: pdfURL,
        onload: function(response) {
            const isPDF = response.status === 200 &&
                         response.responseHeaders.toLowerCase().includes('application/pdf');
            addLink(isPDF ? '[PDF] LibSTC' : '[No] LibSTC', pdfURL, buttonContainer);
        },
        onerror: function() {
            addLink('[No] LibSTC', pdfURL, buttonContainer);
        }
    });
}

function fetchDOI(titleLink, callback) {
    GM_xmlhttpRequest({
        method: 'GET',
        url: titleLink.href,
        onload: function(response) {
            const matches = response.responseText.match(DOI_REGEX);
            matches && matches.length ? callback(matches[0]) : callback(null);
        },
        onerror: function(error) {
            console.error('Error fetching DOI:', error);
            callback(null);
        }
    });
}

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

            // Add basic links
            checkSciHubPDF(titleLink.href, buttonContainer);
            checkLibGenPDF(titleLink.textContent, buttonContainer);

            // DOI-based links
            fetchDOI(titleLink, (doi) => {
                if (doi) {
                    // Always add Anna's Archive
                    checkAnnaPDF(doi, buttonContainer);

                    // Add LibSTC only for recent papers
                    if (extractYear(yearElement) > 2020) {
                        checkLibSTCPDF(doi, buttonContainer);
                    }
                }
            });
        }
    });
}

// Initial setup and mutation observer
addButtons();
new MutationObserver((mutations) => {
    mutations.forEach((mutation) => mutation.addedNodes.length && addButtons());
}).observe(document.body, {childList: true, subtree: true});