Skip to content

Instantly share code, notes, and snippets.

@liuzhenqi77
Last active January 21, 2026 20:22
Show Gist options
  • Select an option

  • Save liuzhenqi77/64b5eb22ab4c9c2f12e769f6d7105d73 to your computer and use it in GitHub Desktop.

Select an option

Save liuzhenqi77/64b5eb22ab4c9c2f12e769f6d7105d73 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Google Scholar Researcher Page Enhance
// @version 0.1.1
// @description Fetching some useful metadata to save some clicks
// @author Zhen-Qi Liu
// @match https://scholar.google.com/citations?*user=*
// @exclude https://scholar.google.com/citations?*view_op=view_citation*
// @match https://scholar.google.ca/citations?*user=*
// @exclude https://scholar.google.ca/citations?*view_op=view_citation*
// @match https://scholar.google.co.uk/citations?*user=*
// @exclude https://scholar.google.co.uk/citations?*view_op=view_citation*
// @note 2026/01/21 Added first author detection and researcher name highlighting
// @note 2025/08/08 Added borders to last author papers
// @note 2025/08/07 Added the basics: full author list, abstract, links
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// ==/UserScript==
(function () {
"use strict";
// Add CSS styles for the abstract display
GM_addStyle(`
#gsc_bdy {
max-width: 1800px;
}
.load-metadata-panel {
position: fixed;
left: 2px;
top: 95%;
transform: translateY(-50%);
color: white;
padding: 8px 10px;
border-radius: 4px;
font-size: 13px;
z-index: 1000;
box-shadow: 0 2px 2px rgba(0,0,0,0.2);
}
.load-metadata-btn {
color: #1a0dab;
cursor: pointer;
}
.load-metadata-btn:hover:not(:disabled) {
text-decoration: underline;
}
.load-metadata-btn:disabled {
color: #777777 !important;
cursor: not-allowed !important;
text-decoration: none !important;
}
.progress-indicator {
text-align: center;
margin: 10px 0;
font-size: 10px;
color: #666;
}
.progress-bar {
width: 100%;
height: 4px;
background-color: #e0e0e0;
border-radius: 2px;
overflow: hidden;
margin: 10px 0;
}
.progress-fill {
height: 100%;
background-color: #1a73e8;
width: 0%;
transition: width 0.3s ease;
}
.metadata-container {
margin: 8px 0;
padding-left: 4px;
background-color: #f8f9fa;
border-left: 2px solid #4285f4;
font-size: 13px;
}
.metadata-content {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.metadata-field {
display: flex;
flex-wrap: wrap;
}
.metadata-description {
text-align: justify;
font-style: italic;
color: #555;
width: 100%;
}
.metadata-loading {
color: #666;
font-style: italic;
}
.metadata-error {
color: #d93025;
font-style: italic;
}
.metadata-links-container {
font-weight:normal;
padding:16px 16px 0 16px;
vertical-align:top;
text-align:right;
}
#gsc_a_t th.metadata-links-container {
box-sizing:border-box;
text-transform:uppercase;
vertical-align:middle;
padding-top:0;
padding-bottom:0;
}
th.metadata-links-container {
width: 88px;
white-space: nowrap;
}
.metadata-links {
margin-top: 3px;
}
.lastAuthor {
border: 1px solid darkblue;
}
.firstAuthor {
border: 1px solid darkred;
}
.researcher-name-highlight {
background-color: yellow;
font-weight: bold;
}
`);
let isLoading = false;
let loadPanel = null;
let loadButton = null;
let progressIndicator = null;
let progressBar = null;
const scholarName = document.querySelector("#gsc_prf_in").textContent;
function createLoadPanel() {
const container = document.createElement("div");
container.className = "load-metadata-panel";
container.innerHTML = `
<button class="load-metadata-btn">Load All Metadata</button>
<div class="progress-indicator">Fetching...</div>
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
`;
return container;
}
// Function to create abstract container for a paper
function createMetadataContainer() {
const container = document.createElement("div");
container.className = "metadata-container";
container.innerHTML = '<div class="metadata-loading">Loading...</div>';
return container;
}
function createMetadataLinksContainer() {
const container = document.createElement("td");
container.className = "metadata-links-container";
return container;
}
function createMetadataLinksHeader() {
const tr0 = document.createElement("th");
tr0.className = "metadata-links-container";
const trh = document.createElement("th");
trh.className = "metadata-links-container";
trh.scope = "col";
trh.innerHTML = `
<div>Links</div>
`;
return { tr0, trh };
}
function isLastAuthor(authorString, targetName) {
const authors = authorString
.split(",")
.map((author) => author.trim())
.filter((author) => author.length > 0);
if (authors.length === 0) return false;
const lastAuthor = authors[authors.length - 1];
const lastAuthorParts = lastAuthor.trim().split(/\s+/);
const lastAuthorLastName = lastAuthorParts[lastAuthorParts.length - 1];
const targetNameParts = targetName.trim().split(/\s+/);
const targetNameLastName = targetNameParts[targetNameParts.length - 1];
return (
lastAuthorLastName.toLowerCase() === targetNameLastName.toLowerCase()
);
}
function isFirstAuthor(authorString, targetName) {
const authors = authorString
.split(",")
.map((author) => author.trim())
.filter((author) => author.length > 0);
if (authors.length === 0) return false;
const firstAuthor = authors[0];
const firstAuthorParts = firstAuthor.trim().split(/\s+/);
const firstAuthorLastName = firstAuthorParts[firstAuthorParts.length - 1];
const targetNameParts = targetName.trim().split(/\s+/);
const targetNameLastName = targetNameParts[targetNameParts.length - 1];
return (
firstAuthorLastName.toLowerCase() === targetNameLastName.toLowerCase()
);
}
function highlightResearcherName(authorString, targetName) {
const targetNameParts = targetName.trim().split(/\s+/);
const targetNameLastName = targetNameParts[targetNameParts.length - 1];
// Use a regex to find and wrap the researcher's last name
// This will match word boundaries to avoid partial matches
const regex = new RegExp(`\\b(${targetNameLastName})\\b`, 'gi');
return authorString.replace(regex, '<span class="researcher-name-highlight">$1</span>');
}
// Function to fetch metadata from paper URL
function fetchMetadata(container, paperUrl) {
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: "GET",
url: paperUrl,
onload: function (response) {
const parser = new DOMParser();
const doc = parser.parseFromString(
response.responseText,
"text/html"
);
// Extract all metadata fields
const metadataFields = doc.querySelectorAll(".gs_scl");
const metadata = {};
metadataFields.forEach((field) => {
const fieldLabel = field.querySelector(".gsc_oci_field");
const fieldValue = field.querySelector(".gsc_oci_value");
if (fieldLabel && fieldValue) {
const label = fieldLabel.textContent.trim();
let value = fieldValue.textContent.trim();
if (label === "Authors") {
metadata[label] = value;
}
// Special handling for description (abstract)
if (label === "Description") {
metadata[label] = fieldValue.textContent.trim();
}
}
});
const metadataLinkFields = doc.querySelectorAll(".gsc_oci_title_ggi");
const metadataLinks = [];
metadataLinkFields.forEach((metadataLinkField) => {
const linkObject = metadataLinkField.querySelector("a");
let href = linkObject.href;
const trimToFormat = (text) =>
text.includes("[HTML]")
? "[HTML]"
: text.includes("[PDF]")
? "[PDF]"
: text;
const text = trimToFormat(linkObject.text);
metadataLinks.push({ href, text });
});
// Build HTML display
if (Object.keys(metadata).length > 0) {
if (metadata["Authors"]) {
const highlightedAuthors = highlightResearcherName(metadata["Authors"], scholarName);
container.parentElement.querySelector(".gs_gray").innerHTML =
highlightedAuthors;
const block = container.parentElement.parentElement;
if (isLastAuthor(metadata["Authors"], scholarName)) {
block.className += " lastAuthor";
} else if (isFirstAuthor(metadata["Authors"], scholarName)) {
block.className += " firstAuthor";
}
}
if (metadataLinks.length > 0) {
let html_links = "<div>";
for (let i = 0; i < metadataLinks.length; i++) {
html_links += `<div><a href=${metadataLinks[i]["href"]}>${metadataLinks[i]["text"]}</a></div>`;
}
html_links += "</div>";
container.parentElement.parentElement.querySelector(
".metadata-links-container"
).innerHTML = html_links;
}
let html = '<div class="metadata-content">';
if (metadata["Description"]) {
html += `<div class="metadata-field">
<div class="metadata-description">${metadata["Description"]}</div>
</div>`;
html += "</div>";
container.innerHTML = html;
}
} else {
container.innerHTML =
'<div class="metadata-error">Metadata not available</div>';
console.log(response);
}
resolve();
},
onerror: function () {
container.innerHTML =
'<div class="metadata-error">Failed to load metadata</div>';
resolve();
},
});
});
}
// Function to update progress
function updateProgress(current, total) {
const percentage = (current / total) * 100;
const progressFill = document.querySelector(".progress-fill");
const progressText = document.querySelector(".progress-indicator");
progressFill.style.width = `${percentage}%`;
progressText.textContent = `Fetching... ${current}/${total}`;
}
// Main function to load all metadata
async function loadAllMetadata() {
if (isLoading) return;
isLoading = true;
loadButton.disabled = true;
// loadButton.textContent = 'Loading...';
// Show progress indicator
progressIndicator.style.display = "block";
const paperElements = document.querySelectorAll(".gsc_a_tr");
const validPapers = [];
// Add table header for metadata-links-container
const { tr0, trh } = createMetadataLinksHeader();
const tableHead = document.querySelector("#gsc_a_tw table thead");
tableHead.querySelector("#gsc_a_tr0").appendChild(tr0);
tableHead.querySelector("#gsc_a_trh").appendChild(trh);
// Prepare papers and add containers
paperElements.forEach((paperElement) => {
const titleLink = paperElement.querySelector(".gsc_a_at");
if (!titleLink) return;
const paperUrl =
"https://scholar.google.com" + titleLink.getAttribute("href");
const titleCell = paperElement.querySelector(".gsc_a_t");
if (titleCell && !titleCell.querySelector(".metadata-container")) {
const container = createMetadataContainer();
titleCell.appendChild(container);
validPapers.push({ container, paperUrl });
}
if (!paperElement.querySelector(".metadata-links-container")) {
const links = createMetadataLinksContainer();
paperElement.appendChild(links);
}
});
// Load metadata with delay to avoid rate limiting
for (let i = 0; i < validPapers.length; i++) {
const { container, paperUrl } = validPapers[i];
updateProgress(i + 1, validPapers.length);
await fetchMetadata(container, paperUrl);
// Add delay between requests to be respectful to the server
if (i < validPapers.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 500));
}
}
// Hide progress and update button
progressIndicator.style.display = "none";
loadButton.textContent = "Metadata Loaded";
loadButton.disabled = true;
isLoading = false;
}
function insertLoadPanel() {
if (loadPanel) return;
loadPanel = createLoadPanel();
loadButton = loadPanel.querySelector(".load-metadata-btn");
loadButton.onclick = loadAllMetadata;
progressIndicator = loadPanel.querySelector(".progress-indicator");
progressIndicator.style.display = "none";
// Insert floating button directly to body
document.body.appendChild(loadPanel);
}
// Function to handle dynamic content loading
function observeChanges() {
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes.length > 0) {
// Check if new paper entries were added
const hasNewPapers = Array.from(mutation.addedNodes).some(
(node) =>
node.nodeType === 1 &&
(node.matches(".gsc_a_tr") || node.querySelector(".gsc_a_tr"))
);
if (hasNewPapers && loadButton && !isLoading) {
// Re-enable button if new papers are loaded
loadButton.disabled = false;
loadButton.textContent = "Load All Metadata";
}
}
});
});
const targetNode = document.querySelector("#gsc_a_b") || document.body;
observer.observe(targetNode, {
childList: true,
subtree: true,
});
}
// Initialize the script
function init() {
// Wait for page to load
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
return;
}
// Remove language parameters from the Google Scholar URL and reload the page if necessary.
const currentUrl = window.location.href;
const url = new URL(currentUrl);
url.searchParams.delete('hl');
if (url.toString() !== currentUrl) {
window.location.href = url.toString();
}
// Insert load button
setTimeout(insertLoadPanel, 1000);
// Set up observer for dynamic loading
observeChanges();
// Handle "Show more" button clicks
const showMoreButton = document.querySelector("#gsc_bpf_more");
if (showMoreButton) {
showMoreButton.addEventListener("click", function () {
setTimeout(() => {
if (loadButton && !isLoading) {
loadButton.disabled = false;
loadButton.textContent = "Load All Metadata";
}
}, 2000);
});
}
}
// Start the script
init();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment