YouTube 社区图片强制原图

使Youtube社区帖子图片无裁剪完全显示

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name   YouTube 社区图片强制原图
// @name:en YouTube community image forced original image
// @description 使Youtube社区帖子图片无裁剪完全显示
// @description:en Make Youtube community post images fully displayed without cropping
// @version        1.2.1
// @author         kaesinol
// @match          https://*.youtube.com/*
// @match          https://youtube.com/*
// @grant          GM_download
// @license        MIT
// @namespace https://greasyfork.org/users/1243515
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  console.log('[YouTubeImgFix] 脚本启动');

  // 匹配需要“还原原图”的 URL 片段(图片 URL)
  const MATCH_RE = /-c-fcrop64=|=s\d+/;

  // 页面匹配规则:与原先的 @match 四条等价,但在运行时判断(适配 SPA)
  const PAGE_PATTERNS = [
    /https?:\/\/([^\/]+\.)?youtube\.com\/[^?#]*\/posts(?:[\/?#]|$)/,                       // */posts
    /https?:\/\/([^\/]+\.)?youtube\.com\/post\/[^\/?#]+(?:[\/?#]|$)/,                 // /post/*
    /https?:\/\/([^\/]+\.)?youtube\.com\/channel\/[^\/?#]+\/posts(?:[\/?#]|$)/       // /channel/*/posts
  ];

  function pageMatches() {
    const href = location.href;
    for (const re of PAGE_PATTERNS) if (re.test(href)) return true;
    return false;
  }

  function fixImg(img) {
    if (!active) return; // 只有在目标页面时处理
    const src = img.getAttribute('src');
    if (!src || !MATCH_RE.test(src)) return;      // 只有真正匹配到才处理
    if (img.dataset.processed) return;          // 已处理过的跳过

    console.info('[YouTubeImgFix] 原图替换前 src:', src);
    let newSrc = src
      .replace(/-c-fcrop64=[^=]*/g, '')          // 去掉裁剪参数
      .replace(/=s\d+/g, '=s0');                // 强制最大尺寸
    console.info('[YouTubeImgFix] 原图替换后 src:', newSrc);

    img.setAttribute('src', newSrc);
    img.setAttribute('loading', 'lazy');         // 添加 lazyload 属性
    img.dataset.processed = '1';
  }

  // SPA 状态:当前页面是否为目标页面
  let active = pageMatches();
  console.log('[YouTubeImgFix] 初始 active =', active);

  // 当地址发生变化时(pushState / replaceState / popstate),更新 active
  function updateActiveAndProcess() {
    const prev = active;
    active = pageMatches();
    if (active && !prev) {
      console.log('[YouTubeImgFix] 进入目标页面,开始处理已有图片');
      // 进入目标页面时对现有图片做一次处理
      for (const img of document.images) fixImg(img);
    }
    if (!active && prev) console.log('[YouTubeImgFix] 离开目标页面,暂停处理');
  }

  // hook history API
  (function() {
    const _push = history.pushState;
    const _replace = history.replaceState;
    history.pushState = function() {
      const ret = _push.apply(this, arguments);
      window.dispatchEvent(new Event('yt-navigate'));
      return ret;
    };
    history.replaceState = function() {
      const ret = _replace.apply(this, arguments);
      window.dispatchEvent(new Event('yt-navigate'));
      return ret;
    };
    window.addEventListener('popstate', () => window.dispatchEvent(new Event('yt-navigate')));
    window.addEventListener('yt-navigate', updateActiveAndProcess);
  })();

  // 1. 初次对已有图片做一次尝试(不会标记未匹配的)
  if (active) {
    for (const img of document.images) {
      fixImg(img);
    }
  }

  // 2. 监听新插入的节点
  const observer = new MutationObserver(records => {
    if (!active) return; // 只在目标页面处理变化
    for (const rec of records) {
      // 新节点加入
      for (const node of rec.addedNodes) {
        if (node.nodeType !== 1) continue;
        if (node.tagName === 'IMG') {
          fixImg(node);
        } else if (node.querySelectorAll) {
          for (const img of node.querySelectorAll('img')) {
            fixImg(img);
          }
        }
      }
      // 属性变更
      if (rec.type === 'attributes' && rec.target.tagName === 'IMG') {
        fixImg(rec.target);
      }
    }
  });

  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['src']
  });

  console.log('[YouTubeImgFix] Observer 已启动,监听插入 & src 变更');
})();