Scribd Enhancer All-in-One (v3.0.1)

Scribd Enhancer with OCR, TXT/HTML export, Snapshot PDF (pixel-perfect), Rich HTML (images inlined), page-range + quality controls. Draggable/collapsible panel + floating gear with position memory. Rich HTML now de-duplicates layered text/image to avoid doubled content. By Eliminater74.

// ==UserScript==
// @name         Scribd Enhancer All-in-One (v3.0.1)
// @namespace    https://gf.qytechs.cn/users/Eliminater74
// @version      3.0.1
// @description  Scribd Enhancer with OCR, TXT/HTML export, Snapshot PDF (pixel-perfect), Rich HTML (images inlined), page-range + quality controls. Draggable/collapsible panel + floating gear with position memory. Rich HTML now de-duplicates layered text/image to avoid doubled content. By Eliminater74.
// @author       Eliminater74
// @license      MIT
// @match        *://*.scribd.com/*
// @grant        none
// @icon         https://s-f.scribdassets.com/favicon.ico
// ==/UserScript==

(function () {
  'use strict';

  // ---------- KEYS ----------
  const SETTINGS_KEY   = 'scribdEnhancerSettings';
  const UI_MENU_KEY    = 'scribdEnhancer_ui_menu';
  const UI_GEAR_KEY    = 'scribdEnhancer_ui_gear';

  // ---------- SETTINGS ----------
  const defaultSettings = {
    unblur: true,
    autoScrape: false,
    darkMode: false,
    showPreview: true,
    enableOCR: true,
    ocrLang: 'auto',
    splitEvery: 0,

    // Snapshot controls
    pageRange: 'all',     // 'all' | '1-25' | '5,7,10-12'
    snapshotScale: 2,     // 1..4
    snapshotQuality: 0.92, // 0.8 | 0.92 | 1.0

    // NEW: Rich HTML layer preference: 'auto' | 'preferText' | 'preferImage'
    richPref: 'auto'
  };
  const settings = { ...defaultSettings, ...JSON.parse(localStorage.getItem(SETTINGS_KEY) || '{}') };
  const saveSettings = () => localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));

  // ---------- LIBS ----------
  const loadScript = (src) => { const s = document.createElement('script'); s.src = src; document.head.appendChild(s); return s; };
  loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/tesseract.min.js');
  loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/html2canvas.min.js');
  loadScript('https://cdn.jsdelivr.net/npm/[email protected]/dist/jspdf.umd.min.js');

  // ---------- STYLES ----------
  const style = document.createElement('style');
  style.textContent = `
    #se-gear {
      position: fixed; width: 40px; height: 40px; line-height: 40px; text-align: center;
      background:#2b2b2b; color:#fff; border-radius: 50%; cursor: pointer;
      box-shadow: 0 2px 10px rgba(0,0,0,.45); z-index: 2147483647; user-select:none;
      font-size: 20px;
    }
    #se-panel {
      position: fixed; background:#1e1f22; color:#f1f1f1; width: 320px; border-radius: 12px;
      box-shadow: 0 10px 30px rgba(0,0,0,.6); z-index: 2147483646; font-family: system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
      display: none;
    }
    #se-header {
      display:flex; align-items:center; justify-content:space-between; padding:8px 10px; cursor:move;
      background:#2a2b2f; border-top-left-radius:12px; border-top-right-radius:12px;
      font-weight:600;
    }
    #se-header .controls { display:flex; gap:6px; }
    #se-header .btn {
      width:24px; height:24px; line-height:24px; text-align:center; border-radius:6px; background:#3a3b41; cursor:pointer;
      user-select:none;
    }
    #se-body { padding:8px 10px 10px; max-height: 70vh; overflow:auto; }
    #se-body label { display:flex; align-items:center; gap:6px; font-size:13px; margin:4px 0; }
    #se-body .row { display:flex; gap:8px; }
    #se-body .row > * { flex:1; }
    #se-body input[type="text"], #se-body select {
      width:100%; padding:6px; border-radius:6px; border:1px solid #444; background:#121316; color:#eee; font-size:13px;
    }
    #se-body button {
      width:100%; padding:8px; margin-top:6px; border:none; border-radius:8px; background:#3b3d45; color:#fff;
      cursor:pointer; font-size:13px;
    }
    #se-body button:hover { filter:brightness(1.08); }
    #se-preview {
      position: fixed; right: 20px; bottom: 80px; width: 380px; top: 12px;
      background:#111; color:#eee; border:1px solid #444; border-radius:10px;
      padding:10px; font-family: ui-monospace,Menlo,Consolas,monospace; font-size:12px; white-space:pre-wrap;
      overflow:auto; z-index: 2147483645;
    }
    .se-dark #se-preview { background:#222; color:#eee; border-color:#555; }
  `;
  document.head.appendChild(style);

  // ---------- HELPERS ----------
  const sleep = (ms) => new Promise(r => setTimeout(r, ms));
  const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
  const safe  = (s) => (s || '').toString();

  function applyDarkMode() {
    document.documentElement.classList.toggle('se-dark', settings.darkMode);
    document.body.classList.toggle('se-dark', settings.darkMode);
  }

  function unblurContent() {
    if (!settings.unblur) return;
    const cleanup = () => {
      document.querySelectorAll('.blurred_page, .promo_div, [unselectable="on"]').forEach(el => el.remove());
      document.querySelectorAll('*').forEach(el => {
        const cs = getComputedStyle(el);
        if (cs.color === 'transparent') el.style.color = '#111';
        if (cs.textShadow && cs.textShadow.includes('white')) el.style.textShadow = 'none';
      });
    };
    cleanup();
    new MutationObserver(cleanup).observe(document.body, { childList: true, subtree: true });
  }

  function cleanOCRText(text) {
    return text.split('\n').map(t => t.trim())
      .filter(line => line.length >= 3 && /[a-zA-Z]/.test(line) && !/^[^a-zA-Z0-9]{3,}$/.test(line))
      .join('\n');
  }

  function detectLanguage(text) {
    const map = { spa:/[ñáéíóúü]/i, fra:/[éèêëàâôûùç]/i, deu:/[äöüß]/i, ron:/[șțăîâ]/i };
    for (const [k,re] of Object.entries(map)) if (re.test(text)) return k;
    return 'eng';
  }

  async function preprocessImage(src) {
    return new Promise(resolve => {
      const img = new Image(); img.crossOrigin = 'anonymous';
      img.onload = () => {
        if (img.naturalWidth < 100 || img.naturalHeight < 100 || /logo|icon|watermark/i.test(src)) return resolve(null);
        const c = document.createElement('canvas'); c.width = img.width; c.height = img.height;
        const ctx = c.getContext('2d'); ctx.drawImage(img, 0, 0);
        const d = ctx.getImageData(0,0,c.width,c.height);
        for (let i=0; i<d.data.length; i+=4) {
          const avg = (d.data[i]+d.data[i+1]+d.data[i+2])/3;
          d.data[i]=d.data[i+1]=d.data[i+2]=avg;
        }
        ctx.putImageData(d,0,0);
        resolve(c.toDataURL('image/png'));
      };
      img.onerror = () => resolve(null);
      img.src = src;
    });
  }

  function getScribdPages() {
    return [...document.querySelectorAll(
      '.page, .reader_column, [id^="page_container"], .outer_page, .abs_page, .scribd_page, .text_layer'
    )];
  }

  function parsePageRange(rangeText, totalPages) {
    const txt = safe(rangeText).trim().toLowerCase();
    if (!txt || txt === 'all') return Array.from({length: totalPages}, (_,i)=>i);
    const set = new Set();
    for (const part of txt.split(/[,;]\s*/)) {
      const m = part.match(/^(\d+)\s*-\s*(\d+)$/);
      if (m) {
        let a = clamp(+m[1],1,totalPages), b = clamp(+m[2],1,totalPages);
        if (a>b) [a,b]=[b,a];
        for (let p=a; p<=b; p++) set.add(p-1);
      } else {
        const n = clamp(parseInt(part,10),1,totalPages);
        if (!isNaN(n)) set.add(n-1);
      }
    }
    return [...set].sort((x,y)=>x-y);
  }

  // ---------- EXPORTS ----------
  function exportOutput(content, ext) {
    const split = settings.splitEvery | 0;
    const parts = content.split(/(?=\[Page \d+])/);
    if (!split || split < 1) {
      const blob = new Blob([content], { type: ext==='html' ? 'text/html' : 'text/plain' });
      const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `scribd_output.${ext}`; a.click();
      return;
    }
    for (let i=0; i<parts.length; i+=split) {
      const chunk = parts.slice(i,i+split).join('\n');
      const blob = new Blob([chunk], { type: ext==='html' ? 'text/html' : 'text/plain' });
      const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `scribd_part${Math.floor(i/split)+1}.${ext}`; a.click();
    }
  }

  function printToPDF(content) {
    const win = window.open('', 'PrintView');
    win.document.write(`<html><head><title>Scribd Print</title></head><body><pre>${content}</pre></body></html>`);
    win.document.close(); win.focus(); setTimeout(() => win.print(), 600);
  }

  async function exportSnapshotPDF(allPages) {
    await new Promise(r => { const chk = () => (window.html2canvas && window.jspdf) ? r() : setTimeout(chk,100); chk(); });
    const pages = getPagesInRange(allPages); if (!pages.length) return alert('No pages selected.');

    const scale   = clamp(+settings.snapshotScale || 2, 1, 4);
    const quality = +settings.snapshotQuality || 0.92;

    const { jsPDF } = window.jspdf;
    const pdf = new jsPDF({ unit:'pt', format:'a4', compress:true });
    const pageW = pdf.internal.pageSize.getWidth();
    const pageH = pdf.internal.pageSize.getHeight();

    for (let i=0; i<pages.length; i++) {
      const node = pages[i];
      node.scrollIntoView({block:'center'}); await sleep(220);
      const canvas = await window.html2canvas(node, { useCORS:true, allowTaint:true, backgroundColor:'#ffffff', scale });
      const imgData = canvas.toDataURL('image/jpeg', quality);
      const imgW = pageW, imgH = (canvas.height/canvas.width) * imgW;
      if (i>0) pdf.addPage();
      const finalH = imgH > pageH ? pageH : imgH;
      const finalW = imgH > pageH ? (pageH/imgH)*imgW : imgW;
      pdf.addImage(imgData, 'JPEG', 0, 0, finalW, finalH);
      if (i % 10 === 0) await sleep(40);
    }
    pdf.save('scribd_snapshot.pdf');
  }
  function getPagesInRange(allPages) {
    const idxs = parsePageRange(settings.pageRange, allPages.length);
    return idxs.map(i => allPages[i]).filter(Boolean);
  }

  // --- Rich HTML (DOM clone + images inlined) with layer de-dup ---
  async function exportRichHTML(allPages) {
    const pages = getPagesInRange(allPages); if (!pages.length) return alert('No pages selected.');
    const sections = [];

    for (let i=0; i<pages.length; i++) {
      const clone = pages[i].cloneNode(true);

      // Remove hidden bits that can become visible offline
      clone.querySelectorAll('[aria-hidden="true"], [style*="opacity:0"], [style*="opacity: 0"], [style*="visibility:hidden"]').forEach(n => n.remove());

      // Decide which layer to keep
      const hasTextLayer = !!clone.querySelector('.text_layer, [class*="textLayer"]');
      const preferText = settings.richPref === 'preferText' || (settings.richPref === 'auto' && hasTextLayer);

      if (preferText) {
        // Keep selectable text: drop canvases and likely page-wide images
        clone.querySelectorAll('canvas').forEach(n => n.remove());
        clone.querySelectorAll('img').forEach(img => {
          const cls = img.className || '';
          const w = (img.getAttribute('width') || '') + (img.style?.width || '');
          const h = (img.getAttribute('height') || '') + (img.style?.height || '');
          if (/page|render|canvas|background/i.test(cls) || /100%/.test(w+h)) img.remove();
        });
      } else {
        // Keep raster layer: drop absolutely-positioned text
        clone.querySelectorAll('.text_layer, [class*="textLayer"]').forEach(n => n.remove());
      }

      // Inline images (best effort)
      const imgs = [...clone.querySelectorAll('img')];
      await Promise.all(imgs.map(async (img) => {
        try {
          const src = img.getAttribute('src') || img.src;
          if (!src) return;
          img.setAttribute('src', await imageToDataURL(src));
        } catch { /* keep original src */ }
      }));

      // Strip scripts/styles inside clone
      clone.querySelectorAll('script, link[rel="stylesheet"]').forEach(n => n.remove());
      sections.push(`<section style="page-break-after:always">${clone.outerHTML}</section>`);
      if (i % 20 === 0) await sleep(15);
    }

    const html = `<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Scribd Rich Export</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
  /* keep it readable offline */
  *{transform:none !important}
  body{margin:16px;font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif;}
  section{margin:0 auto; max-width:900px;}
  img{max-width:100%; height:auto;}
</style>
</head>
<body>
${sections.join('\n')}
</body>
</html>`;

    const blob = new Blob([html], { type: 'text/html' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'scribd_rich.html';
    a.click();
  }

  function imageToDataURL(src) {
    return new Promise(resolve => {
      const img = new Image(); img.crossOrigin = 'anonymous';
      img.onload = () => {
        try {
          const c = document.createElement('canvas'); c.width = img.naturalWidth; c.height = img.naturalHeight;
          const ctx = c.getContext('2d'); ctx.drawImage(img,0,0); resolve(c.toDataURL('image/png'));
        } catch { resolve(src); }
      };
      img.onerror = () => resolve(src);
      const bust = src.includes('?') ? '&' : '?'; img.src = src + bust + 'x=' + Date.now();
    });
  }

  // ---------- SCRAPER ----------
  async function scrapePages(pages, preview) {
    const concurrency = 4; let index = 0; const firstText = [];
    async function scrape(page, i) {
      page.scrollIntoView(); await sleep(300);
      let found = false;
      const text = page.innerText.trim();
      if (text) { preview.textContent += `[Page ${i+1}] ✅\n${text}\n\n`; firstText.push(text); found = true; }
      if (settings.enableOCR && window.Tesseract) {
        const imgs = page.querySelectorAll('img');
        for (let img of imgs) {
          const src = img.src || ''; const processed = await preprocessImage(src);
          if (!processed) continue;
          const lang = settings.ocrLang === 'auto' ? detectLanguage(firstText.join(' ')) : settings.ocrLang;
          try {
            const res = await window.Tesseract.recognize(processed, lang);
            const ocrText = cleanOCRText(res.data.text || '');
            if (ocrText) { preview.textContent += `[OCR] ${ocrText}\n\n`; found = true; }
          } catch {}
        }
      }
      if (!found) preview.textContent += `[Page ${i+1}] ❌ No content\n\n`;
    }
    const workers = Array(concurrency).fill().map(async ()=>{ while (index < pages.length) { const i = index++; await scrape(pages[i], i); }});
    await Promise.all(workers);
    alert(`✅ Scraped ${pages.length} pages.`);
  }

  // ---------- DRAGGABLE + UI ----------
  function makeDraggable(el, storageKey, fallbackPos) {
    el.style.position = 'fixed'; el.style.touchAction = 'none';
    try {
      const saved = JSON.parse(localStorage.getItem(storageKey) || 'null');
      if (saved && Number.isFinite(saved.x) && Number.isFinite(saved.y)) {
        el.style.left = saved.x + 'px'; el.style.top = saved.y + 'px';
      } else if (fallbackPos) {
        const {x,y} = fallbackPos(); el.style.left = x + 'px'; el.style.top = y + 'px';
      }
    } catch {}
    let startX, startY, startL, startT, moved=false;
    const onDown = (e) => {
      moved = false;
      const p = e.touches ? e.touches[0] : e;
      startX=p.clientX; startY=p.clientY;
      const r = el.getBoundingClientRect(); startL=r.left; startT=r.top;
      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onUp);
      document.addEventListener('touchmove', onMove, {passive:false});
      document.addEventListener('touchend', onUp);
    };
    const onMove = (e) => {
      const p = e.touches ? e.touches[0] : e;
      if (e.cancelable) e.preventDefault();
      moved = true;
      const nx = clamp(startL + (p.clientX-startX), 0, window.innerWidth - el.offsetWidth);
      const ny = clamp(startT + (p.clientY-startY), 0, window.innerHeight - el.offsetHeight);
      el.style.left = nx + 'px'; el.style.top = ny + 'px';
    };
    const onUp = () => {
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
      document.removeEventListener('touchmove', onMove);
      document.removeEventListener('touchend', onUp);
      const r = el.getBoundingClientRect();
      localStorage.setItem(storageKey, JSON.stringify({x:r.left, y:r.top}));
      if (moved) { el.dataset.justDragged = '1'; setTimeout(()=>delete el.dataset.justDragged,150); }
    };
    el.addEventListener('mousedown', onDown);
    el.addEventListener('touchstart', onDown, {passive:false});
  }

  function buildUI() {
    // Gear
    const gear = document.createElement('div');
    gear.id = 'se-gear'; gear.textContent = '⚙️';
    document.body.appendChild(gear);
    makeDraggable(gear, UI_GEAR_KEY, () => ({ x: window.innerWidth - 70, y: window.innerHeight - 70 }));

    // Panel
    const panel = document.createElement('div'); panel.id = 'se-panel';
    panel.innerHTML = `
      <div id="se-header">
        <div>📚 Scribd Enhancer</div>
        <div class="controls">
          <div id="se-min" class="btn" title="Collapse">–</div>
          <div id="se-close" class="btn" title="Close">✕</div>
        </div>
      </div>
      <div id="se-body">
        <label><input type="checkbox" id="opt-unblur"> Unblur</label>
        <label><input type="checkbox" id="opt-autoscrape"> Auto Scrape</label>
        <label><input type="checkbox" id="opt-dark"> Dark Mode</label>
        <label><input type="checkbox" id="opt-preview"> Show Preview</label>

        <div class="row">
          <label style="flex:1">OCR
            <select id="opt-lang">
              <option value="auto">Auto</option>
              <option value="eng">English</option>
              <option value="spa">Spanish</option>
              <option value="fra">French</option>
              <option value="deu">German</option>
            </select>
          </label>
          <label style="flex:1">Split
            <select id="opt-split">
              <option value="0">Off</option>
              <option value="100">100</option>
              <option value="250">250</option>
              <option value="500">500</option>
            </select>
          </label>
        </div>

        <label>Export Page Range
          <input id="opt-range" type="text" placeholder="all | 1-25 | 5,7,10-12">
        </label>

        <div class="row">
          <label>Scale
            <select id="opt-scale">
              <option value="1">1x</option>
              <option value="2">2x</option>
              <option value="3">3x</option>
              <option value="4">4x</option>
            </select>
          </label>
          <label>JPEG
            <select id="opt-quality">
              <option value="0.8">0.80</option>
              <option value="0.92">0.92</option>
              <option value="1.0">1.00</option>
            </select>
          </label>
        </div>

        <label>Rich Export Preference
          <select id="opt-richpref">
            <option value="auto">Auto (prefer text layer if present)</option>
            <option value="preferText">Keep Text (remove page images)</option>
            <option value="preferImage">Keep Images (remove text layer)</option>
          </select>
        </label>

        <button id="btn-scrape">📖 Scrape Pages (Text/OCR)</button>
        <button id="btn-export">💾 Export TXT</button>
        <button id="btn-html">🧾 Export Plain HTML</button>
        <button id="btn-print">🖨️ Print (Text)</button>
        <button id="btn-snapshot-pdf">📸 Export Snapshot PDF</button>
        <button id="btn-rich-html">🖼️ Export Rich HTML</button>
      </div>
    `;
    document.body.appendChild(panel);
    makeDraggable(panel, UI_MENU_KEY, () => ({ x: window.innerWidth - 360, y: window.innerHeight - 360 }));

    // Open/Close & collapse
    const togglePanel = () => {
      if (gear.dataset.justDragged) return;
      panel.style.display = (panel.style.display === 'none' || !panel.style.display) ? 'block' : 'none';
    };
    gear.addEventListener('click', togglePanel);
    panel.querySelector('#se-close').addEventListener('click', () => panel.style.display = 'none');

    const body = panel.querySelector('#se-body');
    let collapsed = false;
    panel.querySelector('#se-min').addEventListener('click', () => {
      collapsed = !collapsed;
      body.style.display = collapsed ? 'none' : 'block';
      panel.querySelector('#se-min').textContent = collapsed ? '+' : '–';
    });

    // Keyboard shortcuts
    document.addEventListener('keydown', (e) => {
      if (e.key.toLowerCase() === 'g') togglePanel();
      if (e.key === 'Escape') panel.style.display = 'none';
    });

    // Bind controls
    const bind = (sel, prop, parser = v=>v) => {
      const el = panel.querySelector(sel);
      el.value = (prop in settings) ? settings[prop] : el.value;
      if (el.type === 'checkbox') el.checked = !!settings[prop];
      el.addEventListener('change', () => {
        settings[prop] = el.type === 'checkbox' ? el.checked : parser(el.value);
        saveSettings();
        applyDarkMode();
        if (prop === 'showPreview') {
          if (settings.showPreview && !document.getElementById('se-preview')) document.body.appendChild(preview);
          if (!settings.showPreview && document.getElementById('se-preview')) preview.remove();
        }
      });
      return el;
    };

    bind('#opt-unblur',   'unblur');
    bind('#opt-autoscrape','autoScrape');
    bind('#opt-dark',     'darkMode');
    bind('#opt-preview',  'showPreview');
    bind('#opt-lang',     'ocrLang');
    bind('#opt-split',    'splitEvery', v=>parseInt(v,10)||0);
    bind('#opt-range',    'pageRange',  v=>safe(v)||'all');
    bind('#opt-scale',    'snapshotScale', v=>clamp(parseInt(v,10)||2,1,4));
    bind('#opt-quality',  'snapshotQuality', v=>Number(v)||0.92);
    bind('#opt-richpref', 'richPref');

    // Actions
    panel.querySelector('#btn-scrape').onclick = () => {
      const pages = getScribdPages();
      if (!pages.length) return alert('❌ No pages found.');
      if (settings.showPreview && !document.getElementById('se-preview')) document.body.appendChild(preview);
      scrapePages(pages, preview);
    };
    panel.querySelector('#btn-export').onclick = () => exportOutput(preview.textContent, 'txt');
    panel.querySelector('#btn-html').onclick   = () => exportOutput(`<html><body><pre>${preview.textContent}</pre></body></html>`, 'html');
    panel.querySelector('#btn-print').onclick  = () => printToPDF(preview.textContent);
    panel.querySelector('#btn-snapshot-pdf').onclick = async () => {
      const pages = getScribdPages();
      if (!pages.length) return alert('❌ No pages found.');
      try { await exportSnapshotPDF(pages); } catch (e) { console.error(e); alert('Snapshot export failed. Try Rich HTML.'); }
    };
    panel.querySelector('#btn-rich-html').onclick = async () => {
      const pages = getScribdPages();
      if (!pages.length) return alert('❌ No pages found.');
      try { await exportRichHTML(pages); } catch (e) { console.error(e); alert('Rich HTML export failed.'); }
    };

    return { gear, panel };
  }

  // Preview box
  function createPreview() {
    const preview = document.createElement('div');
    preview.id = 'se-preview';
    if (settings.showPreview) {
      preview.textContent = '[Preview Initialized]\n';
      document.body.appendChild(preview);
    }
    return preview;
  }

  // ---------- BOOT ----------
  applyDarkMode();
  unblurContent();
  const preview = createPreview();
  const { gear } = buildUI();
  makeDraggable(gear, UI_GEAR_KEY, () => ({ x: window.innerWidth - 70, y: window.innerHeight - 70 }));

  // Auto-scrape if desired
  if (settings.autoScrape) {
    const pages = getScribdPages();
    if (pages.length && settings.showPreview && !document.getElementById('se-preview')) document.body.appendChild(preview);
    if (pages.length) scrapePages(pages, preview);
  }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址