在网页左下角生成一个按钮,从dblp中获取选定文本的BibTeX并复制到剪贴板。支持批量获取,支持从剪贴板读取,支持随时下载,支持导出URL和CSV。白名单模式。
< Feedback on 标题批量导出DBLP的BibTeX
感谢分享,建议可以匹配一下网站,要不然每个页面都显示按钮
好滴,我弄一个配置,允许在某个网站或域名永久关闭这个按钮吧。我这个按钮的使用场景比较杂,有时候搜索的时候顺便也复制一下,不好确定自己在哪个网站上用
感谢分享,建议可以匹配一下网站,要不然每个页面都显示按钮
我设置了只在顶层框架运行,并更新了按钮的样式,增加了一个叉方便直接关闭这个按钮,也增加了屏蔽的配置和选项,你可以更新试试
// ==UserScript==
// @name 标题批量导出DBLP的BibTeX
// @namespace http://tampermonkey.net/
// @version 1.6
// @description 在网页左下角生成一个按钮,从dblp中获取选定文本的BibTeX并复制到剪贴板。支持批量获取,支持从剪贴板读取,支持随时下载,支持导出URL和CSV。
// @author shandianchengzi
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @license GPL-3.0
// @downloadURL https://update.greasyfork.org/scripts/522825/%E6%A0%87%E9%A2%98%E6%89%B9%E9%87%8F%E5%AF%BC%E5%87%BADBLP%E7%9A%84BibTeX.user.js
// @updateURL https://update.greasyfork.org/scripts/522825/%E6%A0%87%E9%A2%98%E6%89%B9%E9%87%8F%E5%AF%BC%E5%87%BADBLP%E7%9A%84BibTeX.meta.js
// ==/UserScript==
(function () {
'use strict';
// =========================
// 0) “显示策略”相关配置
// =========================
const HOST = location.hostname;
const ORIGIN = location.origin;
// dblp 网站:始终显示
const isDblpSite = /(^|\.)dblp\.org$/i.test(HOST);
// 非 dblp 网站:默认是否显示(用户可在菜单里切换)
// 默认:false(只在 dblp 显示)
const KEY_SHOW_NON_DBLP_DEFAULT = 'show_non_dblp_default';
const showNonDblpDefault = GM_getValue(KEY_SHOW_NON_DBLP_DEFAULT, false);
// 白名单:哪些站点始终显示(非 dblp 也可以强制显示)
// 建议用 origin 级别(https://example.com)
const KEY_ALWAYS_SHOW_ORIGINS = 'always_show_origins';
function getAlwaysShowOrigins() {
const v = GM_getValue(KEY_ALWAYS_SHOW_ORIGINS, []);
return Array.isArray(v) ? v : [];
}
function setAlwaysShowOrigins(arr) {
GM_setValue(KEY_ALWAYS_SHOW_ORIGINS, arr);
}
function isOriginAlwaysShow(origin) {
return getAlwaysShowOrigins().includes(origin);
}
// —— 菜单:切换“非DBLP默认显示/隐藏”
GM_registerMenuCommand(
(navigator.language || '').startsWith('zh')
? `非DBLP默认:${showNonDblpDefault ? '显示' : '隐藏'}(点击切换)`
: `Non-DBLP default: ${showNonDblpDefault ? 'SHOW' : 'HIDE'} (Click to toggle)`,
function () {
const newVal = !GM_getValue(KEY_SHOW_NON_DBLP_DEFAULT, false);
GM_setValue(KEY_SHOW_NON_DBLP_DEFAULT, newVal);
alert(
(navigator.language || '').startsWith('zh')
? `已设置:非DBLP网站默认${newVal ? '显示' : '隐藏'}按钮。\n刷新页面生效。`
: `Set: Non-DBLP sites default ${newVal ? 'SHOW' : 'HIDE'} button.\nRefresh to take effect.`
);
}
);
// —— 菜单:对当前站点加入/移除“始终显示”
const alreadyAlwaysShow = isOriginAlwaysShow(ORIGIN);
GM_registerMenuCommand(
(navigator.language || '').startsWith('zh')
? (alreadyAlwaysShow ? '取消:此站点始终显示按钮' : '设置:此站点始终显示按钮')
: (alreadyAlwaysShow ? 'Disable: Always show on this site' : 'Enable: Always show on this site'),
function () {
const list = getAlwaysShowOrigins();
const idx = list.indexOf(ORIGIN);
if (idx >= 0) list.splice(idx, 1);
else list.push(ORIGIN);
setAlwaysShowOrigins(list);
alert(
(navigator.language || '').startsWith('zh')
? `已更新站点白名单。\n当前:${list.length} 个站点。\n刷新页面生效。`
: `Whitelist updated.\nNow: ${list.length} site(s).\nRefresh to take effect.`
);
}
);
// —— 菜单:查看/清空白名单
GM_registerMenuCommand(
(navigator.language || '').startsWith('zh') ? '查看/清空:站点白名单' : 'View/Clear: Site whitelist',
function () {
const list = getAlwaysShowOrigins();
const text =
list.length === 0
? ((navigator.language || '').startsWith('zh') ? '白名单为空。' : 'Whitelist is empty.')
: list.join('\n');
const wantClear = confirm(
(navigator.language || '').startsWith('zh')
? `当前白名单如下:\n\n${text}\n\n是否清空白名单?`
: `Current whitelist:\n\n${text}\n\nClear whitelist?`
);
if (wantClear) {
setAlwaysShowOrigins([]);
alert((navigator.language || '').startsWith('zh') ? '已清空白名单,刷新生效。' : 'Cleared. Refresh to take effect.');
}
}
);
// =========================
// 1) 根据策略决定:本页是否注入按钮/UI
// =========================
const shouldShowHere = isDblpSite || isOriginAlwaysShow(ORIGIN) || showNonDblpDefault;
// 如果不该显示:不注入UI(但菜单已注册,用户仍可在此页直接设置)
if (!shouldShowHere) return;
// =========================
// 2) 原脚本内容(基本保持不变)
// ——从这里开始是你原来的逻辑
// =========================
// Inject Custom CSS
const css = `
#dblp-batch-overlay {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.85);
color: white;
padding: 25px;
border-radius: 10px;
z-index: 100000;
text-align: center;
min-width: 400px;
max-width: 90%;
backdrop-filter: blur(5px);
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: none;
}
#dblp-batch-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #fff;
}
#dblp-batch-current {
font-size: 14px;
margin-bottom: 20px;
color: #ccc;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 450px;
margin-left: auto;
margin-right: auto;
min-height: 20px;
}
.dblp-btn {
border: none;
border-radius: 6px;
padding: 8px 15px;
cursor: pointer;
font-weight: bold;
transition: all 0.2s;
margin: 5px;
outline: none;
font-size: 13px;
display: inline-flex;
align-items: center;
justify-content: center;
}
#dblp-btn-download {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
#dblp-btn-download:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(118, 75, 162, 0.4); }
#dblp-btn-copy-urls {
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
color: white;
}
#dblp-btn-copy-urls:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(56, 239, 125, 0.4); }
#dblp-btn-csv {
background: linear-gradient(135deg, #ff9966 0%, #ff5e62 100%);
color: white;
}
#dblp-btn-csv:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(255, 94, 98, 0.4); }
#dblp-btn-close {
background: rgba(255, 255, 255, 0.15);
color: #ddd;
border: 1px solid rgba(255,255,255,0.2);
}
#dblp-btn-close:hover { background: rgba(255, 255, 255, 0.25); color: white; }
/* Confirm Modal */
#dblp-confirm-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
color: #333;
padding: 20px;
border-radius: 8px;
z-index: 100001;
box-shadow: 0 10px 25px rgba(0,0,0,0.5);
text-align: center;
max-width: 400px;
display: none;
}
#dblp-confirm-text {
background: #f5f5f5;
padding: 10px;
margin: 10px 0;
border-radius: 4px;
font-family: monospace;
text-align: left;
max-height: 100px;
overflow-y: auto;
font-size: 12px;
}
`;
if (typeof GM_addStyle !== 'undefined') {
GM_addStyle(css);
} else {
const styleNode = document.createElement('style');
styleNode.innerHTML = css;
document.head.appendChild(styleNode);
}
// Toast function
function Toast(msg, duration) {
duration = isNaN(duration) ? 3000 : duration;
var m = document.createElement('div');
m.innerHTML = msg;
m.style.cssText =
"font-family: 'siyuan'; max-width: 60%; min-width: 150px; padding: 10px 14px; height: auto; color: rgb(255, 255, 255); line-height: 1.5; text-align: center; border-radius: 4px; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 999999; background: rgba(0, 0, 0, 0.7); font-size: 16px;";
document.body.appendChild(m);
setTimeout(function () {
m.style.transition = 'opacity 0.5s ease-in';
m.style.opacity = '0';
setTimeout(function () {
if (m.parentNode) document.body.removeChild(m);
}, 500);
}, duration);
}
var headers = {
Accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.36',
Referer: 'https://dblp.org/',
};
var lang = navigator.appName == 'Netscape' ? navigator.language : navigator.userLanguage;
var lang_hint = {
error_no_text: '没有选中文本且剪贴板为空!',
clipboard_confirm: '未选中文本。是否使用剪贴板内容?',
clipboard_read_err: '无法读取剪贴板,请手动选择文本。',
fetching_one: '正在获取...',
done_copy: '已完成并复制!',
batch_title: (cur, total) => `批量提取中: ${cur} / ${total}`,
batch_done_title: '批量提取完成',
download_btn: '下载 BibTeX (.bib)',
copy_urls_btn: '仅复制 URL',
csv_btn: '下载表格 (.csv)',
close_btn: '关闭面板',
current_prefix: '正在搜索: ',
default_btn: 'Get BibTeX',
urls_copied: 'URLs 已复制到剪贴板!',
};
if (!lang.startsWith('zh')) {
lang_hint = {
error_no_text: 'No text selected and clipboard is empty!',
clipboard_confirm: 'No text selected. Use clipboard content?',
clipboard_read_err: 'Cannot read clipboard.',
fetching_one: 'Fetching...',
done_copy: 'Done & Copied!',
batch_title: (cur, total) => `Processing: ${cur} / ${total}`,
batch_done_title: 'Batch Complete',
download_btn: 'Download BibTeX',
copy_urls_btn: 'Copy URLs',
csv_btn: 'Download CSV',
close_btn: 'Close',
current_prefix: 'Searching: ',
default_btn: 'Get BibTeX',
urls_copied: 'URLs copied to clipboard!',
};
}
// --- UI Elements Creation ---
// 1. Trigger Button
const button = document.createElement('button');
button.innerText = lang_hint.default_btn;
button.style.cssText =
'position: fixed; bottom: 10px; left: 10px; z-index: 9999; padding: 10px; background-color: #007BFF; color: white; border: none; border-radius: 5px; cursor: pointer; white-space: pre; text-align: center;';
document.body.appendChild(button);
// 2. Batch Overlay
const overlay = document.createElement('div');
overlay.id = 'dblp-batch-overlay';
overlay.innerHTML = `
`;
document.body.appendChild(overlay);
// 3. Confirm Modal
const confirmModal = document.createElement('div');
confirmModal.id = 'dblp-confirm-modal';
confirmModal.innerHTML = `
`;
document.body.appendChild(confirmModal);
// --- Logic Variables ---
let batchResults = []; // Stores BibTeX strings
let batchLines = []; // Stores original queries
let isBatchProcessing = false;
// Robust function to extract fields from BibTeX (handles nested braces and multi-lines)
function extractBibField(bibtex, fieldName) {
if (!bibtex || bibtex === 'None') return 'None';
const regex = new RegExp(`${fieldName}\\s*=\\s*\\{`, 'i');
const match = bibtex.match(regex);
if (!match) return 'None';
let openCount = 1;
let content = '';
let startIndex = match.index + match[0].length;
for (let i = startIndex; i < bibtex.length; i++) {
const char = bibtex[i];
if (char === '{') openCount++;
else if (char === '}') openCount--;
if (openCount === 0) break;
content += char;
}
return content.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ').trim();
}
function downloadContent(content, filename) {
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || 'download.txt';
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function fetchBibTeX(query, silent, callback) {
const searchUrl = `https://dblp.org/search?q=${encodeURIComponent(query)}`;
GM_xmlhttpRequest({
method: 'GET',
url: searchUrl,
headers: headers,
onload: function (response) {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, 'text/html');
const bibLink = doc.querySelector('a[href*="?view=bibtex"]');
if (!bibLink) {
if (!silent) Toast('BibTeX Not Found');
callback('None');
return;
}
const bibUrl = bibLink.href.replace('.html?view=bibtex', '.bib');
GM_xmlhttpRequest({
method: 'GET',
url: bibUrl,
headers: headers,
onload: function (bibResponse) {
callback(bibResponse.responseText);
},
onerror: function () {
if (!silent) Toast('Error fetching bib file');
callback('None');
},
});
},
onerror: function () {
if (!silent) Toast('Error searching DBLP');
callback('None');
},
});
}
// --- Event Handlers ---
const titleEl = document.getElementById('dblp-batch-title');
const currentEl = document.getElementById('dblp-batch-current');
const downloadBtn = document.getElementById('dblp-btn-download');
const csvBtn = document.getElementById('dblp-btn-csv');
const copyUrlsBtn = document.getElementById('dblp-btn-copy-urls');
const closeBtn = document.getElementById('dblp-btn-close');
function getResultsSoFar() {
return batchLines
.map((line, idx) => ({
line: line,
bib: batchResults[idx],
}))
.filter((item) => item.bib !== null && item.bib !== undefined);
}
downloadBtn.onclick = () => {
const results = getResultsSoFar();
if (results.length === 0) {
Toast('Nothing fetched yet.');
return;
}
const content = results
.map((r) => (r.bib === 'None' ? `% Failed to fetch: ${r.line}` : r.bib))
.join('\n\n');
downloadContent(content, 'dblp_bibtex.bib');
};
copyUrlsBtn.onclick = () => {
const results = getResultsSoFar();
if (results.length === 0) {
Toast('Nothing fetched yet.');
return;
}
const urlList = results
.map((r) => {
if (r.bib === 'None') return 'None';
return extractBibField(r.bib, 'url');
})
.join('\n');
GM_setClipboard(urlList);
Toast(lang_hint.urls_copied);
};
csvBtn.onclick = () => {
const results = getResultsSoFar();
if (results.length === 0) {
Toast('Nothing fetched yet.');
return;
}
let csvContent = '\uFEFF原始搜索词,提取标题,URL,BibTeX\n';
const esc = (val) => {
if (val === null || val === undefined) return '';
val = String(val);
if (val.search(/("|,|\n|\r)/g) >= 0) {
return `"${val.replace(/"/g, '""')}"`;
}
return val;
};
csvContent += results
.map((r) => {
if (r.bib === 'None') {
return `${esc(r.line)},None,None,None`;
}
const title = extractBibField(r.bib, 'title');
const url = extractBibField(r.bib, 'url');
return `${esc(r.line)},${esc(title)},${esc(url)},${esc(r.bib)}`;
})
.join('\n');
downloadContent(csvContent, 'dblp_results.csv');
};
closeBtn.onclick = () => {
overlay.style.display = 'none';
isBatchProcessing = false;
};
function askClipboard(text) {
return new Promise((resolve) => {
document.getElementById('dblp-confirm-text').innerText =
text.length > 200 ? text.substring(0, 200) + '...' : text;
confirmModal.style.display = 'block';
document.getElementById('dblp-confirm-yes').onclick = () => {
confirmModal.style.display = 'none';
resolve(true);
};
document.getElementById('dblp-confirm-no').onclick = () => {
confirmModal.style.display = 'none';
resolve(false);
};
});
}
button.addEventListener('click', async () => {
if (isBatchProcessing) {
Toast('正在批量处理中,请使用中间面板控制');
return;
}
let selection = window.getSelection().toString().trim();
if (!selection) {
try {
const clipText = await navigator.clipboard.readText();
if (clipText && clipText.trim()) {
const useClip = await askClipboard(clipText.trim());
if (useClip) selection = clipText.trim();
else return;
} else {
Toast(lang_hint.error_no_text);
return;
}
} catch (e) {
Toast(lang_hint.clipboard_read_err);
return;
}
}
if (!selection) return;
const lines = selection
.split(/[\r\n]+/)
.map((s) => s.trim())
.filter((s) => s);
if (lines.length === 0) return;
if (lines.length === 1) {
Toast(lang_hint.fetching_one, 1000);
fetchBibTeX(lines[0], false, (res) => {
if (res && res !== 'None') {
GM_setClipboard(res);
Toast(lang_hint.done_copy);
} else {
Toast('Failed: ' + lines[0]);
}
});
} else {
isBatchProcessing = true;
batchLines = lines;
batchResults = new Array(lines.length).fill(null);
let completedCount = 0;
overlay.style.display = 'block';
closeBtn.style.display = 'none';
titleEl.innerText = lang_hint.batch_title(0, lines.length);
currentEl.innerText = 'Initializing...';
lines.forEach((line, index) => {
setTimeout(() => {
if (!isBatchProcessing) return;
currentEl.innerText = lang_hint.current_prefix + line;
fetchBibTeX(line, true, (result) => {
batchResults[index] = result === 'None' ? 'None' : result;
completedCount++;
if (isBatchProcessing) {
titleEl.innerText = lang_hint.batch_title(completedCount, lines.length);
}
if (completedCount === lines.length) {
isBatchProcessing = false;
titleEl.innerText = lang_hint.batch_done_title;
currentEl.innerText = '';
closeBtn.style.display = 'inline-block';
const finalContent = batchResults.map((r) => (r === 'None' ? '% Failed' : r)).join('\n\n');
GM_setClipboard(finalContent);
Toast(lang_hint.done_copy);
}
});
}, index * 800);
});
}
});
})();
感谢分享,建议可以匹配一下网站,要不然每个页面都显示按钮