Automatically jumps to PDF when you visit a journal article abstract page. Also includes a utility to copy or download citation info.
// ==UserScript== // @name Publication Auto PDF // @name:zh-CN SCI文#PDF直达 // @version 0.5.0 // @author sincostandx // @description Automatically jumps to PDF when you visit a journal article abstract page. Also includes a utility to copy or download citation info. // @description:zh-CN 访问SCI文#摘要页时自动跳转至PDF,附带文#摘录工具 // @match https://www.sciencedirect.com/science/article/* // @match https://onlinelibrary.wiley.com/doi/* // @match https://*.onlinelibrary.wiley.com/doi/* // @match https://pubs.acs.org/doi/* // @match https://www.tandfonline.com/doi/* // @match https://www.beilstein-journals.org/* // @match https://pubs.rsc.org/en/content/* // @match https://link.springer.com/article* // @match https://pubs.aip.org/aip/*/article/* // @match https://www.nature.com/articles* // @match https://www.science.org/doi/* // @match https://journals.aps.org/*/abstract/10* // @match https://cdnsciencepub.com/doi/* // @match https://iopscience.iop.org/article/10* // @match https://www.cell.com/*/fulltext/* // @match https://journals.lww.com/* // @match https://*.biomedcentral.com/articles/* // @match https://journals.sagepub.com/doi/* // @match https://academic.oup.com/*/article/* // @match https://karger.com/*/article/* // @match https://www.cambridge.org/core/journals/*/article/* // @match https://www.annualreviews.org/doi/* // @match https://www.jstage.jst.go.jp/article/* // @match https://www.hindawi.com/journals/* // @match https://*.theclinics.com/article/* // @match https://www.liebertpub.com/doi/* // @match https://thorax.bmj.com/content/* // @match https://journals.physiology.org/doi/* // @match https://www.ahajournals.org/doi/* // @match https://dl.acm.org/doi/* // @match https://journals.asm.org/doi/* // @match https://*.apa.org/record/* // @match https://*.apa.org/fulltext/* // @match https://www.thelancet.com/journals/*/article/* // @match https://jamanetwork.com/journals/* // @match https://aacrjournals.org/*/article/* // @match https://royalsocietypublishing.org/doi/* // @match https://journals.plos.org/*/article* // @match https://*.psychiatryonline.org/doi/* // @match https://opg.optica.org/*/*.cfm* // @match https://www.thieme-connect.de/products/ejournals/* // @match https://journals.ametsoc.org/view/journals/* // @match https://www.frontiersin.org/articles/* // @match https://www.worldscientific.com/doi/* // @match https://www.nejm.org/doi/* // @match https://ascopubs.org/doi/* // @match https://www.jto.org/article/* // @match https://www.jci.org/articles/* // @match https://www.pnas.org/doi/* // @grant GM.getValue // @grant GM.setValue // @run-at document-start // @namespace https://greasyfork.org/users/171198 // ==/UserScript== (function() { "use strict"; let tit = null; // title let doi = null; let pdf = null; // pdf url let sty = null; // citation text style // attempt to extract DOI from URL function getCrudeDOI() { const l = location.pathname.match(/(^.+doi\/)([^/]+\/)?(10\.[^/]+\/[^/]+)/); if (l === null) return [null, null]; const d = l[l.length-1]; return [d, l[1] + "pdf/" + d]; } const [doiCrude, pdfPathname] = getCrudeDOI(); // determine if we need to redirect to PDF let jump = sessionStorage.getItem("%" + doiCrude) === null && sessionStorage.getItem(location.pathname) === null; // For sites in "shortcutSites" modify the URL directly. // Otherwise, load PDF link from meta data or DOM. if (doiCrude !== null) { sessionStorage.setItem("%" + doiCrude, "1"); if (jump && location.pathname !== pdfPathname) { const shortcutSites = ["acs", "aps", "wiley", "tandfonline", "sagepub", "annualreviews", "liebertpub", "physiology", "ahajournals", "acm", "royalsocietypublishing", "psychiatryonline", "thieme", "worldscientific", "nejm", "ascopubs", "cdnsciencepub", "asm", "science", "pnas"]; const hostname = location.hostname; if (shortcutSites.some(a=>hostname.includes(a))) { location.pathname = pdfPathname; } } else { new Promise(checkLoaded).then(loadMeta); } } else { sessionStorage.setItem(location.pathname, "1"); if (location.hostname.includes(".apa.")) { new Promise(checkAPALoaded).then(loadMeta); } else { new Promise(checkLoaded).then(loadMeta); } } function checkLoaded(resolve) { function check(){ if (document.body !== null && document.body.innerHTML.length !== 0) { resolve(); } else { setTimeout(check, 100); } } check(); } function checkAPALoaded(resolve) { let count = 0; function check(){ const main = document.querySelector("main"); if (main !== null && main.offsetHeight > 300) { resolve(); } else if (++count < 30){ setTimeout(check, 1000); } } check(); } function loadMeta() { const titmeta = ["dc.title", "citation_title", "wkhealth_title", "og:title"]; const doimeta = ["citation_doi", "dc.identifier", "dc.source"]; const pdfmeta = ["citation_pdf_url", "wkhealth_pdf_url"]; const metaList = document.getElementsByTagName("meta"); for (const meta of metaList) { let n = meta.getAttribute("name"); if (n === null) { n = meta.getAttribute("property"); if (n === null) continue; } n = n.toLowerCase(); if (tit === null && titmeta.includes(n)) { tit = meta.getAttribute("content"); continue; } if (doi === null && doimeta.includes(n)) { const d = meta.getAttribute("content"); if (d.includes("10.")) { if (d.includes("doi")) { doi = d.slice(d.indexOf("10.")); } else { doi = d; } continue; } } if (pdf === null && pdfmeta.includes(n)) { pdf = meta.getAttribute("content"); } } if (jump && location.hostname.includes("sciencedirect")) { if (loadElsevierPDF()) return; } if (pdf !== null && location.hostname.includes(".apa.")) { // need to remove query string in apa pdf links const url = new URL(pdf); url.search = ''; pdf = url.toString(); } if (jump && pdf !== null && location.href !== pdf) { location.href = pdf; } else { if (doi === null) { doi = doiCrude; if (doi === null) return; } if (tit === null) tit = "Unknown Title"; if (pdf === null) pdf = pdfPathname; toolbox(tit, doi, pdf); } } // newbr, newinput, newtag are util functions to create toolbox elements. function newbr(parent) { parent.appendChild(document.createElement("br")); } function newinput(parent, type, value, onclick) { const i = document.createElement("input"); i.type = type; i.value = value; if (onclick !== null) { i.addEventListener("click", onclick, false); } i.className = "toolbox"; parent.appendChild(i); return i; } function newtag(parent, tag, text) { const i = document.createElement(tag); if (text !== null) { i.textContent = text; } i.className = "toolbox"; parent.appendChild(i); return i; } function loadElsevierPDF() { let pdflink = null; let trials = 0; function getJSON(resolve) { function get() { const json = document.querySelector('script[type="application/json"]'); try { const meta = JSON.parse(json.innerHTML).article.pdfDownload.urlMetadata; pdflink = "/" + meta.path + "/" + meta.pii + meta.pdfExtension + "?md5=" + meta.queryParams.md5 + "&pid=" + meta.queryParams.pid; return true; } catch(e) { return false; } } if (!get()) { if (++trials > 30) { console.error("Auto PDF: Unable to load JSON from DOM!"); throw ""; } setTimeout(() => {getJSON(resolve);}, 1000); return; } resolve(pdflink); } new Promise(getJSON).then((pdflink) => { location.href = pdflink; }).catch(() => {}); return true; } function toolbox(tit, doi, pdf) { const div = document.createElement("div"); div.style = ` z-index: 2147483647; position: fixed; right: 10px; top: 50%; transform: translate(0, -50%); border: 2px groove black; background: white; box-shadow: 6px 6px 3px grey;`; const sheet = document.createElement("style") sheet.textContent = ` .toolbox { font-size: small !important; font-family: sans-serif !important; margin-bottom: 4px !important; margin-top: 0 !important; display: initial !important; line-height: initial !important; } input.toolbox, select.toolbox { background-image: none !important; width: auto; max-height: none; height: initial; } textarea.toolbox, input.toolbox[type=text] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; width: 11em; padding: 0.5rem; border-style: solid; } a.toolbox:hover { color: #006db4; } a.toolbox { cursor: pointer; color: #10147e; } input.toolbox[type=button]:hover { background: #006db4; } input.toolbox[type=button] { padding: 4px 8px; background: #10147e; color: white; border-radius: 4px; border: none; cursor: pointer; } [tooltip]:before { position: absolute; opacity: 0; } [tooltip]:hover:before { content: attr(tooltip); opacity: 1; color: black; background: white; padding: 2px; border: 1px groove black; white-space: nowrap; overflow: hidden; right: 0; margin-top: -25pt; } h2.toolbox { font-size: large !important; } `; div.appendChild(sheet); // Hide and Settings button const hide_btn_parent = newtag(div, "div", null); hide_btn_parent.style = "display: flex !important; justify-content: flex-end; padding-right: 10px;"; const hide_btn = newtag(hide_btn_parent, "a", "✖"); hide_btn.style = "font-size: large !important; text-decoration: none;"; hide_btn.addEventListener("click", () => div.remove(), false); // DOI textbox and copy button const txt_doi = newinput(div,"text",doi,null); newbr(div); newinput(div, "button", "Copy", () => {txt_doi.select(); document.execCommand("Copy");}); newbr(div);newbr(div); // info textbox and copy button const info = tit + "\t" + doi; const txt_inf = newinput(div, "text", info, null); newbr(div); newinput(div, "button", "Copy", () => {txt_inf.select(); document.execCommand("Copy");}); newbr(div);newbr(div); // bibtex button const txt_bib = newtag(div, "textarea", null); txt_bib.style = "resize: none; display: none !important;"; newbr(div); const btn_bib_label = "Copy BibTeX"; const btn_bib = newinput(div, "button", btn_bib_label, loadBib); newbr(div); function loadBib() { btn_bib.disabled = true; btn_bib.value = "Loading"; fetch("https://dx.doi.org/" + doi, { headers: { "Accept": "application/x-bibtex" }}) .then(response => response.text()) .then(data => { txt_bib.value = data; txt_bib.style.display = ""; txt_bib.select(); document.execCommand("Copy"); btn_bib.value = btn_bib_label; btn_bib.disabled = false; }) .catch(error => { alert("Cannot load BibTeX."); btn_bib.value = btn_bib_label; btn_bib.disabled = false; }); } // Text button const btn_txt_label = "Copy Text"; const btn_txt = newinput(div, "button", btn_txt_label, loadTxt); function loadTxt() { if (sty === "") { setstyle(); return; } btn_txt.disabled = true; btn_txt.value = "Loading"; fetch("https://dx.doi.org/" + doi, { headers: { "Accept": "text/x-bibliography; style=" + sty }}) .then(response => response.text()) .then(data => { txt_bib.value = data; txt_bib.style.display = ""; txt_bib.select(); document.execCommand("Copy"); btn_txt.value = btn_txt_label; btn_txt.disabled = false; }) .catch(error => { alert("Cannot load text reference."); btn_txt.value = btn_txt_label; btn_txt.disabled = false; }); } // text style link const stylink = newtag(div, "a", "Style"); stylink.addEventListener("click", setstyle, false); newbr(div); function setstyle() { const div = document.createElement("div"); div.style = ` z-index: 2147483647; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); border: 2px groove black; background: white; padding: 20px; box-shadow: 10px 10px 5px grey;`; newtag(div, "h2", "Choose a citation style for text references:"); const sel = newtag(div, "select", null); sel.size = 15; sel.style.minWidth = "600px"; newbr(div); newtag(div, "p", "This citation style will be saved as default.").style = "margin-top: 5px !important"; newbr(div); const btns = newtag(div, "div", null); btns.style = "display: block !important; text-align: center;"; // OK button newinput(btns, "button", "OK", () => { setStyleAndTooltip(sel.value); GM.setValue("sty", sty); document.body.removeChild(div); loadTxt(); }); // cancel button newinput(btns, "button", "Cancel", () => document.body.removeChild(div)).style.marginLeft = "1em"; // load styles fetch("https://citation.crosscite.org/styles") .then(response => response.json()) .then(data => { for (const i of data) { const x = document.createElement("option"); x.text = i; sel.add(x); } if (sty !== "") { sel.value = sty; } else { sel.selectedIndex = 0; } }) .catch(error => { alert("Cannot load style list."); document.body.removeChild(div); }); document.body.appendChild(div); } // RIS link const rislink = newtag(div, "a", "Download RIS"); rislink.addEventListener("click", loadris); function loadris() { rislink.removeEventListener("click", loadris); fetch("https://dx.doi.org/" + doi, { headers: { "Accept": "application/x-research-info-systems" }}) .then(response => response.text()) .then(data => { const blob = new Blob([data], {type: "octet/stream"}); const url = URL.createObjectURL(blob); rislink.href = url; rislink.download = doi.replace("/", "_") + ".ris"; rislink.click(); }) .catch(error => { alert("Cannot download RIS."); rislink.addEventListener("click", loadris); }); } newbr(div);newbr(div); // PDF link if (pdf === null && location.hostname.includes("sciencedirect")) { pdf = sessionStorage.getItem(doi); if (pdf === null) { const pdflink = newtag(div, "a", "PDF"); pdflink.addEventListener("click",() => {if (!loadElsevierPDF()) alert("Failed to load PDF link");}); newbr(div);newbr(div); } } if (pdf !== null) { const pdflink = newtag(div,"a","PDF"); pdflink.href = pdf; newbr(div);newbr(div); } // Sci-Hub link const scihubURL = "https://sci-hub.st/"; const scihublink = newtag(div, "a", "Sci-Hub"); if (doi !== null) { scihublink.href = scihubURL + doi; } else { scihublink.href = scihubURL + location.href; } newbr(div);newbr(div); document.body.appendChild(div); function setStyleAndTooltip(v) { sty = v; stylink.setAttribute("tooltip", v === null ? "Reference style not set" : "Current style: " + v); } GM.getValue("sty", null).then(setStyleAndTooltip); } })();