DOI, SCImago Journal Rank and JIF for Google Scholar

Show DOI, journal name, ISSN, SJR, H-Index, and JIF on Google Scholar search results

当前为 2025-06-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         DOI, SCImago Journal Rank and JIF for Google Scholar
// @namespace    https://greasyfork.org/
// @version      1.6
// @description  Show DOI, journal name, ISSN, SJR, H-Index, and JIF on Google Scholar search results
// @author       Bui Quoc Dung
// @match        https://scholar.google.com/*
// @grant        GM_xmlhttpRequest
// @connect      api.crossref.org
// @connect      www.scimagojr.com
// @connect      wos-journal.info
// @connect      *
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function () {
  'use strict';

  const CROSSREF_API = 'https://api.crossref.org/works';
  const SJR_SEARCH_URL = 'https://www.scimagojr.com/journalsearch.php?q=';
  const SJR_BASE_URL = 'https://www.scimagojr.com/';
  const WOS_JOURNAL_URL = 'https://wos-journal.info/?jsearch=';
  const DOI_REGEX = /\b(10\.\d{4,}(?:\.\d+)*\/(?:(?!["&'<>])\S)+)\b/gi;
  const MAX_SINGLE_LINE_LENGTH = 80;

  const ICONS = {
    CrossRef: 'https://www.crossref.org/favicon.ico',
    SJR: 'https://www.scimagojr.com/favicon.ico',
    Clarivate: 'https://www.google.com/s2/favicons?sz=64&domain=clarivate.com.cn',
    Journal: 'https://www.crossref.org/favicon.ico'
  };

  function createFaviconIcon(url, alt) {
    const img = document.createElement('img');
    img.src = url;
    img.alt = alt;
    img.title = alt;
    img.style.width = '16px';
    img.style.height = '16px';
    img.style.verticalAlign = 'text-bottom';
    img.style.marginRight = '4px';
    return img;
  }

  function getFaviconFromUrl(url) {
    try {
      const u = new URL(url);
      return `https://www.google.com/s2/favicons?sz=64&domain=${u.hostname}`;
    } catch {
      return null;
    }
  }

  function shouldBreakIntoTwoLines(doi1, doi2) {
    const totalLength = (doi1 ? doi1.length : 0) + (doi2 ? doi2.length : 0);
    return totalLength > MAX_SINGLE_LINE_LENGTH;
  }

  function fetchDOI(titleLink, callback) {
    if (!titleLink) return callback(null, null);

    GM_xmlhttpRequest({
      method: 'GET',
      url: titleLink.href,
      onload: res => {
        const matches = res.responseText.match(DOI_REGEX);
        const doi1 = matches?.[0]?.replace(/\/(full|pdf|epdf|abs|abstract)(\/.*)?$/i, '') || null;
        const title = encodeURIComponent(titleLink.textContent.trim());
        const crossrefUrl = `${CROSSREF_API}?query.title=${title}&rows=1`;

        GM_xmlhttpRequest({
          method: 'GET',
          url: crossrefUrl,
          onload: crRes => {
            try {
              const data = JSON.parse(crRes.responseText);
              const doi2 = data.message.items?.[0]?.DOI || null;
              callback(doi1, doi2);
            } catch {
              callback(doi1, null);
            }
          },
          onerror: () => callback(doi1, null)
        });
      },
      onerror: () => callback(null, null)
    });
  }

  function queryCrossRef(title, callback) {
    const url = `${CROSSREF_API}?query.title=${encodeURIComponent(title)}&rows=1`;
    GM_xmlhttpRequest({
      method: 'GET',
      url: url,
      onload: function (res) {
        try {
          const data = JSON.parse(res.responseText);
          const item = data.message.items?.[0];
          if (!item) return callback(null);
          const journal = item['container-title']?.[0] || null;
          const issn = item.ISSN?.[0] || null;
          callback({ journal, issn });
        } catch (e) {
          callback(null);
        }
      },
      onerror: () => callback(null)
    });
  }

  function querySJRByISSN(issn, callback) {
    if (!issn) return callback(null);

    const searchUrl = `${SJR_SEARCH_URL}${encodeURIComponent(issn)}`;
    GM_xmlhttpRequest({
      method: 'GET',
      url: searchUrl,
      onload: function (res) {
        const doc = new DOMParser().parseFromString(res.responseText, "text/html");
        const firstLink = doc.querySelector('.search_results a');
        if (!firstLink) return callback(null);

        const href = firstLink.getAttribute('href');
        if (!href) return callback(null);

        const journalUrl = SJR_BASE_URL + href;
        GM_xmlhttpRequest({
          method: 'GET',
          url: journalUrl,
          onload: function (res2) {
            const doc2 = new DOMParser().parseFromString(res2.responseText, "text/html");
            const container = doc2.querySelector('div.journalgrid');
            if (!container) return callback(null);

            const sjrPs = container.querySelectorAll('p.hindexnumber');
            let sjrValue = null;
            let quartile = null;
            let hIndex = null;

            if (sjrPs.length >= 2) {
              sjrValue = sjrPs[0].childNodes[0]?.textContent.trim();
              quartile = sjrPs[0].querySelector('span')?.textContent.trim();
              hIndex = sjrPs[1].textContent.trim();
            }

            let result = '';
            if (sjrValue && quartile) result += `SJR ${quartile}; ${sjrValue}`;
            else if (sjrValue) result += `SJR ${sjrValue}`;
            if (hIndex) result += ` | H-Index: ${hIndex}`;

            callback(result || null);
          },
          onerror: () => callback(null)
        });
      },
      onerror: () => callback(null)
    });
  }

function queryJIFByISSN(issn, callback) {
  if (!issn) {
    console.log('queryJIFByISSN: No ISSN provided');
    return callback(null);
  }

  const url = `https://wos-journal.info/?jsearch=${encodeURIComponent(issn)}`;
  console.log('queryJIFByISSN: Requesting URL:', url);

  GM_xmlhttpRequest({
    method: 'GET',
    url: url,
    onload: res => {
      console.log('queryJIFByISSN: Response received');
      const doc = new DOMParser().parseFromString(res.responseText, "text/html");
      const titleElems = doc.querySelectorAll('.title.col-4.col-md-3');
      const contentElems = doc.querySelectorAll('.content.col-8.col-md-9');

      console.log('queryJIFByISSN: Found title elements count:', titleElems.length);
      console.log('queryJIFByISSN: Found content elements count:', contentElems.length);

      if (!titleElems.length || !contentElems.length || titleElems.length !== contentElems.length) {
        console.log('queryJIFByISSN: Titles and contents count mismatch or missing');
        return callback(null);
      }

      let jifValue = null;
      for (let i = 0; i < titleElems.length; i++) {
        const titleText = titleElems[i].textContent.trim();
        if (titleText === 'Journal Impact Factor (JIF):') {
          jifValue = contentElems[i].textContent.trim();
          console.log('queryJIFByISSN: Found JIF text:', jifValue);
          break;
        }
      }

      if (jifValue && !isNaN(jifValue)) {
        callback(jifValue);
      } else {
        console.log('queryJIFByISSN: JIF not found or invalid number');
        callback(null);
      }
    },
    onerror: err => {
      console.log('queryJIFByISSN: Request error', err);
      callback(null);
    }
  });
}





  function processEntry(entry) {
    if (entry.querySelector('.sjr-crossref-info')) return;

    const titleLink = entry.querySelector('.gs_rt a');
    const titleText = titleLink?.textContent.trim();
    if (!titleText) return;

    const infoDiv = document.createElement('div');
    infoDiv.className = 'sjr-crossref-info';
    infoDiv.style.marginTop = '4px';
    entry.appendChild(infoDiv);

    const line1 = document.createElement('div');
    line1.textContent = 'Loading DOI...';
    infoDiv.appendChild(line1);

    const line2 = document.createElement('div');
    line2.textContent = 'Loading Journal...';
    infoDiv.appendChild(line2);

    const line3 = document.createElement('div');
    line3.textContent = 'Loading SJR and JIF...';
    infoDiv.appendChild(line3);

    fetchDOI(titleLink, (doi1, doi2) => {
      const doiHref1 = doi1 ? `https://doi.org/${doi1}` : null;
      const doiHref2 = doi2 ? `https://doi.org/${doi2}` : null;
      line1.innerHTML = '';

      if (doi1 && doi2 && doi1.toLowerCase() !== doi2.toLowerCase()) {
        const useTwoLines = shouldBreakIntoTwoLines(doi1, doi2);
        if (useTwoLines) {
          const line1a = document.createElement('div');
          const fav1 = createFaviconIcon(getFaviconFromUrl(titleLink.href), 'Source');
          line1a.appendChild(fav1);
          line1a.innerHTML += `DOI: <a href="${doiHref1}" target="_blank">${doi1}</a>`;
          infoDiv.insertBefore(line1a, line2);

          const fav2 = createFaviconIcon(ICONS.CrossRef, 'CrossRef');
          line1.appendChild(fav2);
          line1.innerHTML += `CrossRef: <a href="${doiHref2}" target="_blank">${doi2}</a>`;
        } else {
          const fav1 = createFaviconIcon(getFaviconFromUrl(titleLink.href), 'Source');
          line1.appendChild(fav1);
          line1.innerHTML += `DOI: <a href="${doiHref1}" target="_blank">${doi1}</a> | `;
          const fav2 = createFaviconIcon(ICONS.CrossRef, 'CrossRef');
          line1.appendChild(fav2);
          line1.innerHTML += `CrossRef: <a href="${doiHref2}" target="_blank">${doi2}</a>`;
        }
      } else if (doi1 || doi2) {
        const doi = doi1 || doi2;
        const href = `https://doi.org/${doi}`;
        const fav = doi1 ? createFaviconIcon(getFaviconFromUrl(titleLink.href), 'Source') : createFaviconIcon(ICONS.CrossRef, 'CrossRef');
        line1.appendChild(fav);
        line1.innerHTML += `DOI: <a href="${href}" target="_blank">${doi}</a>`;
      } else {
        line1.textContent = 'DOI: Not found';
      }

      queryCrossRef(titleText, result => {
        if (!result) {
          line2.innerHTML = '';
          const journalIcon = createFaviconIcon(ICONS.Journal, 'Journal');
          line2.appendChild(journalIcon);
          line2.appendChild(document.createTextNode('Journal info: Not found'));

          line3.innerHTML = '';
          const iconSJR = createFaviconIcon(ICONS.SJR, 'SJR');
          line3.appendChild(iconSJR);
          line3.appendChild(document.createTextNode('SJR info: Not found'));
          return;
        }

        const { journal, issn } = result;
        line2.innerHTML = '';
        const journalIcon = createFaviconIcon(ICONS.Journal, 'Journal');
        line2.appendChild(journalIcon);
        if (journal) {
          const issnText = issn ? ` (ISSN: ${issn})` : '';
          line2.appendChild(document.createTextNode(`${journal}${issnText}`));
        } else {
          line2.appendChild(document.createTextNode('Journal info: Not found'));
        }

        querySJRByISSN(issn, sjrInfo => {
          line3.innerHTML = '';
          const iconSJR = createFaviconIcon(ICONS.SJR, 'SJR');
          line3.appendChild(iconSJR);
          line3.appendChild(document.createTextNode(sjrInfo || 'SJR info: Not found'));

          queryJIFByISSN(issn, jif => {
            if (jif) {
              const clarivateIcon = createFaviconIcon(ICONS.Clarivate, 'Clarivate');
              line3.appendChild(document.createTextNode(' | '));
              line3.appendChild(clarivateIcon);
              line3.appendChild(document.createTextNode(`JIF: ${jif}`));
            }
          });
        });
      });
    });
  }

  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        if (!(node instanceof HTMLElement)) continue;
        const entries = node.matches('.gs_ri') ? [node] : node.querySelectorAll?.('.gs_ri') || [];
        entries.forEach(entry => processEntry(entry));
      }
    }
  });

  observer.observe(document.body, { childList: true, subtree: true });
  document.querySelectorAll('.gs_ri').forEach(entry => processEntry(entry));
})();