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