// ==UserScript==
// @name 检测网址跳转
// @namespace http://tampermonkey.net/
// @version 1.4.2
// @description 选中一个网址后在新标签页跳转,并添加 F2 弹出窗口功能
// @author 小楠
// @match *://*/*
// @icon https://t.tutu.to/img/kjcbA
// @license MIT
// @grant GM_openInTab
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// ==/UserScript==
let isSelecting = false;
let selectedText = '';
let hasPattern = false;
let showedPopup = false;
let currentPageUrl = window.location.href;
let isDetectionEnabled = true;
let isProcessed = false;
let lastSelectedUrl = '';
let linkRecords = [];
// 当标签重新获得焦点或页面重新可见时,允许再次对相同链接弹窗
try {
window.addEventListener('focus', () => { showedPopup = false; lastSelectedUrl = ''; currentPageUrl = window.location.href; });
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') { showedPopup = false; lastSelectedUrl = ''; currentPageUrl = window.location.href; }
});
} catch {}
function trimTrailingPunctuation(text) {
if (!text) return text;
return text.replace(/[\)\]\}\.,;:!?]+$/g, '');
}
function normalizeUrl(rawUrl) {
if (!rawUrl) return '';
let url = rawUrl.trim();
url = trimTrailingPunctuation(url);
const looksLikeDomain = /^(?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[\w\-\.~!$&'()*+,;=:@%\/?#]*)?$/;
if (looksLikeDomain.test(url) && !/^([a-zA-Z][a-zA-Z0-9+.-]*):/.test(url)) {
return 'https://' + url;
}
return url;
}
function extractUrlsFromText(text) {
if (!text) return [];
const matches = [];
const schemeRegex = /\b((?:https?|ftps?):\/\/[\w\-\.~!$&'()*+,;=:@%\/?#]+)\b/gi;
const specialRegex = /\b(magnet:\?[\w\-\.~!$&'()*+,;=:@%\/?#]+|ed2k:\/\/[\w\-\.~!$&'()*+,;=:@%\/?#]+|thunder:\/\/[\w\-\.~!$&'()*+,;=:@%\/?#]+)\b/gi;
const domainRegex = /\b((?:www\.)?(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[\w\-\.~!$&'()*+,;=:@%\/?#]*)?)\b/g;
const addMatches = (regex, normalizer) => {
let m;
while ((m = regex.exec(text)) !== null) {
const found = normalizer ? normalizer(m[1]) : m[1];
if (found) matches.push(found);
}
};
addMatches(schemeRegex, (u) => normalizeUrl(u));
addMatches(specialRegex, (u) => normalizeUrl(u));
addMatches(domainRegex, (u) => normalizeUrl(u));
const unique = Array.from(new Set(matches)).filter(Boolean);
return unique;
}
function openUrlWithMode(url, mode) {
const finalUrl = normalizeUrl(url);
if (!finalUrl) return;
if (mode === 'backgroundTab') {
if (typeof GM_openInTab === 'function') {
GM_openInTab(finalUrl, { active: false, insert: true, setParent: true });
} else {
window.open(finalUrl, '_blank');
}
} else if (mode === 'foregroundTab') {
if (typeof GM_openInTab === 'function') {
GM_openInTab(finalUrl, { active: true, insert: true, setParent: true });
} else {
window.open(finalUrl, '_blank');
}
} else if (mode === 'newWindow') {
window.open(finalUrl, '_blank', 'noopener,noreferrer,width=1200,height=800');
} else {
window.open(finalUrl, '_blank');
}
}
function batchOpen(urls, mode, delayMs = 200) {
if (!Array.isArray(urls) || urls.length === 0) return;
urls.forEach((u, idx) => {
setTimeout(() => openUrlWithMode(u, mode), idx * delayMs);
});
}
document.addEventListener('click', function (event) {
const anchor = event.target && event.target.closest ? event.target.closest('a[href]') : null;
if (!anchor) return;
const href = anchor.href;
if (!href) return;
const isCtrlLike = event.ctrlKey || event.metaKey;
const isShift = event.shiftKey;
if (isCtrlLike) {
event.preventDefault();
event.stopPropagation();
openUrlWithMode(href, 'backgroundTab');
} else if (isShift) {
event.preventDefault();
event.stopPropagation();
openUrlWithMode(href, 'newWindow');
}
}, true);
const KEY_ALIASES = 'domainAliases.v1';
const CODE_KEY_PREFIX = 'pendingExtractCode.';
const KEY_ALIASES_BUILTIN_OVERRIDE = 'domainAliases.builtinOverride.v1';
const BUILT_IN_ALIASES = {};
function getUserAliases() {
try { return GM_getValue(KEY_ALIASES, {}); } catch { return {}; }
}
function getAliases() {
const builtin = BUILT_IN_ALIASES || {};
let override = {};
try { override = GM_getValue(KEY_ALIASES_BUILTIN_OVERRIDE, {}); } catch {}
try {
const userMap = GM_getValue(KEY_ALIASES, {});
return Object.assign({}, builtin, override, userMap);
} catch {
return Object.assign({}, builtin, override);
}
}
function setAliases(map) {
try { GM_setValue(KEY_ALIASES, map); } catch {}
}
function upsertAlias(domain, name) {
if (!domain || !name) return;
const map = getUserAliases();
map[domain] = name;
setAliases(map);
}
function getAlias(domain) {
if (!domain) return '';
const map = getAliases();
let host = domain.toLowerCase();
if (map[host]) return map[host];
const nowww = host.replace(/^www\./, '');
if (map[nowww]) return map[nowww];
let h = host;
while (h.indexOf('.') !== -1) {
h = h.substring(h.indexOf('.') + 1);
if (map[h]) return map[h];
const hNoWww = h.replace(/^www\./, '');
if (map[hNoWww]) return map[hNoWww];
}
return '';
}
function getBaseDomain(host) {
try {
const parts = (host || '').split('.');
if (parts.length <= 2) return host || '';
return parts.slice(parts.length - 2).join('.');
} catch { return host || ''; }
}
function setPendingCodeForHost(host, code) {
if (!host || !code) return;
try {
GM_setValue(CODE_KEY_PREFIX + host, code);
const base = getBaseDomain(host);
if (base && base !== host) GM_setValue(CODE_KEY_PREFIX + base, code);
} catch {}
}
function takePendingCodeForHost(host) {
try {
const exactKey = CODE_KEY_PREFIX + host;
let val = GM_getValue(exactKey, '');
if (val) { GM_deleteValue(exactKey); return val; }
const base = getBaseDomain(host);
const baseKey = CODE_KEY_PREFIX + base;
val = GM_getValue(baseKey, '');
if (val) GM_deleteValue(baseKey);
return val || '';
} catch { return ''; }
}
function extractCodesFromText(text) {
if (!text) return [];
const codes = [];
const regex = /(提取码|密码|访问码|暗号|口令|Pass\s*Code|Access\s*Code)[:: ]*([a-zA-Z0-9]{4,8})/g;
let m;
while ((m = regex.exec(text)) !== null) {
codes.push(m[2]);
}
return Array.from(new Set(codes));
}
function getHostnameFromUrl(url) {
try { return new URL(normalizeUrl(url)).hostname; } catch { return ''; }
}
function isNetdiskHost(host) {
if (!host) return false;
const h = host.toLowerCase();
const patterns = [
'pan.baidu.com', 'yun.baidu.com',
'cloud.189.cn',
'lanzou.com', 'lanzoui.com', 'lanzoux.com', 'lanzoup.com', 'lanzouy.com',
'123pan.com',
'weiyun.com', 'share.weiyun.com',
'pan.xunlei.com',
'pan.quark.cn', 'drive.quark.cn',
'alipan.com', 'aliyundrive.com', 'drive.aliyundrive.com',
'terabox.com',
'ctfile.com',
'cowtransfer.com',
'sendspace.com', 'dropbox.com', 'drive.google.com', 'onedrive.live.com', 'sharepoint.com',
'mega.nz'
];
return patterns.some(d => h === d || h.endsWith('.' + d)) || /(lanzou[a-z]|lanzn)\.com$/.test(h);
}
// 新增:常见网盘站点的输入与按钮选择器,以及辅助方法
const PAN_SITE_CONFIG = [
{ host: /(pan|e?yun)\.baidu\.com/, input: ['#accessCode', '.share-access-code', '#wpdoc-share-page .u-input__inner'], button: ['#submitBtn', '.share-access .g-button', '#wpdoc-share-page .u-btn--primary'] },
{ host: /www\.(aliyundrive|alipan)\.com|alywp\.net/, input: ['form .ant-input', 'form input[type="text"]', 'input[name="pwd"]'], button: ['form .button--fep7l', 'form button[type="submit"]'] },
{ host: /share\.weiyun\.com/, input: ['.mod-card-s input[type=password]', 'input.pw-input'], button: ['.mod-card-s .btn-main', '.pw-btn-wrap button.btn'] },
{ host: /(?:lanzou[a-z]|lanzn)\.com/, input: ['#pwd','input[name="pwd"]','.pwd','form input[type="text"]'], button: ['.passwddiv-btn', '#sub','form button','button[type="submit"]'] },
{ host: /cloud\.189\.cn/, input: ['.access-code-item #code_txt', 'input.access-code-input'], button: ['.access-code-item .visit', '.button'] },
{ host: /www\.123pan\.com/, input: ['.ca-fot input', '.appinput .appinput'], button: ['.ca-fot button', '.appinput button'] },
{ host: /pan\.xunlei\.com/, input: ['.pass-input-wrap .td-input__inner'], button: ['.pass-input-wrap .td-button'] },
{ host: /pan\.quark\.cn/, input: ['.ant-input'], button: ['.ant-btn-primary'] },
{ host: /(?:ctfile|545c|u062|ghpym)\.com/, input: ['#passcode'], button: ['.card-body button'] },
{ host: /(?:[a-zA-Z\d-.]+)?cowtransfer\.com/, input: ['.receive-code-input input'], button: ['.open-button'] }
];
function getPanConfigByHost(host) {
try {
for (let i = 0; i < PAN_SITE_CONFIG.length; i++) {
if (PAN_SITE_CONFIG[i].host.test(host)) return PAN_SITE_CONFIG[i];
}
} catch {}
return null;
}
function queryAny(selectors) {
if (!selectors || selectors.length === 0) return null;
for (let i = 0; i < selectors.length; i++) {
try { const el = document.querySelector(selectors[i]); if (el) return el; } catch {}
}
return null;
}
function isHiddenEl(el) {
try {
const cs = window.getComputedStyle(el);
return cs.display === 'none' || cs.visibility === 'hidden' || el.offsetParent === null;
} catch { return false; }
}
function augmentNetdiskUrlWithCode(url, code) {
try {
if (!code) return url;
const u = new URL(normalizeUrl(url));
if (!isNetdiskHost(u.hostname)) return url;
const params = u.searchParams;
if (!params.get('pwd')) params.set('pwd', code);
u.search = params.toString();
u.hash = '#' + code;
return u.toString();
} catch { return url; }
}
function buildLinkRecordsFromAllText(text) {
const lines = (text || '').split(/\r?\n/);
const records = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const urls = extractUrlsFromText(line);
if (urls.length === 0) continue;
const neighborhood = line + '\n' + (lines[i + 1] || '');
const codes = extractCodesFromText(neighborhood);
let code = codes[0] || '';
urls.forEach(u => {
const host = getHostnameFromUrl(u);
const effectiveCode = isNetdiskHost(host) ? code : '';
records.push({ url: u, host, code: effectiveCode, display: getAlias(host) || '' });
});
}
const seen = new Set();
const dedup = [];
for (const r of records) {
if (seen.has(r.url)) continue;
seen.add(r.url);
dedup.push(r);
}
return dedup;
}
function findCodeFromSelectionContext() {
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0) return '';
const range = sel.getRangeAt(0);
let node = range.commonAncestorContainer;
if (node.nodeType !== 1) node = node.parentElement;
if (!node) return '';
const container = node.closest ? node.closest('p,li,div,section,article') || node : node;
const txt = container ? container.innerText : '';
const codes = extractCodesFromText(txt);
return codes[0] || '';
}
function showConfirmModal(options) {
const { title = '确认跳转', message = '', confirmText = '打开', cancelText = '取消', onConfirm, onCancel } = options || {};
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed;inset:0;background:rgba(2,8,23,.25);z-index:99998;`;
const modal = document.createElement('div');
modal.style.cssText = `position:fixed;top:64px;left:50%;transform:translateX(-50%);width:280px;max-width:92vw;background:#ffffff;color:#0f172a;border-radius:12px;border:1px solid #e2e8f0;box-shadow:0 10px 24px rgba(2,8,23,.12);z-index:99999;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;`;
modal.innerHTML = `
<div style="padding:12px 14px;border-bottom:1px solid #f1f5f9;font-weight:600;font-size:14px;">${title}</div>
<div style="padding:12px 14px;font-size:13px;color:#334155;line-height:1.6;word-break:break-all;">${message}</div>
<div style="display:flex;justify-content:flex-end;gap:8px;padding:10px 14px;border-top:1px solid #f1f5f9;">
<button id="cm_cancel" style="padding:6px 10px;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:8px;cursor:pointer;font-size:12px;">${cancelText}</button>
<button id="cm_ok" style="padding:6px 12px;border:1px solid #2563eb;background:#2563eb;color:#fff;border-radius:8px;cursor:pointer;font-size:12px;">${confirmText}</button>
</div>
`;
function cleanup() {
if (modal.parentNode) modal.parentNode.removeChild(modal);
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
}
overlay.addEventListener('click', () => { cleanup(); onCancel && onCancel(); });
modal.querySelector('#cm_cancel').addEventListener('click', () => { cleanup(); onCancel && onCancel(); });
modal.querySelector('#cm_ok').addEventListener('click', () => { cleanup(); onConfirm && onConfirm(); });
document.body.appendChild(overlay);
document.body.appendChild(modal);
}
function showLinkJumpConfirm(url, code) {
const host = getHostnameFromUrl(url);
const alias = getAlias(host);
const title = '跳转确认';
const display = alias || host || url;
const isPan = isNetdiskHost(host);
let message = `是否跳转到:<br><strong>${display}</strong>`;
if (code && isPan) message += `<br><span style=\"color:#16a34a;\">已识别提取码:<b>${code}</b>(将自动填入)</span>`;
showConfirmModal({ title, message, onConfirm: () => {
if (code && isPan) {
const targetHost = host || getHostnameFromUrl(url);
setPendingCodeForHost(targetHost, code);
try { localStorage.setItem('nd_last_code', code); } catch {}
navigator.clipboard.writeText(code).catch(() => {});
}
const targetUrl = augmentNetdiskUrlWithCode(url, code);
window.open(targetUrl, '_blank');
} });
}
document.addEventListener('mousedown', function () {
isSelecting = true;
// 当用户重新在当前标签页进行新的选择时,解除上一次去重限制
if (document.hasFocus()) {
showedPopup = false;
}
});
document.addEventListener('mouseup', function () {
isSelecting = false;
if (window.getSelection().toString()) {
selectedText = window.getSelection().toString();
hasPattern = extractUrlsFromText(selectedText).length > 0;
} else {
selectedText = '';
hasPattern = false;
}
checkAndPrompt();
});
let checkAndPrompt = function () {
if (!isDetectionEnabled) return;
if (hasPattern && window.location.href === currentPageUrl) {
const urls = extractUrlsFromText(selectedText);
if (urls && urls.length > 0) {
const urlToCopy = urls[0];
if (urlToCopy !== lastSelectedUrl) {
const host = getHostnameFromUrl(urlToCopy);
const code = isNetdiskHost(host) ? (findCodeFromSelectionContext() || extractCodesFromText(selectedText)[0] || '') : '';
navigator.clipboard.writeText(urlToCopy).catch(() => {});
showLinkJumpConfirm(urlToCopy, code);
showedPopup = true;
lastSelectedUrl = urlToCopy;
setTimeout(() => { showedPopup = false; }, 1200);
}
}
} else {
showedPopup = false;
isProcessed = false;
lastSelectedUrl = '';
}
};
document.addEventListener('selectionchange', checkAndPrompt);
document.addEventListener('keydown', function (event) {
if (event.key === 'F2') {
const allTextContent = document.body.innerText;
linkRecords = buildLinkRecordsFromAllText(allTextContent);
const popupStyle = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 360px;
height: 420px;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(2, 8, 23, .18);
padding: 14px;
z-index: 9999;
overflow-y: auto;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
font-family: system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;
`;
const popupTitleStyle = `
text-align: left;
font-size: 16px;
font-weight: 700;
display: inline-block;
margin-right: 10px;
`;
const linkContainerStyle = `
margin-top: 10px;
`;
const toolbarStyle = `
background: #f8fafc;
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
justify-content: space-between;
padding-bottom: 6px;
margin-top: 6px;
`;
const btnStyle = `
padding: 4px 8px;
border: 1px solid #cbd5e1;
background: #fff;
border-radius: 8px;
cursor: pointer;
`;
const popupContent = `<div style="display:flex;flex-direction:column;justify-content:flex-start;height:100%;"><div style="position:sticky;top:0;background:#f8fafc;display:flex;justify-content:space-between;align-items:center;z-index:2;padding-bottom:6px;"><h3 id="popupTitle" style="width:100%;${popupTitleStyle}">链接列表(${linkRecords.length})</h3></div><div id="toolbar" style="${toolbarStyle}"><div style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;"><button id="selectAllBtn" style="${btnStyle}">全选</button><input id="searchInput" placeholder="搜索别名/域名/链接/提取码" style="padding:6px 8px;border:1px solid #cbd5e1;border-radius:8px;width:200px;background:#fff;color:#111;outline:none;" /></div><div style="display:flex;gap:6px;flex-wrap:wrap;"><button id="openSelBg" style="${btnStyle}">打开选中(后台)</button><button id="openSelWin" style="${btnStyle}">打开选中(新窗口)</button><button id="openAllBg" style="${btnStyle}">打开全部(后台)</button></div></div><div id="linkContainer" style="${linkContainerStyle}"></div></div>`;
const popup = document.createElement('div');
popup.style.cssText = popupStyle;
popup.innerHTML = popupContent;
document.body.appendChild(popup);
const outsideClose = document.createElement('button');
outsideClose.id = 'popupCloseOutside';
outsideClose.textContent = '✕';
outsideClose.style.cssText = `position:fixed;width:28px;height:28px;display:flex;align-items:center;justify-content:center;font-size:14px;line-height:1;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:999px;cursor:pointer;box-shadow:0 6px 16px rgba(2,8,23,.2);z-index:10000;`;
document.body.appendChild(outsideClose);
function positionOutsideClose() {
const rect = popup.getBoundingClientRect();
const left = Math.min(window.innerWidth - 36, Math.max(8, rect.right + 8));
const top = Math.min(window.innerHeight - 36, Math.max(8, rect.top - 8));
outsideClose.style.left = left + 'px';
outsideClose.style.top = top + 'px';
}
positionOutsideClose();
window.addEventListener('resize', positionOutsideClose);
outsideClose.addEventListener('click', function () {
try { document.body.removeChild(popup); } catch {}
try { document.body.removeChild(outsideClose); } catch {}
});
let isDragging = false;
let offsetX, offsetY;
let originalBorderColor = '#e2e8f0';
let draggingBorderColor = '#60a5fa';
popup.addEventListener('mousedown', function (event) {
isDragging = true;
offsetX = event.clientX - popup.offsetLeft;
offsetY = event.clientY - popup.offsetTop;
popup.style.borderColor = draggingBorderColor;
});
document.addEventListener('mousemove', function (event) {
if (isDragging) {
popup.style.left = event.clientX - offsetX + 'px';
popup.style.top = event.clientY - offsetY + 'px';
try { positionOutsideClose(); } catch {}
}
});
document.addEventListener('mouseup', function () {
isDragging = false;
popup.style.borderColor = originalBorderColor;
try { positionOutsideClose(); } catch {}
});
popup.addEventListener('wheel', function (event) {
event.preventDefault();
popup.scrollTop += event.deltaY;
try { positionOutsideClose(); } catch {}
});
const linkContainer = popup.querySelector('#linkContainer');
linkRecords.forEach(() => {});
const selectAllBtn = document.getElementById('selectAllBtn');
const searchInput = document.getElementById('searchInput');
const openSelBg = document.getElementById('openSelBg');
const openSelWin = document.getElementById('openSelWin');
const openAllBg = document.getElementById('openAllBg');
let allSelected = false;
selectAllBtn.addEventListener('click', function () {
const checks = popup.querySelectorAll('.link-check');
allSelected = !allSelected;
checks.forEach(c => c.checked = allSelected);
selectAllBtn.textContent = allSelected ? '取消全选' : '全选';
});
function normalize(str) {
return (str || '').toLowerCase();
}
function itemMatches(rec, q) {
const s = normalize(q);
if (!s) return true;
const fields = [rec.display, rec.host, rec.url, rec.code];
return fields.some(f => normalize(f).includes(s));
}
function renderList(filterText) {
linkContainer.innerHTML = '';
let count = 0;
linkRecords.forEach((rec, index) => {
if (!itemMatches(rec, filterText)) return;
count++;
const row = document.createElement('div');
row.style.margin = '8px 0';
const safe = rec.url.replace(/"/g, '"');
const label = rec.display || rec.host || rec.url;
const codeBadge = rec.code ? `<span style="margin-left:6px;color:#16a34a;background:#dcfce7;border:1px solid #86efac;border-radius:4px;padding:0 4px;">码:${rec.code}</span>` : '';
row.innerHTML = `<label style="display:flex;align-items:flex-start;gap:8px;line-height:1.4;"><input type="checkbox" class="link-check" data-url="${safe}" data-code="${rec.code || ''}" data-host="${rec.host || ''}"/><span style="opacity:.7;min-width:2.2em;text-align:right;">${index + 1}.</span><div style="display:flex;flex-direction:column;gap:2px;min-width:0;"><div style="font-size:12px;color:#334155;">${label}${codeBadge}</div><a href="${safe}" target="_blank" rel="noopener noreferrer" style="word-break:break-all;color:#0ea5e9;text-decoration:none;">${safe}</a></div></label>`;
linkContainer.appendChild(row);
});
const titleEl = popup.querySelector('#popupTitle');
if (titleEl) titleEl.textContent = `链接列表(${count})`;
}
if (searchInput) {
searchInput.addEventListener('input', function(){
renderList(searchInput.value);
allSelected = false;
selectAllBtn.textContent = '全选';
});
}
renderList('');
function openOne(recUrl, mode, code, host) {
if (code && isNetdiskHost(host || getHostnameFromUrl(recUrl))) {
setPendingCodeForHost(host || getHostnameFromUrl(recUrl), code);
navigator.clipboard.writeText(code).catch(() => {});
}
const finalUrl = augmentNetdiskUrlWithCode(recUrl, code);
openUrlWithMode(finalUrl, mode);
}
function getSelectedRecs() {
const checks = popup.querySelectorAll('.link-check');
const recs = [];
checks.forEach(c => {
if (c.checked) recs.push({ url: c.getAttribute('data-url'), code: c.getAttribute('data-code') || '', host: c.getAttribute('data-host') || '' });
});
return recs;
}
openSelBg.addEventListener('click', function () {
const recs = getSelectedRecs();
recs.forEach((r, idx) => setTimeout(() => openOne(r.url, 'backgroundTab', r.code, r.host), idx * 200));
});
openSelWin.addEventListener('click', function () {
const recs = getSelectedRecs();
recs.forEach((r, idx) => setTimeout(() => openOne(r.url, 'newWindow', r.code, r.host), idx * 200));
});
openAllBg.addEventListener('click', function () {
linkRecords.forEach((r, idx) => setTimeout(() => openOne(r.url, 'backgroundTab', r.code, r.host), idx * 200));
});
}
if (event.key === 'F3') {
let prefillDomain = '';
const sel = window.getSelection().toString().trim();
const urls = extractUrlsFromText(sel);
if (urls.length > 0) prefillDomain = getHostnameFromUrl(urls[0]);
if (!prefillDomain && document.activeElement && document.activeElement.href) {
try { prefillDomain = new URL(document.activeElement.href).hostname; } catch {}
}
const body = `
<div style="display:flex;flex-direction:column;gap:10px;padding:12px 0;">
<label style="display:flex;flex-direction:column;gap:6px;">
<span style="font-size:12px;color:#475569;">域名(例如:example.com)</span>
<input id="alias_domain" placeholder="example.com" style="padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px;outline:none;background:#fff;color:#111;" value="${prefillDomain || ''}" />
</label>
<label style="display:flex;flex-direction:column;gap:6px;">
<span style="font-size:12px;color:#475569;">显示名称(别名)</span>
<input id="alias_name" placeholder="我的网盘" style="padding:8px 10px;border:1px solid #cbd5e1;border-radius:8px;outline:none;background:#fff;color:#111;" value="${getAlias(prefillDomain) || ''}" />
</label>
<div style="display:flex;gap:8px;flex-wrap:wrap;padding-top:4px;">
<button id="alias_export" style="padding:6px 10px;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:8px;cursor:pointer;font-size:12px;">导出</button>
<button id="alias_import" style="padding:6px 10px;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:8px;cursor:pointer;font-size:12px;">导入</button>
<button id="alias_copy_embed" style="padding:6px 10px;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:8px;cursor:pointer;font-size:12px;">设为内置</button>
</div>
</div>
`;
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed;inset:0;background:rgba(2,8,23,.25);z-index:99998;`;
const modal = document.createElement('div');
modal.style.cssText = `position:fixed;top:64px;left:50%;transform:translateX(-50%);width:360px;max-width:92vw;background:#ffffff;color:#0f172a;border-radius:12px;border:1px solid #e2e8f0;box-shadow:0 10px 24px rgba(2,8,23,.12);z-index:99999;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;`;
modal.innerHTML = `
<div style="padding:12px 14px;border-bottom:1px solid #f1f5f9;font-weight:600;font-size:14px;">设置域名别名 (F3)</div>
<div style="padding:10px 14px;">${body}</div>
<div style="display:flex;justify-content:flex-end;gap:8px;padding:10px 14px;border-top:1px solid #f1f5f9;">
<button id="alias_cancel" style="padding:6px 10px;border:1px solid #cbd5e1;background:#fff;color:#334155;border-radius:8px;cursor:pointer;font-size:12px;">取消</button>
<button id="alias_ok" style="padding:6px 12px;border:1px solid #2563eb;background:#2563eb;color:#fff;border-radius:8px;cursor:pointer;font-size:12px;">保存</button>
</div>`;
function cleanup() {
if (modal.parentNode) modal.parentNode.removeChild(modal);
if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
}
function downloadText(filename, text) {
try {
const blob = new Blob([text], { type: 'application/json;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch {}
}
function copyToClipboard(text) { try { navigator.clipboard.writeText(text); } catch {} }
function pretty(obj) { try { return JSON.stringify(obj, null, 2); } catch { return '{}'; } }
const exportBtn = modal.querySelector('#alias_export');
const importBtn = modal.querySelector('#alias_import');
const embedBtn = modal.querySelector('#alias_copy_embed');
if (exportBtn) exportBtn.addEventListener('click', () => {
const data = getUserAliases();
downloadText('aliases.json', pretty(data));
});
if (importBtn) importBtn.addEventListener('click', async () => {
try {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json,.json';
input.onchange = () => {
const file = input.files && input.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
try {
const obj = JSON.parse(reader.result);
if (obj && typeof obj === 'object') setAliases(obj);
alert('导入成功');
} catch { alert('导入失败:JSON 格式不正确'); }
};
reader.readAsText(file, 'utf-8');
};
input.click();
} catch { alert('导入失败:浏览器不支持文件选择'); }
});
if (embedBtn) embedBtn.addEventListener('click', () => {
try {
const data = getUserAliases();
GM_setValue(KEY_ALIASES_BUILTIN_OVERRIDE, data);
alert('已将当前别名保存为内置(本地),将作为默认值使用。');
} catch {
alert('保存失败:环境不支持写入本地存储');
}
});
overlay.addEventListener('click', cleanup);
modal.querySelector('#alias_cancel').addEventListener('click', cleanup);
modal.querySelector('#alias_ok').addEventListener('click', () => {
const d = modal.querySelector('#alias_domain').value.trim().replace(/^https?:\/\//,'');
const n = modal.querySelector('#alias_name').value.trim();
if (d && n) upsertAlias(d, n);
try {
if (Array.isArray(linkRecords) && linkRecords.length > 0) {
linkRecords = linkRecords.map(r => {
if (!r) return r;
const alias = getAlias(r.host);
return Object.assign({}, r, { display: alias || r.display || '' });
});
}
} catch {}
cleanup();
});
document.body.appendChild(overlay);
document.body.appendChild(modal);
}
});
(function tryAutoFillExtractionCode() {
const host = location.hostname;
// 从 URL 参数、hash 或待写入的本地值获取提取码
let code = '';
try {
const sp = new URLSearchParams(location.search);
code = sp.get('pwd') || sp.get('p') || '';
} catch {}
if (!code) {
try { code = (location.hash || '').replace(/^#/, '').trim(); } catch {}
}
if (!code) code = takePendingCodeForHost(host);
if (!code) { try { code = localStorage.getItem('nd_last_code') || ''; } catch {}
}
if (!code) return;
let attempts = 0;
const conf = getPanConfigByHost(host);
const timer = setInterval(() => {
attempts++;
let input = null;
let button = null;
if (conf) {
input = queryAny(conf.input);
button = queryAny(conf.button);
}
if (!input) {
// 退化为通用探测
let inputs = Array.from(document.querySelectorAll('input'))
.filter(el => {
const p = (el.getAttribute('placeholder') || '').trim();
const a = (el.getAttribute('aria-label') || '').trim();
const name = (el.getAttribute('name') || '').toLowerCase();
const id = (el.getAttribute('id') || '').toLowerCase();
const nearby = el.parentElement ? el.parentElement.innerText : '';
const kw = /(提取码|密码|访问码|Access\s*Code|提取)/;
return kw.test(p) || kw.test(a) || kw.test(nearby) || /code|access|pass/.test(name) || /code|access|pass/.test(id);
});
if (inputs.length > 0) input = inputs[0];
}
if (input && !isHiddenEl(input)) {
input.focus();
const last = input.value;
input.value = code;
try { const tracker = input._valueTracker; if (tracker) tracker.setValue(last); } catch {}
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
// 若有按钮,优先点击;否则尝试智能按钮匹配
const clickSubmit = () => {
if (button) { try { button.click(); } catch {} return; }
const candidateSelectors = ['button','input[type="submit"]','input[type="button"]','a','[role="button"]','[class*="btn"]','[class*="button"]','[class*="submit"]','[class*="confirm"]'];
const candidates = Array.from(document.querySelectorAll(candidateSelectors.join(',')));
const matchRe = /^(确\s*定|确定|提交|访问|打开|提取|确认|继续|下一步|GO|Enter|OK|Submit|Continue)$/i;
const btn = candidates.find(el => {
if (el.disabled) return false;
const style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden' || style.pointerEvents === 'none') return false;
const text = (el.innerText || el.value || el.textContent || '').trim();
const title = (el.getAttribute('title') || el.getAttribute('aria-label') || '').trim();
const className = (el.className || '').toString();
return matchRe.test(text) || matchRe.test(title) || /(submit|confirm|ok|go|enter)/i.test(className);
});
if (btn) {
try { btn.click(); } catch {}
} else {
const form = input.form || (input.closest && input.closest('form'));
if (form) {
if (typeof form.requestSubmit === 'function') {
form.requestSubmit();
} else {
form.submit();
}
} else {
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
input.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true }));
}
}
};
setTimeout(clickSubmit, 500);
clearInterval(timer);
} else if (attempts > 100) {
clearInterval(timer);
}
}, 200);
})();
const KEY_AUTO_EXPAND = 'autoExpandHidden.v1';
function isAutoExpandEnabled() { try { return GM_getValue(KEY_AUTO_EXPAND, false); } catch { return false; } }
function setAutoExpandEnabled(v) { try { GM_setValue(KEY_AUTO_EXPAND, !!v); } catch {} }
function isAutoExpandSupportedSite() {
const h = location.hostname;
const p = location.pathname;
const hp = (host, pathPrefix) => h === host && (pathPrefix ? p.startsWith(pathPrefix) : true);
return (
hp('blog.csdn.net') ||
hp('bbs.csdn.net') ||
hp('download.csdn.net', '/download/') ||
hp('www.doc88.com') ||
hp('wenku.baidu.com', '/view/') ||
hp('zhidao.baidu.com', '/question') ||
hp('www.ipaperclip.net', '/doku.php') ||
hp('wap.peopleapp.com', '/article/') ||
hp('ishare.ifeng.com', '/c/s/') ||
hp('www.ximalaya.com') ||
hp('www.awesomes.cn') ||
hp('www.imooc.com', '/article/') ||
hp('www.zhihu.com', '/question/') ||
hp('www.bandbbs.cn') ||
hp('www.cnbeta.com') ||
hp('www.chinaz.com') ||
hp('www.douban.com', '/note/')
);
}
const AutoExpand = (function(){
let loopTimer = null;
let loadHandler = null;
const btns = Array(
'.btn-readmore',
'.show-hide-btn',
'.down-arrow',
'.paperclip__showbtn',
'.expend',
'.shadow-2n5oidXt',
'.read_more_btn',
'.QuestionRichText-more',
'.QuestionMainAction',
'.ContentItem-expandButton',
'.js_show_topic',
'.tbl-read-more-btn',
'.more-intro-wrapper',
'.showMore',
'.unfoldFullText',
'.taboola-open',
);
const asyncBtns = Array(
'#continueButton',
'.read-more-zhankai',
'.wgt-answers-showbtn',
'.wgt-best-showbtn',
'.bbCodeBlock-expandLink'
);
const delay = 500;
function showFull(selectors, handler, once) {
for (let i = 0; i < selectors.length; i++) {
try { continue }
finally {
const sel = selectors[i], nodes = document.querySelectorAll(sel);
if (!!nodes[0]) {
handler(nodes, sel);
}
}
}
clearTimeout(loopTimer);
if (!once) loopTimer = setTimeout(() => showFull(selectors, handler, false), delay);
}
function doShow(nodes, sel) {
if (sel === '.paperclip__showbtn') {
nodes.forEach(item => item.click());
} else if (sel === '.showMore') {
try { nodes[0].querySelector('span').click(); } catch { try { nodes[0].click(); } catch {} }
} else {
try { nodes[0].click(); } catch {}
}
}
function doAsyncShow(nodes, sel) {
try { nodes[0].click(); } catch {}
}
function start() {
if (loopTimer || loadHandler) return;
showFull(btns, doShow, false);
loadHandler = () => {
clearTimeout(loopTimer);
setTimeout(() => showFull(asyncBtns, doAsyncShow, true), delay);
};
window.addEventListener('load', loadHandler);
}
function stop() {
if (loopTimer) { clearTimeout(loopTimer); loopTimer = null; }
if (loadHandler) { window.removeEventListener('load', loadHandler); loadHandler = null; }
}
return { start, stop };
})();
(function initAutoExpand(){
const enabled = isAutoExpandEnabled();
if (enabled && isAutoExpandSupportedSite()) {
AutoExpand.start();
}
try {
GM_registerMenuCommand('自动展开隐藏内容:' + (enabled ? '已开启' : '已关闭'), () => {
const now = isAutoExpandEnabled();
const next = !now;
setAutoExpandEnabled(next);
if (next && isAutoExpandSupportedSite()) {
AutoExpand.start();
} else {
AutoExpand.stop();
}
alert('自动展开隐藏内容已' + (next ? '开启' : '关闭') + (isAutoExpandSupportedSite() ? '' : '(当前站点不在支持列表)'));
});
} catch {}
})();
// 新增:全站链接打开方式(当前页 / 新标签页)
const KEY_OPEN_LINKS_NEW_TAB = 'openLinksNewTab.v1';
function isOpenLinksInNewTab() { try { return GM_getValue(KEY_OPEN_LINKS_NEW_TAB, true); } catch { return true; } }
function setOpenLinksInNewTab(v) { try { GM_setValue(KEY_OPEN_LINKS_NEW_TAB, !!v); } catch {} }
function isEligibleAnchorForOpenMode(a) {
try {
if (!a || a.nodeType !== 1) return false;
if (a.closest && a.closest('button, [role="button"], .no-link-open-mode')) return false;
const hrefAttr = a.getAttribute('href') || '';
if (!hrefAttr) return false;
if (hrefAttr.startsWith('#')) return false;
if (/^javascript:/i.test(hrefAttr)) return false;
if (/^(mailto:|tel:|sms:|intent:)/i.test(hrefAttr)) return false;
return true;
} catch { return false; }
}
function applyLinkOpenModeToPage() {
const newTab = isOpenLinksInNewTab();
const anchors = document.querySelectorAll('a[href]');
anchors.forEach(a => {
if (!isEligibleAnchorForOpenMode(a)) return;
if (newTab) {
a.setAttribute('target', '_blank');
const rel = (a.getAttribute('rel') || '').toLowerCase();
let nextRel = rel;
if (!/\bnoopener\b/.test(rel)) nextRel = (nextRel ? nextRel + ' ' : '') + 'noopener';
if (!/\bnoreferrer\b/.test(rel)) nextRel = (nextRel ? nextRel + ' ' : '') + 'noreferrer';
a.setAttribute('rel', nextRel.trim());
} else {
a.removeAttribute('target');
}
});
}
(function initGlobalLinkOpenMode(){
try { applyLinkOpenModeToPage(); } catch {}
try {
GM_registerMenuCommand('链接打开方式:' + (isOpenLinksInNewTab() ? '新标签页' : '当前页'), () => {
const next = !isOpenLinksInNewTab();
setOpenLinksInNewTab(next);
applyLinkOpenModeToPage();
alert('已切换为:' + (next ? '新标签页打开' : '当前页打开'));
});
} catch {}
try {
const mo = new MutationObserver((muts) => {
for (let i = 0; i < muts.length; i++) {
const m = muts[i];
if (m.addedNodes && m.addedNodes.length) { applyLinkOpenModeToPage(); break; }
}
});
mo.observe(document.documentElement || document.body, { childList: true, subtree: true });
} catch {}
})();