Publication Auto PDF

Automatically jumps to PDF when you visit a journal article abstract page. Also includes a utility to copy or download citation info.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
  }
})();