Greasy Fork

来自缓存

Greasy Fork is available in English.

SEO 站长助手(快捷脚本)

在任意页面右侧浮动一个快捷脚本启动器,一键把当前域名丢到 Ahrefs/Similarweb/Google 等工具;支持自定义脚本列表(JSON 管理)。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         SEO 站长助手(快捷脚本)
// @namespace    https://yestool.org
// @version      1.0.0
// @description  在任意页面右侧浮动一个快捷脚本启动器,一键把当前域名丢到 Ahrefs/Similarweb/Google 等工具;支持自定义脚本列表(JSON 管理)。
// @author       https://github.com/yestool
// @license MIT
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_openInTab
// @run-at       document-idle
// ==/UserScript==

(function() {
  'use strict';

  // ---------- Utilities ----------
  const SKEY = 'qs_scripts_v1';
  const HKEY = 'qs_hidden_v1';

  // 简易 eTLD+1 近似:常见多段后缀覆盖,其他场景回退为最后两段
  const multiPartTLD = new Set([
    'co.uk','org.uk','ac.uk','gov.uk','co.jp','ne.jp','or.jp','com.au','net.au','org.au','co.nz','org.nz','com.br','com.cn','net.cn','org.cn','gov.cn','edu.cn','com.hk','com.sg'
  ]);
  function getDomain(hostname) {
    hostname = (hostname || location.hostname || '').toLowerCase();
    if (!hostname) return '';
    const parts = hostname.split('.');
    if (parts.length <= 2) return hostname;
    const last2 = parts.slice(-2).join('.');
    const last3 = parts.slice(-3).join('.');
    if (multiPartTLD.has(last2)) {
      // xxx.co.uk 场景 -> 取最后三段
      return parts.slice(-3).join('.');
    }
    if (multiPartTLD.has(last3)) {
      // 极少见更长多段,兜底
      return parts.slice(-4).join('.');
    }
    // 普通:取最后两段
    return last2;
  }

  function openTab(url, active = true) {
    try {
      GM_openInTab(url, { active, insert: true });
    } catch (e) {
      window.open(url, '_blank');
    }
  }

  function $(sel, root=document) { return root.querySelector(sel); }

  // 读取/初始化脚本定义
  function defaultScripts() {
    return [
      {
        name: "Ahrefs Backlink",
        type: "func",
        desc: "Ahrefs Backlink查询",
        code: `
          var domain = window.location.hostname;
          var ahrefsUrl = 'https://ahrefs.com/backlink-checker?input=' + encodeURIComponent(domain) + '&mode=subdomains';
          window.open(ahrefsUrl, '_blank');
        `
      },
      {
        name: "Ahrefs Site Explorer(domain)",
        type: "urlTemplate",
        desc: "用当前域名打开 Ahrefs Site Explorer(需已登录)",
        urlTemplate: "https://app.ahrefs.com/site-explorer/overview/v2/subdomains/live?target={domain}"
      },
      {
        name: "Google site:(domain)",
        type: "urlTemplate",
        desc: "快速 site: 当前域名",
        urlTemplate: "https://www.google.com/search?q=site%3A{domain}"
      },
      {
        name: "Similarweb ",
        type: "urlTemplate",
        desc: "查看 Similarweb 站点画像(需已登录)",
        urlTemplate: "https://pro.similarweb.com/#/digitalsuite/websiteanalysis/overview/website-performance/*/999/1m?webSource=Total&key={domain}"
      },
      {
        name: "Ahrefs Keyword Difficulty",
        type: "func",
        desc: "Ahrefs KD值查询(仅Google搜索页有效)",
        code: `
          var kw=document.querySelector('textarea[name=q]').value;
          var ahrefsUrl = 'https://ahrefs.com/keyword-difficulty/?country=us&input=' + encodeURIComponent(kw);
          window.open(ahrefsUrl, '_blank');
        `
      }
    ];
  }

  function readScripts() {
    let scripts = GM_getValue(SKEY, null);
    if (!scripts || !Array.isArray(scripts) || scripts.length === 0) {
      scripts = defaultScripts();
      GM_setValue(SKEY, scripts);
    }
    return scripts;
  }

  function saveScripts(scripts) {
    if (!Array.isArray(scripts)) return;
    GM_setValue(SKEY, scripts);
  }

  // ---------- UI ----------
  // 用 Shadow DOM 隔离样式,避免被站点 CSS 污染
  const host = document.createElement('div');
  host.id = 'qs-launcher-host';
  document.documentElement.appendChild(host);
  const shadow = host.attachShadow({ mode: 'open' });

  const style = document.createElement('style');
  style.textContent = `
    .qs-wrap{ position: fixed; top: 35%; right: 12px; z-index: 2147483647; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;}
    .qs-btn{ width: 44px; height: 44px; border-radius: 50%; background: #111; color:#fff; display:flex; align-items:center; justify-content:center; cursor:pointer; box-shadow:0 6px 18px rgba(0,0,0,.25); transition:.2s; }
    .qs-btn:hover{ transform: translateY(-1px); }
    .qs-hide{ display:none !important; }

    .qs-panel{ position: fixed; top: 20%; right: 70px; width: 320px; max-height: 60vh; overflow:auto; background:#fff; color:#111; border:1px solid #e5e7eb; border-radius:12px; box-shadow:0 10px 30px rgba(0,0,0,.15); }
    .qs-header{ display:flex; align-items:center; justify-content:space-between; padding:10px 12px; border-bottom:1px solid #eee; position:sticky; top:0; background:#fff; z-index:1;}
    .qs-title{ font-weight:600; font-size:14px;}
    .qs-actions button{ margin-left:8px; font-size:12px; padding:4px 8px; border:1px solid #e5e7eb; background:#f8fafc; border-radius:8px; cursor:pointer;}
    .qs-actions button:hover{ background:#eef2f7;}
    .qs-list{ padding:8px; }
    .qs-item{ display:flex; flex-direction:column; padding:8px; border-radius:10px; border:1px solid #f1f5f9; margin-bottom:8px; }
    .qs-item h4{ margin:0 0 6px 0; font-size:14px; }
    .qs-item p{ margin:0 0 8px 0; font-size:12px; color:#475569;}
    .qs-run{ align-self:flex-start; padding:6px 10px; border-radius:8px; border:1px solid #e5e7eb; background:#111; color:#fff; cursor:pointer; font-size:12px;}
    .qs-run:hover{ filter:brightness(1.05); }
    .qs-empty{ padding:16px; color:#64748b; font-size:13px; }
    .qs-close{ position:absolute; top:6px; right:8px; background:transparent; border:none; font-size:18px; cursor:pointer; color:#333;}
    .qs-toggle{ margin-left:8px; font-size:12px; padding:4px 8px; border:1px solid #e5e7eb; background:#fff; border-radius:8px; cursor:pointer;}
  `;

  const wrap = document.createElement('div');
  wrap.className = 'qs-wrap';

  const btn = document.createElement('div');
  btn.className = 'qs-btn';
  btn.title = '快捷脚本';
  btn.textContent = '⚡';

  const panel = document.createElement('div');
  panel.className = 'qs-panel qs-hide';
  panel.innerHTML = `
    <div class="qs-header">
      <div class="qs-title">快捷脚本</div>
      <div class="qs-actions">
        <button class="qs-manage" title="管理脚本(JSON)">⚙ 管理</button>
        <button class="qs-hidebtn" title="隐藏浮标">🙈 隐藏</button>
      </div>
      <button class="qs-close" title="关闭面板">×</button>
    </div>
    <div class="qs-list"></div>
  `;

  shadow.append(style, wrap);
  wrap.append(btn, panel);

  // 记住隐藏状态
  const hidden = GM_getValue(HKEY, false);
  if (hidden) wrap.classList.add('qs-hide');

  // 列表渲染
  function renderList() {
    const list = $('.qs-list', shadow);
    list.innerHTML = '';
    const scripts = readScripts();
    if (!scripts.length) {
      const div = document.createElement('div');
      div.className = 'qs-empty';
      div.textContent = '暂无脚本,点右上角 “⚙ 管理” 添加。';
      list.appendChild(div);
      return;
    }
    const ctx = buildContext();
    scripts.forEach((s, idx) => {
      const item = document.createElement('div');
      item.className = 'qs-item';
      const h4 = document.createElement('h4');
      h4.textContent = s.name || `脚本 #${idx+1}`;
      const p = document.createElement('p');
      p.textContent = s.desc || '';
      const run = document.createElement('button');
      run.className = 'qs-run';
      run.textContent = '执行';
      run.addEventListener('click', () => runScript(s, ctx));
      item.append(h4, p, run);
      list.appendChild(item);
    });
  }

  // 上下文对象
  function buildContext() {
    const hostname = location.hostname;
    const domain = getDomain(hostname);
    const selection = String(window.getSelection ? (window.getSelection()+'') : '') || '';
    const title = document.title || '';
    const url = location.href;
    return { hostname, domain, selection, title, url };
  }

  // 执行器:两类——urlTemplate / func(字符串函数体)
  function runScript(s, ctx) {
    try {
      if (s.type === 'urlTemplate' && s.urlTemplate) {
        const finalUrl = s.urlTemplate
          .replaceAll('{domain}', encodeURIComponent(ctx.domain))
          .replaceAll('{hostname}', encodeURIComponent(ctx.hostname))
          .replaceAll('{url}', encodeURIComponent(ctx.url))
          .replaceAll('{title}', encodeURIComponent(ctx.title))
          .replaceAll('{selection}', encodeURIComponent(ctx.selection));
        if (!finalUrl || !/^https?:\/\//i.test(finalUrl)) {
          alert('URL 模板无效');
          return;
        }
        openTab(finalUrl, true);
        return;
      }
      if (s.type === 'func' && s.code) {
        // 在 userscript 沙箱中执行,可直接操作 DOM
        const fn = new Function(s.code);
        fn.call(window);
        return;
      }
      alert('未知脚本类型或缺少必要字段。');
    } catch (e) {
      console.error('[Quick Scripts] 执行出错:', e);
      alert('脚本执行失败:' + e.message);
    }
  }

  // 事件绑定
  btn.addEventListener('click', () => {
    panel.classList.toggle('qs-hide');
    if (!panel.classList.contains('qs-hide')) renderList();
  });
  panel.querySelector('.qs-close').addEventListener('click', () => {
    panel.classList.add('qs-hide');
  });
  panel.querySelector('.qs-hidebtn').addEventListener('click', () => {
    wrap.classList.add('qs-hide');
    GM_setValue(HKEY, true);
  });
  panel.querySelector('.qs-manage').addEventListener('click', () => {
    const current = JSON.stringify(readScripts(), null, 2);
    const next = prompt(
      '以 JSON 数组形式编辑脚本:\n' +
      '支持两种类型:\n' +
      '1) { "name":"xxx", "type":"urlTemplate", "desc":"...", "urlTemplate":"https://...{domain}..." }\n' +
      '2) { "name":"xxx", "type":"func", "desc":"...", "code":"/* JS 函数体,可用 document.querySelector 等 */" }\n\n' +
      '当前:',
      current
    );
    if (next == null) return;
    try {
      const parsed = JSON.parse(next);
      if (!Array.isArray(parsed)) throw new Error('必须是数组');
      saveScripts(parsed);
      alert('已保存。');
      renderList();
    } catch (e) {
      alert('JSON 解析失败:' + e.message);
    }
  });

  // 在页面左下角加一个“显示浮标”的极简入口(当你把浮标隐藏后)
  function addRestoreBtn() {
    if ($('#qs-restore', shadow)) return;
    const r = document.createElement('button');
    r.id = 'qs-restore';
    r.textContent = '⚡';
    r.title = '显示快捷脚本浮标';
    r.style.position = 'fixed';
    r.style.left = '10px';
    r.style.bottom = '10px';
    r.style.zIndex = '2147483647';
    r.style.width = '36px';
    r.style.height = '36px';
    r.style.borderRadius = '50%';
    r.style.border = '1px solid #e5e7eb';
    r.style.background = '#fff';
    r.style.cursor = 'pointer';
    r.addEventListener('click', () => {
      wrap.classList.remove('qs-hide');
      GM_setValue(HKEY, false);
      r.remove();
    });
    shadow.appendChild(r);
  }
  if (hidden) addRestoreBtn();

})();