导出文献的Bibtex(镜像站)
// ==UserScript==
// @name 导出文献的Bibtex(镜像站)
// @namespace jw23
// @version 0.1.13
// @description 1. 支持从知网和谷歌学术镜像导出BiBTex。2. 从google scholar(谷歌学术)导入文献到petal文献库。
// @author jw23
// @match https://kns.cnki.net/kns8/defaultresult/index
// @match https://kns.cnki.net/KXReader/Detai*
// @match https://scholar.google.com/scholar*
// @match https://scholar.google*
// @match https://s.wanfangdata.com.cn/paper*
// @match https://*.studiodahu.com*
// @match https://usercontent.beijingbang.top*
// @match https://*/scholar*
// @match https://cite.petal.org*
// @match https://www.bing.com*
// @match https://usercontent.beijingbang.top*
// @match https://*.sciencemag.org/*
// @match http*://*.webofknowledge.com/*
// @match https://academic.oup.com/*
// @match https://academic.microsoft.com/*
// @match https://dl.acm.org/doi/*
// @match https://ieeexplore.ieee.org/*
// @match https://journals.sagepub.com/*
// @match https://link.springer.com/*
// @match https://onlinelibrary.wiley.com/doi/*
// @match https://psycnet.apa.org/*
// @match https://pubmed.ncbi.nlm.nih.gov/*
// @match https://pubs.rsc.org/*
// @match https://pubs.acs.org/doi/*
// @match https://pubsonline.informs.org/doi/abs/*
// @match https://schlr.cnki.net/Detail/index/*
// @match https://schlr.cnki.net//Detail/index/*
// @match https://xueshu.baidu.com/usercenter/paper/*
// @match https://www.ingentaconnect.com/*
// @match https://www.jstor.org/*
// @match https://www.nature.com/*
// @match https://www.ncbi.nlm.nih.gov/*
// @match https://www.researchgate.net/*
// @match https://www.sciencedirect.com/*
// @match http://www.socolar.com/*
// @match https://www.scinapse.io/*
// @match https://www.science.org/*
// @match https://www.tandfonline.com/*
// @match https://www.webofscience.com/wos/*
// @require https://raw.githubusercontent.com/ORCID/bibtexParseJs/master/bibtexParse.js
// @require https://raw.githubusercontent.com/TheWindRises-2/coco-message/main/coco-message.min.js
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @grant GM_setClipboard
// @grant unsafeWindow
// @grant CAT_userConfig
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @license MIT
// ==/UserScript==
/* ==UserConfig==
configure:
auth:
title: 令牌
description: 请输入petal的令牌
type: text
id:
title: 组织ID
description: 请输入您的组织ID
type: select
bind: $organizations
userId:
title: 用户ID
description: 这是用户ID
update:
title: 每次访问petal时更新令牌
description: 每次访问将会自动获取令牌和组织ID
type: checkbox
default: true
refStyle:
title: 引用格式
description: 引用的格式
type: select
bind: $refStyles
mainUrl:
title: 谷歌学术URL($QUERYWORD泛指要查询的关键词)
description: 谷歌学术的地址($QUERYWORD泛指要查询的关键词)
type: text
default: https://scholar.google.com.hk/scholar?q=$QUERYWORD
isCopy:
title: 是否复制bibex到剪切板
description: 选中即每次导入自动复制bibtex到剪切板
type: checkbox
default: false
remainTime:
title: 悬浮窗口停留时间
description: 按Shift+S时弹窗停留时间
type: number
unit: 毫秒
min: 5000
max: 15000
default: 8000
==/UserConfig== */
let Authorization = GM_getValue("configure.auth", "");
let organization_name = GM_getValue("configure.id", "");
(function () {
'use strict';
document.querySelectorAll('div.MuiListItemText-root>p.MuiTypography-root.MuiTypography-body2.org-name').forEach((e) => {
e.click = () => {
refresh_data()
}
})
GM_registerMenuCommand("去Google Scholar查询", () => {
goGoogleScholar()
})
waitUtil("div.MuiListItemText-root>p.MuiTypography-root.MuiTypography-body2.org-name", () => {
document.querySelectorAll('div.MuiListItemText-root>p.MuiTypography-root.MuiTypography-body2.org-name').forEach((e) => {
e.onclick = () => {
refresh_data()
}
})
}, 6000)
const refresh_data = () => {
var connection = indexedDB.open('localforage', 2)
connection.onsuccess = (e) => {
var database = e.target.result;
var transaction = database.transaction(['keyvaluepairs']);
var objectStore = transaction.objectStore('keyvaluepairs');
var req = objectStore.get('persist:root')
req.onsuccess = (e) => {
let data = JSON.parse(e.target.result)
let user = JSON.parse(data.session).user
let token = user['access_token']
GM_setValue("configure.userId", user.id)
let documents = [];
for (let org of user.organizations) {
documents.push({ "id": org.id, "value": org.name })
}
GM_setValue("documents", documents)
GM_setValue("configure.id", findOrgName(JSON.parse(data.session).currentOrganizationId))
cocoMessage.info(3000, `Current organization has been changed to ${findOrgName(JSON.parse(data.session).currentOrganizationId)}`)
GM_setValue("organizations", documents.map(v => v.value))
/* GM_xmlhttpRequest({
url: `https://cite.petal.org/api/user/${data.id}/organization/list`,
method: "GET",
headers: {
"Authorization": Authorization,
},
onload: response => {
let documents = [];
let lists = JSON.parse(response.response);
for (let item of lists) {
documents.push({ "id": item.id, "value": item.name })
}
GM_setValue("documents", documents)
GM_setValue("organizations", documents.map(v => v.value))
}
}) */
GM_setValue("configure.auth", `Bearer ${token}`)
cocoMessage.info(3000, "Get the token and id successfully")
}
}
}
refresh_data()
document.onkeydown = (e) => {
if (e.shiftKey && e.key == "S") {
goGoogleScholar()
} else if (e.shiftKey && e.key == "L") {
CAT_userConfig()
}
}
const [open, closeDialog] = createLoading()
const lauch_in_cnki = () => {
setTimeout(() => {
waitUnUtil("div.divLoading", () => {
console.log("开始插入元素")
waitUtil(".cbItem", () => {
insertToHTML()
}, 3000)
}, 8000)
}, 1000)
}
if (document.URL.indexOf("cnki") != -1) {
lauch_in_cnki()
document.onkeydown = (e) => {
if (e.key == "Enter") {
lauch_in_cnki()
}
if (e.ctrlKey && e.shiftKey && e.key == "C") {
GM_setClipboard(document.getSelection().toString())
}
}
document.querySelector("input.search-btn").onclick = () => {
lauch_in_cnki()
}
} else if (document.URL.indexOf("scholar.google") != -1) {
waitUtil(".gs_r.gs_or.gs_scl", () => {
let items = document.querySelectorAll(".gs_r.gs_or.gs_scl")
for (let item of items) {
let id = item.querySelector('h3>a').id
let box = item.querySelector('div.gs_fl:nth-last-child(1)')
let btn = document.createElement('a')
btn.style = "cursor:pointer"
btn.text = "import to petal"
let btn2 = document.createElement('a')
btn2.style = "cursor:pointer"
btn2.text = "go to cnki"
let btn3 = document.createElement('a')
btn3.style = "cursor:pointer"
btn3.text = "copy reference"
btn2.onclick = () => {
GM_setClipboard(item.querySelector('h3').textContent)
window.open("https://kns.cnki.net/kns8/defaultresult/index");
}
btn3.onclick = () => {
getBixTexPageFromGoogle(id).then(page => {
let dom = document.createElement("div")
dom.innerHTML = page;
let refs = dom.querySelectorAll("#gs_citt tr")
let styles = []
for (let ref of refs) {
let v = ref.querySelector('.gs_citr').textContent.trim();
let styleName = ref.querySelector('.gs_cith').textContent.trim();
if (GM_getValue("configure.refStyle", "GB/T 7714") == styleName) {
GM_setClipboard(v)
cocoMessage.info(3000, "Copy reference successfullly")
}
styles.push({ styleName: styleName, ref: v })
}
GM_setValue("refStyles", styles.map(s => s.styleName))
}).catch(err => {
cocoMessage.error("复制Ref失败:" + err, 3000);
})
}
btn.onclick = () => {
open()
getBixTexPageFromGoogle(id).then(page => {
let dom = document.createElement("div")
dom.innerHTML = page;
let lists = dom.querySelector("#gs_citi")
let first = lists.querySelector("a").href
GM_xmlhttpRequest({
url: first,
method: "GET",
onload: (response) => {
uploadToPetal(response.responseText)
closeDialog()
if (GM_getValue("configure.isCopy", false))
GM_setClipboard(response.responseText)
},
onerror: () => {
closeDialog()
}
})
}).catch(err => {
cocoMessage.error("获取BibTex失败:" + err, 3000);
})
}
box.appendChild(btn)
box.appendChild(btn2)
box.appendChild(btn3)
}
}, 5000)
} else if (document.URL.indexOf("wanfangdata") != -1) {
waitUtil("div.normal-list", () => {
let list = document.querySelectorAll("div.normal-list");
for (let article of list) {
let title = article.querySelector("span.title").textContent;
let button_group = article.querySelector("div.button-list>div")
let btn_google = document.createElement("div")
btn_google.className = "wf-list-button"
btn_google.textContent = "去Google学术查看"
btn_google.onclick = () => { window.open(`https://scholar.google.com.hk/scholar?hl=zh-CN&as_sdt=0%2C5&q=${title}&btnG=`) }
button_group.appendChild(btn_google)
}
}, 8000)
} else {
waitUtil(".gs_r.gs_or.gs_scl", () => {
let items = document.querySelectorAll(".gs_r.gs_or.gs_scl")
for (let item of items) {
let id = item.querySelector('h3>a').id
let box = item.querySelector('div.gs_fl:nth-last-child(1)')
let btn = document.createElement('a')
btn.style = "cursor:pointer"
btn.text = "import to petal"
let btn2 = document.createElement('a')
btn2.style = "cursor:pointer"
btn2.text = "go to cnki"
btn2.onclick = () => {
GM_setClipboard(item.querySelector('h3').textContent)
window.open("https://kns.cnki.net/kns8/defaultresult/index");
}
let btn3 = document.createElement('a')
btn3.style = "cursor:pointer"
btn3.text = "copy reference"
btn3.onclick = () => {
getBixTexPageFromGoogle(id).then(page => {
let dom = document.createElement("div")
dom.innerHTML = page;
let refs = dom.querySelectorAll("#gs_citt tr")
let styles = []
for (let ref of refs) {
let v = ref.querySelector('.gs_citr').textContent.trim();
let styleName = ref.querySelector('.gs_cith').textContent.trim();
if (GM_getValue("configure.refStyle", "GB/T 7714") == styleName) {
GM_setClipboard(v)
cocoMessage.info(3000, "Copy reference successfullly")
}
styles.push({ styleName: styleName, ref: v })
}
GM_setValue("refStyles", styles.map(s => s.styleName))
}).catch(err => {
cocoMessage.error("复制Ref失败:" + err, 3000);
})
}
btn.onclick = () => {
open()
getBixTexPageFromGoogle(id).then(page => {
let dom = document.createElement("div")
dom.innerHTML = page;
let first = dom.querySelector("#gs_citi>a.gs_citi").href
GM_xmlhttpRequest({
url: first,
method: "GET",
onload: (response) => {
uploadToPetal(response.responseText)
closeDialog()
if (GM_getValue("configure.isCopy", false))
GM_setClipboard(response.responseText)
},
onerror: () => {
closeDialog()
}
})
}).catch(err => {
cocoMessage.error("Failed in getting bibtex:" + err, 3000);
})
}
box.appendChild(btn)
box.appendChild(btn2)
box.appendChild(btn3)
}
}, 5000)
}
setInterval(() => {
closeDialog()
}, 5000)
})();
function createLoading() {
let loading = document.createElement("div");
loading.style = `position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: rgb(0 0 0 / 70%);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999`
loading.id = "jw-loading"
loading.className = "jw-hidden"
loading.innerHTML = `
<style>
.jw-hidden{
visibility: hidden
}
@keyframes turn {
0% {
transform: rotate(0deg);
}
33.3%{
transform: rotate(120deg);
}
66.7%{
transform: rotate(240deg);
}
100%{
transform: rotate(360deg);
}
.loading-icon{
animation: turn 10s linear infinite;
}
</style>
<svg t="1677564985321" class="loading-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2797" width="32" height="32" style="animation: turn 2s linear infinite;"><path d="M480 96a32 32 0 0 1 64 0v192a32 32 0 0 1-64 0V96z m250.624 60.64a32 32 0 1 1 51.776 37.632l-112.832 155.328a32 32 0 0 1-51.808-37.632l112.864-155.328z m167.136 196.384a32 32 0 1 1 19.776 60.864l-182.624 59.328a32 32 0 0 1-19.776-60.864l182.624-59.328z m19.776 257.088a32 32 0 1 1-19.776 60.864l-182.624-59.328a32 32 0 0 1 19.776-60.864l182.624 59.328zM782.4 829.76a32 32 0 0 1-51.776 37.632l-112.864-155.328a32 32 0 1 1 51.808-37.632l112.832 155.328zM544 928a32 32 0 0 1-64 0v-192a32 32 0 0 1 64 0v192z m-250.624-60.64a32 32 0 0 1-51.776-37.632l112.832-155.328a32 32 0 0 1 51.808 37.632l-112.864 155.328z m-167.136-196.384a32 32 0 1 1-19.776-60.864l182.624-59.328a32 32 0 0 1 19.776 60.864l-182.624 59.328z m-19.776-257.088a32 32 0 0 1 19.776-60.864l182.624 59.328a32 32 0 1 1-19.776 60.864l-182.624-59.328zM241.6 194.24a32 32 0 1 1 51.776-37.632l112.864 155.328a32 32 0 1 1-51.808 37.632L241.6 194.24z" fill="#1296db" p-id="2798"></path></svg>
`
document.body.appendChild(loading)
let dialog = document.querySelector('#jw-loading')
return [() => {
dialog.className = ""
}, () => {
dialog.className = "jw-hidden"
}]
}
function insertToHTML() {
const [open, closeDialog] = createLoading()
let items = document.querySelectorAll('.cbItem')
for (let checkbox of items) {
checkbox.onchange = (e) => {
open()
let filename = e.target.value;
getBixTex(filename).then(bib => {
cocoMessage.info(3000, "BiBTex has been copied to clipboard");
closeDialog()
if (GM_getValue("configure.isCopy", false))
GM_setClipboard(bib)
}).catch((err) => {
cocoMessage.error("获取BibTex失败:" + err, 3000);
closeDialog()
})
}
}
let box = document.querySelector("#batchOpsBox")
let li = document.createElement("li")
li.textContent = "导出所有的BibTex"
li.onclick = () => {
let total = ""
for (let checkbox of items) {
if (checkbox.checked) {
getBixTex(checkbox.value).then(bib => {
total += bib;
if (GM_getValue("configure.isCopy", false))
GM_setClipboard(total)
})
}
}
cocoMessage.info(3000, "BiBTex全部导出成功");
}
box.appendChild(li)
let lines = document.querySelectorAll("tbody>tr")
for (let line of lines) {
let icon = document.createElement('td');
icon.textContent = "BibTex"
icon.style = "cursor:pointer"
let checkbox = line.querySelector('.cbItem')
icon.onclick = () => {
getBixTex(checkbox.value).then(bib => {
uploadToPetal(bib)
})
}
line.appendChild(icon)
}
}
function getBixTex(filename) {
// GM_xmlhttpRequest({
// url: "https://kns.cnki.net/dm/api/ShowExport",
// headers: { "Content-Type": "application/x-www-form-urlencoded" },
// data: encodeURIComponent(`FileName=${filename}&DisplayMode=BibTex&OrderParam=0&OrderType=desc&SelectField=&PageIndex=1&PageSize=20&language=&uniplatform=NZKPT&random=${Math.random()}`),
// onload: (response) => {
// console.log("得到结果:");
// console.log(response.responseText)
// },
// onerror: (err) => {
// console.log("获取BixTex失败", err)
// }
// })
return fetch("https://kns.cnki.net/dm/api/ShowExport", {
"headers": {
"accept": "text/plain, */*; q=0.01",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"content-type": "application/x-www-form-urlencoded",
"sec-ch-ua": "\"Chromium\";v=\"110\", \"Not A(Brand\";v=\"24\", \"Microsoft Edge\";v=\"110\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XMLHttpRequest"
},
"referrerPolicy": "strict-origin-when-cross-origin",
"body": `FileName=${filename}&DisplayMode=BibTex&OrderParam=0&OrderType=desc&SelectField=&PageIndex=1&PageSize=20&language=&uniplatform=NZKPT&random=0.6662329504606448`,
"method": "POST",
"mode": "cors",
"credentials": "include"
}).then(respose => respose.text()).then(result => {
let dom = document.createElement("div")
dom.innerHTML = result;
let bib = dom.querySelector("li")
// console.log(bib.textContent)
return bib.textContent
})
}
function waitUtil(ele, callback, timeout) {
let success = false;
let id = setInterval(function () {
let target = document.querySelector(ele)
if (target != null) {
success = true
clearInterval(id);
callback(target)
}
}, 100)
setTimeout(() => {
if (!success) {
clearInterval(id)
console.log("[何碧]页面超时")
}
}, timeout)
}
function waitUnUtil(ele, callback, timeout) {
let success = false;
let id = setInterval(function () {
let target = document.querySelector(ele)
if (target == null) {
success = true
clearInterval(id);
callback(target)
}
}, 100)
setTimeout(() => {
if (!success) {
clearInterval(id)
cocoMessage.error("[何碧]页面超时", 3000);
console.log("[何碧]页面超时")
}
}, timeout)
}
function getBixTexPageFromGoogle(id) {
return fetch(`/scholar?q=info:${id}:scholar.google.com/&output=cite&scirp=1&hl=zh-CN`, {
"headers": {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"sec-ch-ua": "\"Chromium\";v=\"110\", \"Not A(Brand\";v=\"24\", \"Microsoft Edge\";v=\"110\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-requested-with": "XHR"
},
"referrerPolicy": "origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
}).then(response => response.text()).then(result => { return result })
}
function uploadToPetal(bib) {
if (GM_getValue("configure.auth", "") == "" || GM_getValue("configure.id", "") == "") {
cocoMessage.info(3000, "您没有填写petal的令牌和组织ID", function () {
GM_openInTab("https://cite.petal.org", {
active: true,
insert: true
})
})
return
}
let bibJSON = bibtexParse.toJSON(bib);
let subs = []
for (let i = 0; i < bibJSON.length; i++) {
console.log(bibJSON[i])
subs.push({
"meta_json": JSON.stringify({
"title": bibJSON[i].entryTags.title,
"author": bibJSON[i].entryTags.author,
"journal": bibJSON[i].entryTags.journal,
"volume": bibJSON[i].entryTags.volume,
"number": bibJSON[i].entryTags.number,
"pages": bibJSON[i].entryTags.pages,
"year": bibJSON[i].entryTags.year,
"entrytype": bibJSON[i].entryType,
"citekey": bibJSON[i].citationKey,
"editor": ""
})
})
}
GM_xmlhttpRequest({
url: `https://cite.petal.org/api/organization/${getCurrentID()}/document/list`,
method: "GET",
headers: {
"Authorization": Authorization,
},
onload: (response) => {
let lists = JSON.parse(response.response)
// console.log(Authorization)
console.log(lists)
for (let item of lists) {
if (!bibJSON[0]) {
cocoMessage.error("失败,可能需要验证是否为人机", 3000)
}
if (item.meta.citekey == bibJSON[0].citationKey && !item.is_trash) {
cocoMessage.warning(`导入重复引用(${GM_getValue("configure.id", "")})`, 3000)
return
}
}
GM_xmlhttpRequest({
url: "https://cite.petal.org/api/document/create_stubs",
method: "POST",
headers: {
"Authorization": Authorization,
"Content-Type": "application/json"
},
data: JSON.stringify({
"organization_id": getCurrentID(),
"stubs": subs
}),
onload: response => {
cocoMessage.info(3000, `导入成功(${GM_getValue("configure.id", "")})`, function () {
console.log(response.responseText, ":", GM_getValue("configure.id", ""))
})
}
})
}
})
}
function goGoogleScholar() {
let url = GM_getValue("configure.mainUrl", "https://scholar.google.com.hk/scholar?q=$QUERYWORD")
if (document.getSelection().toString().trim() != "") {
let scholarWindow = window.open(url.replace("$QUERYWORD", document.getSelection().toString().trim()), "scholarWindow", "popup,width=600,height=1000");
setTimeout(() => {
scholarWindow.close()
}, GM_getValue("configure.remainTime", 5000))
} else {
navigator.clipboard
.readText()
.then((v) => {
let scholarWindow = window.open(url.replace("$QUERYWORD", v), "scholarWindow", "popup,width=600,height=1000");
setTimeout(() => {
scholarWindow.close()
}, GM_getValue("configure.remainTime", 5000))
}).catch(_ => {
cocoMessage.error("document不在焦点", 3000)
})
}
}
function getCurrentID() {
for (let doc of GM_getValue("documents", "")) {
if (GM_getValue("configure.id", "") == doc.value) {
return doc.id
}
}
return ""
}
function findOrgName(id) {
for (let doc of GM_getValue("documents", "")) {
if (id == doc.id) {
return doc.value
}
}
return GM_getValue("documents", "")[1].value
}