// ==UserScript== // @name OpenNana 免费提示词一键复制 + 无限浏览(解锁登录限制) // @namespace doubao-opennana-helper // @version 2.0.0 // @description 解锁 opennana.com 免费栏目两个限制:①点击“复制”直接复制中/英文提示词,不再弹登录;②自动解除“登录后继续浏览”拦截,无限滚动加载全部免费提示词。带浮动面板,两个独立开关 + 状态提示。 // @author 小雅吧zml // @match https://opennana.com/* // @run-at document-idle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @noframes // @license GPL-3.0 // ==/UserScript== (function () { 'use strict'; var KEY_COPY = 'opennana_copy_unlock'; var KEY_BROWSE = 'opennana_browse_unlock'; // 两个功能默认都开启;状态各自保存,刷新后记住 var copyEnabled = GM_getValue(KEY_COPY, true); var browseEnabled = GM_getValue(KEY_BROWSE, true); // 用于“无限浏览”的占位 token:门控只判断这个键是否存在,有值即视为已登录 var DUMMY_TOKEN = '__opennana_unlock_dummy__'; var panelId = 'on-unlock-panel'; var toastId = 'on-unlock-toast'; // ---------- 无限浏览:尽早写入占位 token(在页面逻辑跑起来之前就生效) ---------- function applyBrowseToken() { if (!browseEnabled) return; // 只在“完全没登录”时才写入占位 token,绝不覆盖真实登录态 if (!localStorage.getItem('user_token')) { localStorage.setItem('user_token', DUMMY_TOKEN); } } function revertBrowseToken() { // 只在当前值是我们写入的占位 token 时才删除,避免误删真实登录 if (localStorage.getItem('user_token') === DUMMY_TOKEN) { localStorage.removeItem('user_token'); } } applyBrowseToken(); // 立即执行,无需等 DOM // ---------- 工具 ---------- function copyText(text) { if (navigator.clipboard && window.isSecureContext) { return navigator.clipboard.writeText(text); } var ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.top = '-9999px'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.focus(); ta.select(); try { document.execCommand('copy'); } catch (e) {} document.body.removeChild(ta); return Promise.resolve(); } // 从“复制”按钮向上找最近的
提示词文本
function promptFromBtn(btn) {
var el = btn;
for (var i = 0; i < 6 && el; i++) {
if (el.querySelector) {
var pre = el.querySelector('pre');
if (pre && pre.textContent && pre.textContent.trim()) {
return pre.textContent;
}
}
el = el.parentElement;
}
return null;
}
function isCopyBtn(node) {
return node && node.tagName === 'BUTTON' && (node.textContent || '').trim() === '复制';
}
// ---------- 提示 toast ----------
function showToast(msg, ok) {
var t = document.getElementById(toastId);
if (!t) {
t = document.createElement('div');
t.id = toastId;
t.style.cssText = 'position:fixed;top:24px;left:50%;transform:translateX(-50%);z-index:999999;padding:10px 18px;border-radius:10px;font-size:14px;font-family:-apple-system,"Microsoft YaHei",sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.18);transition:opacity .3s;pointer-events:none;';
document.body.appendChild(t);
}
t.textContent = msg;
t.style.background = ok ? '#16a34a' : '#dc2626';
t.style.color = '#fff';
t.style.opacity = '1';
clearTimeout(t._timer);
t._timer = setTimeout(function () { t.style.opacity = '0'; }, 2000);
}
// ---------- 核心①:拦截复制按钮(捕获阶段,先于站点逻辑) ----------
function onDocClick(e) {
if (!copyEnabled) return;
var node = e.target;
var btn = node && node.closest ? node.closest('button') : null;
if (!btn || !isCopyBtn(btn)) return;
var text = promptFromBtn(btn);
if (!text) return;
e.preventDefault();
e.stopImmediatePropagation(); // 阻止站点打开登录弹窗
copyText(text).then(function () {
showToast('已复制提示词(' + text.length + ' 字)', true);
}).catch(function () {
showToast('复制失败,请手动选中文字复制', false);
});
}
document.addEventListener('click', onDocClick, true);
// ---------- 核心②:兜底移除登录条(占位 token 之外的第二道保险) ----------
function removeLoginBanners() {
if (!browseEnabled) return;
// 移除“请登录后继续浏览”/“发现更多精彩提示词”所在的 fixed 底部条
var fixed = document.querySelectorAll('div.fixed');
for (var i = 0; i < fixed.length; i++) {
var el = fixed[i];
var txt = el.textContent || '';
if (/请登录后继续浏览|发现更多精彩提示词|立即登录/.test(txt) && /bottom-6|inset-x-0/.test(el.className)) {
el.remove();
}
}
}
var mo2 = new MutationObserver(function () {
if (browseEnabled) { removeLoginBanners(); forceSelectable(); }
});
function startObservers() {
mo2.observe(document.body, { childList: true, subtree: true });
}
// ---------- 让提示词始终可选中 ----------
function forceSelectable() {
var pres = document.querySelectorAll('pre');
for (var i = 0; i < pres.length; i++) {
pres[i].style.userSelect = 'text';
pres[i].style.webkitUserSelect = 'text';
}
}
// ---------- 浮动面板(两个开关 + 状态) ----------
function buildPanel() {
var old = document.getElementById(panelId);
if (old) old.remove();
var p = document.createElement('div');
p.id = panelId;
p.style.cssText = 'position:fixed;right:16px;bottom:16px;z-index:999998;width:236px;background:#fff;border:1px solid #e5e7eb;border-radius:14px;box-shadow:0 10px 30px rgba(0,0,0,.16);font-family:-apple-system,"Microsoft YaHei",sans-serif;font-size:13px;color:#111827;overflow:hidden;';
var head = document.createElement('div');
head.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:#0f172a;color:#fff;';
head.innerHTML = 'OpenNana 解锁助手';
var closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = 'border:none;background:transparent;color:#cbd5e1;font-size:18px;line-height:1;cursor:pointer;padding:0 2px;';
closeBtn.addEventListener('click', function () { p.style.display = 'none'; });
head.appendChild(closeBtn);
p.appendChild(head);
var body = document.createElement('div');
body.style.cssText = 'padding:12px;';
// ---- 开关控件工厂 ----
function makeSwitch(label, initial) {
var row = document.createElement('div');
row.style.cssText = 'display:flex;align-items:center;justify-content:space-between;margin-top:8px;';
var lab = document.createElement('span');
lab.textContent = label;
lab.style.cssText = 'font-weight:500;';
var sw = document.createElement('label');
sw.style.cssText = 'position:relative;display:inline-block;width:44px;height:24px;cursor:pointer;flex-shrink:0;';
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.checked = initial;
cb.style.cssText = 'opacity:0;width:0;height:0;position:absolute;';
var slider = document.createElement('span');
slider.style.cssText = 'position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background:#cbd5e1;border-radius:24px;transition:.25s;';
var dot = document.createElement('span');
dot.style.cssText = 'position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.25s;box-shadow:0 1px 3px rgba(0,0,0,.3);';
slider.appendChild(dot);
sw.appendChild(cb);
sw.appendChild(slider);
function paint() {
if (cb.checked) { slider.style.background = '#16a34a'; dot.style.transform = 'translateX(20px)'; }
else { slider.style.background = '#cbd5e1'; dot.style.transform = 'translateX(0)'; }
}
paint();
row.appendChild(lab);
row.appendChild(sw);
return { row: row, cb: cb, paint: paint };
}
// ---- 开关 1:一键复制 ----
var s1 = makeSwitch('一键复制提示词', copyEnabled);
s1.cb.addEventListener('change', function () {
copyEnabled = s1.cb.checked;
GM_setValue(KEY_COPY, copyEnabled);
s1.paint();
paintStatuses();
showToast(copyEnabled ? '复制解锁已开启' : '复制解锁已关闭', true);
});
body.appendChild(s1.row);
// ---- 开关 2:无限浏览 ----
var s2 = makeSwitch('无限浏览(解除登录条)', browseEnabled);
s2.cb.addEventListener('change', function () {
browseEnabled = s2.cb.checked;
GM_setValue(KEY_BROWSE, browseEnabled);
s2.paint();
paintStatuses();
if (browseEnabled) { applyBrowseToken(); removeLoginBanners(); }
else { revertBrowseToken(); }
showToast(browseEnabled ? '无限浏览已开启:可继续往下加载更多' : '无限浏览已关闭:恢复网站原始限制', true);
});
body.appendChild(s2.row);
// ---- 状态汇总行 ----
var status = document.createElement('div');
status.id = panelId + '-status';
status.style.cssText = 'margin-top:12px;display:flex;flex-direction:column;gap:5px;font-size:12px;color:#374151;';
body.appendChild(status);
function paintStatuses() {
status.innerHTML = '';
function line(on, text) {
var d = document.createElement('div');
d.style.cssText = 'display:flex;align-items:center;gap:6px;';
var c = document.createElement('span');
c.style.cssText = 'width:8px;height:8px;border-radius:50%;background:' + (on ? '#16a34a' : '#9ca3af') + ';flex-shrink:0;';
var s = document.createElement('span');
s.textContent = text;
d.appendChild(c); d.appendChild(s);
return d;
}
status.appendChild(line(copyEnabled, copyEnabled ? '复制:已解锁' : '复制:未解锁'));
status.appendChild(line(browseEnabled, browseEnabled ? '浏览:已解锁,可加载全部' : '浏览:未解锁'));
}
paintStatuses();
// ---- 提示 ----
var hint = document.createElement('div');
hint.style.cssText = 'margin-top:8px;font-size:11px;color:#9ca3af;line-height:1.5;';
hint.innerHTML = '「复制」:直接复制中英文提示词,不弹登录。
「浏览」:自动跳过登录条,往下滚可一直加载免费提示词。';
body.appendChild(hint);
p.appendChild(body);
document.body.appendChild(p);
// 面板可拖动
var dragging = false, sx, sy;
head.addEventListener('mousedown', function (ev) {
dragging = true; sx = ev.clientX; sy = ev.clientY;
});
document.addEventListener('mousemove', function (ev) {
if (!dragging) return;
p.style.right = 'auto';
p.style.bottom = 'auto';
p.style.left = (p.offsetLeft + ev.clientX - sx) + 'px';
p.style.top = (p.offsetTop + ev.clientY - sy) + 'px';
sx = ev.clientX; sy = ev.clientY;
});
document.addEventListener('mouseup', function () { dragging = false; });
}
// ---------- 初始化 ----------
function init() {
if (!document.body) { setTimeout(init, 300); return; }
buildPanel();
startObservers();
forceSelectable();
removeLoginBanners();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
// 附加:油猴菜单命令,快速开关
GM_registerMenuCommand('切换 一键复制 开/关', function () {
copyEnabled = !copyEnabled;
GM_setValue(KEY_COPY, copyEnabled);
showToast(copyEnabled ? '复制解锁已开启' : '复制解锁已关闭', true);
});
GM_registerMenuCommand('切换 无限浏览 开/关', function () {
browseEnabled = !browseEnabled;
GM_setValue(KEY_BROWSE, browseEnabled);
if (browseEnabled) { applyBrowseToken(); removeLoginBanners(); }
else { revertBrowseToken(); }
showToast(browseEnabled ? '无限浏览已开启' : '无限浏览已关闭', true);
});
})();