// ==UserScript==
// @name SCImago Journal Rank and DOI for Google Scholar
// @namespace https://greasyfork.org/
// @version 1.2
// @description Show DOI, journal name, ISSN, SJR and H-Index 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 *
// @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 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',
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 `${u.protocol}//${u.hostname}/favicon.ico`;
} 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 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);
// Loading placeholders
const line1 = document.createElement('div');
line1.textContent = 'Loading DOI...';
infoDiv.appendChild(line1);
const line2 = document.createElement('div');
line2.textContent = 'Loading journal info...';
infoDiv.appendChild(line2);
const line3 = document.createElement('div');
line3.textContent = 'Loading SJR info...';
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 !== doi2) {
const useTwoLines = shouldBreakIntoTwoLines(doi1, doi2);
if (useTwoLines) {
// First line for DOI1
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);
// Second line for DOI2
const fav2 = createFaviconIcon(ICONS.CrossRef, 'CrossRef');
line1.appendChild(fav2);
line1.innerHTML += `CrossRef: <a href="${doiHref2}" target="_blank">${doi2}</a>`;
} else {
// Single line format
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);
line2.appendChild(document.createTextNode(journal || 'Journal info: Not found'));
querySJRByISSN(issn, sjrInfo => {
line3.innerHTML = '';
const iconSJR = createFaviconIcon(ICONS.SJR, 'SJR');
line3.appendChild(iconSJR);
if (!sjrInfo) {
line3.appendChild(document.createTextNode(`ISSN: ${issn || 'N/A'} — SJR info not found`));
return;
}
line3.appendChild(document.createTextNode(`ISSN: ${issn} — ${sjrInfo}`));
});
});
});
}
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));
})();