Linux.do 摸鱼伪装器

伪装标题(默认“abc”);Logo隐藏;按钮/菜单切换;标题栏透明度

// ==UserScript==
// @name         Linux.do 摸鱼伪装器
// @namespace    waino.moyu.material
// @version      1.0.0
// @description  伪装标题(默认“abc”);Logo隐藏;按钮/菜单切换;标题栏透明度
// @match        https://linux.do/*
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // --- 可动态配置标题 ---
  const TITLE_KEY = 'moyu_title';
  const STORE_KEY = 'moyu_enabled';
  const OPACITY_KEY = 'moyu_header_opacity';
  const defaultTitle = 'abc';
  const defaultEnabled = location.pathname.startsWith('/latest');
  const defaultOpacity = 1; // 0~1

  const getTitle = () => (GM_getValue(TITLE_KEY, defaultTitle) || defaultTitle);
  const setTitle = (t) => GM_setValue(TITLE_KEY, (t || '').trim() || defaultTitle);

  const getEnabled = () => {
    const v = GM_getValue(STORE_KEY, null);
    return v === null ? defaultEnabled : !!v;
  };
  const setEnabled = (v) => GM_setValue(STORE_KEY, !!v);

  const getOpacity = () => {
    let v = GM_getValue(OPACITY_KEY, defaultOpacity);
    v = Number.isFinite(+v) ? Math.max(0, Math.min(1, +v)) : defaultOpacity;
    return v;
  };
  const setOpacity = (v) => {
    const num = Number.isFinite(+v) ? Math.max(0, Math.min(1, +v)) : defaultOpacity;
    GM_setValue(OPACITY_KEY, num);
    applyOpacity(num);
  };

  // 1x1 透明 ICO
  const BLANK_ICON_DATA_URL =
    'data:image/x-icon;base64,AAABAAEAEBAAAAAAAABoAwAAJgAAACAgAAAAAAAAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=';

  const CLASS_ACTIVE = 'moyu-active';

  let original = { title: null, icons: [] };
  let headObserver = null;
  let titleObserver = null, titleTimer = null;

  // ---------- icon / logo ----------
  function addBlankFaviconSentinel() {
    const head = document.head || document.documentElement;
    let link = head.querySelector('#moyu-favicon-sentinel');
    if (!link) {
      link = document.createElement('link');
      link.id = 'moyu-favicon-sentinel';
      link.rel = 'icon';
      link.href = BLANK_ICON_DATA_URL;
      head.insertBefore(link, head.firstChild);
    } else {
      link.href = BLANK_ICON_DATA_URL;
    }
  }

  function snapshotFavicons() {
    original.icons = [];
    document
      .querySelectorAll('link[rel*="icon"], link[rel="apple-touch-icon"], link[rel="mask-icon"]')
      .forEach(l => {
        if (l.id === 'moyu-favicon-sentinel') return;
        original.icons.push({
          rel: l.getAttribute('rel'),
          href: l.getAttribute('href'),
          sizes: l.getAttribute('sizes'),
          type: l.getAttribute('type')
        });
      });
  }

  function removeFaviconsAndManifest() {
    document
      .querySelectorAll('link[rel*="icon"], link[rel="apple-touch-icon"], link[rel="mask-icon"], link[rel="manifest"]')
      .forEach(l => { if (l.id !== 'moyu-favicon-sentinel') l.remove(); });
    addBlankFaviconSentinel();
  }

  function startHeadObserver() {
    if (headObserver) return;
    headObserver = new MutationObserver(muts => {
      for (const m of muts) {
        m.addedNodes.forEach(node => {
          if (node.nodeType !== 1 || node.tagName !== 'LINK') return;
          const rel = (node.getAttribute('rel') || '').toLowerCase();
          if (rel.includes('icon') || rel === 'apple-touch-icon' || rel === 'mask-icon' || rel === 'manifest') {
            node.remove();
            addBlankFaviconSentinel();
          }
        });
      }
    });
    headObserver.observe(document.head || document.documentElement, { childList: true, subtree: true });
  }
  function stopHeadObserver() { if (headObserver) headObserver.disconnect(); headObserver = null; }

  // ---------- 标题伪装 ----------
  function enforceTitle() {
    const want = getTitle();
    if (document.title !== want) document.title = want;
  }
  function startTitleGuards() {
    if (!document.querySelector('title')) {
      const t = document.createElement('title');
      (document.head || document.documentElement).appendChild(t);
    }
    if (!titleObserver) {
      titleObserver = new MutationObserver(enforceTitle);
      titleObserver.observe(document.querySelector('title'), { childList: true, characterData: true, subtree: true });
    }
    if (!titleTimer) titleTimer = setInterval(enforceTitle, 500);
    enforceTitle();
  }
  function stopTitleGuards() { if (titleObserver) titleObserver.disconnect(); titleObserver = null; if (titleTimer) clearInterval(titleTimer); titleTimer = null; }

  // ---------- 透明度 ----------
  function ensureOpacityStyle() {
    let style = document.getElementById('moyu-opacity-style');
    if (!style) {
      style = document.createElement('style');
      style.id = 'moyu-opacity-style';
      (document.head || document.documentElement).appendChild(style);
    }
    return style;
  }
  function applyOpacity(v = getOpacity()) {
    const style = ensureOpacityStyle();
    style.textContent = `
      .two-rows.extra-info-wrapper { opacity: ${v}; }
    `;
  }

  // ---------- 模式切换 ----------
  function enterMoyu() {
    if (original.title === null) original.title = document.title;

    snapshotFavicons();
    removeFaviconsAndManifest();
    startHeadObserver();

    document.documentElement.classList.add(CLASS_ACTIVE);

    startTitleGuards();

    updateButton(true);
    setEnabled(true);
  }

  function exitMoyu() {
    stopHeadObserver();
    stopTitleGuards();

    if (original.title !== null) document.title = original.title;

    const sentinel = document.querySelector('#moyu-favicon-sentinel');
    if (sentinel) sentinel.remove();
    const head = document.head || document.documentElement;
    original.icons.forEach(i => {
      if (!i || !i.rel || !i.href) return;
      const link = document.createElement('link');
      link.setAttribute('rel', i.rel);
      link.setAttribute('href', i.href);
      if (i.sizes) link.setAttribute('sizes', i.sizes);
      if (i.type) link.setAttribute('type', i.type);
      head.appendChild(link);
    });

    document.documentElement.classList.remove(CLASS_ACTIVE);

    updateButton(false);
    setEnabled(false);
  }

  // ---------- UI ----------
  let btn;
  function ensureButton() {
    if (btn) return btn;
    btn = document.createElement('button');
    btn.id = 'moyu-toggle-btn';
    btn.textContent = '…';
    btn.title = '切换摸鱼/正常';
    btn.addEventListener('click', () => (getEnabled() ? exitMoyu() : enterMoyu()));
    document.documentElement.appendChild(btn);
    return btn;
  }
  function updateButton(enabled) {
    ensureButton();
    btn.textContent = enabled ? '摸鱼中' : '正常';
    btn.className = enabled ? 'moyu-on' : 'moyu-off';
  }

  // —— 基础样式 ——
  const css = `
    #moyu-toggle-btn {
      position: fixed; left: 14px; bottom: 14px; z-index: 999999;
      padding: 8px 12px; border-radius: 12px; border: none; cursor: pointer;
      font-size: 14px; line-height: 1; box-shadow: 0 2px 10px rgba(0,0,0,.15);
      backdrop-filter: blur(6px);
    }
    #moyu-toggle-btn.moyu-on  { background: #e5e7eb; color: #111827; }
    #moyu-toggle-btn.moyu-off { background: #111827; color: #e5e7eb; }
    #moyu-toggle-btn:hover { opacity: .9; }

    /* 仅在摸鱼状态隐藏首页 Logo 相关元素 */
    html.${CLASS_ACTIVE} .home-logo-wrapper-outlet,
    html.${CLASS_ACTIVE} .title picture,
    html.${CLASS_ACTIVE} .title #site-logo,
    html.${CLASS_ACTIVE} .title .logo-big {
      display: none !important;
      visibility: hidden !important;
    }
  `;
  function injectCSS() {
    const style = document.createElement('style');
    style.textContent = css;
    (document.head || document.documentElement).appendChild(style);
  }

  // —— 菜单 ——
  function registerMenus() {
    GM_registerMenuCommand(`摸鱼模式:${getEnabled() ? '关闭' : '开启'}`, () => {
      getEnabled() ? exitMoyu() : enterMoyu();
    });

    GM_registerMenuCommand(`设置伪装标题`, () => {
      const val = prompt('输入新的伪装标题:', getTitle());
      if (val == null) return;
      setTitle(val);
      if (getEnabled()) enforceTitle();
    });

    GM_registerMenuCommand(`设置标题栏透明度`, () => {
      const cur = getOpacity();
      const input = prompt(`输入透明度(0~1,小数允许):`, String(cur));
      if (input == null) return;
      let v = parseFloat(String(input).trim().replace('%',''));
      // 支持百分比写法(例如“60%”理解为 0.6)
      if (String(input).includes('%')) v = v / 100;
      if (!Number.isFinite(v)) return alert('无效数值');
      v = Math.max(0, Math.min(1, v));
      setOpacity(v);
    });
  }

  function domReady(fn) {
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn, { once: true });
    else fn();
  }

  addBlankFaviconSentinel();
  injectCSS();
  registerMenus();
  applyOpacity(getOpacity());

  domReady(() => {
    ensureButton();
    getEnabled() ? enterMoyu() : exitMoyu();
    applyOpacity(getOpacity());
  });
})();

QingJ © 2025

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