Greasy Fork

YouTube Toolkit+: NewPipe button + H264 ≤30FPS + YouTube Adblock

Floating NewPipe intent button (incl. Shorts), forces H.264 ≤30FPS, hides ad overlays, auto-skips & jumps YouTube ads mobile.

当前为 2025-05-18 提交的版本,查看 最新版本

// ==UserScript==
// @name         YouTube Toolkit+: NewPipe button + H264 ≤30FPS + YouTube Adblock
// @version      1.0
// @description  Floating NewPipe intent button (incl. Shorts), forces H.264 ≤30FPS, hides ad overlays, auto-skips & jumps YouTube ads mobile.
// @author       jc
// @namespace    https://viayoo.com/
// @match        *://*.youtube.com/*
// @match        *://*.youtube-nocookie.com/*
// @match        *://*.youtubekids.com/*
// @match        https://m.youtube.com/*
// @exclude      *://www.youtube.com/*/music*
// @exclude      *://music.youtube.com/*
// @run-at       document-start
// @grant        none
// @icon         https://tenor.com/mIOhyqVCMa1.gif
// ==/UserScript==

/* ── 1. H.264 + FPS limiter (h264ify clone) ─────────────────────────────────── */
(function() {
  const BLOCK_HIGH_FPS = true;
  const DISALLOWED_TYPES_REGEX = /webm|vp8|vp9|av01/i;
  const FRAME_RATE_REGEX     = /framerate=(\d+)/;
  const ms = window.MediaSource;
  if (!ms) return;

  const orig = ms.isTypeSupported.bind(ms);
  ms.isTypeSupported = (type) => {
    if (typeof type !== 'string') return false;
    if (DISALLOWED_TYPES_REGEX.test(type)) return false;
    const m = FRAME_RATE_REGEX.exec(type);
    if (BLOCK_HIGH_FPS && m && +m[1] > 30) return false;
    return orig(type);
  };
})();

/* ── 2. Ad-skip & overlay blocker (undetected, desktop + mobile) ───────────── */
(function() {
  'use strict';

  // 2A. Selectors & CSS for both desktop & mobile
  const SKIP_BUTTON_SELECTORS = [
    'button.ytp-ad-skip-button',
    'button.ytp-ad-skip-button-modern',
    'paper-button.ytp-ad-skip-button'      // mobile Polymer button
  ];
  const OVERLAY_CSS = [
    '.ytp-ad-overlay.ytp-overlay-loading', // in-video desktop overlays
    '.ytp-featured-product',               // featured product promos
    'ytd-ad-preview-slot-renderer',        // mobile ad preview
    'ytm-compact-ad'                       // mobile compact ads
  ];

  // Inject overlay-hiding CSS
  const style = document.createElement('style');
  style.textContent = OVERLAY_CSS.join(',') + ' { display: none !important; }';
  document.head.appendChild(style);

  // Click “Skip Ad” if present
  function clickSkip() {
    for (const sel of SKIP_BUTTON_SELECTORS) {
      const btn = document.querySelector(sel);
      if (btn) { btn.click(); return true; }
    }
    return false;
  }

  // Jump to video end if unskippable
  function jumpAd() {
    const v = document.querySelector('video');
    if (v && isFinite(v.duration)) v.currentTime = v.duration;
  }

  // Observe player class changes
  function setupObserver(playerEl) {
    new MutationObserver(() => {
      if (playerEl.classList.contains('ad-showing')) {
        if (!clickSkip()) jumpAd();
      }
    }).observe(playerEl, { attributes: true, attributeFilter: ['class'] });
  }

  // Desktop player
  const desktopPlayer = document.getElementById('movie_player');
  if (desktopPlayer) setupObserver(desktopPlayer);

  // Mobile player (Polymer element)
  const mobilePlayer = document.querySelector('ytm-player, ytd-player');
  if (mobilePlayer) setupObserver(mobilePlayer);

  // Fallback periodic check
  setInterval(() => {
    if (document.querySelector('.ad-showing') || document.querySelector('video.ad-active')) {
      if (!clickSkip()) jumpAd();
    }
  }, 1000);

})();

/* ── 3. Floating NewPipe Button (incl. Shorts) ─────────────────────────────── */
(function() {
  'use strict';

  function buildIntent(id) {
    return `intent://www.youtube.com/watch?v=${id}#Intent;scheme=https;package=org.schabi.newpipe;end`;
  }

  function getVideoId() {
    try {
      const u = new URL(location.href);
      if (u.searchParams.has('v')) return u.searchParams.get('v');
      const m = location.pathname.match(/\/shorts\/([^\/?&]+)/);
      return m ? m[1] : null;
    } catch {
      return null;
    }
  }

  function makeButton() {
    const btn = document.createElement('div');
    btn.id = 'np-float-btn';
    btn.textContent = '⬇️';
    Object.assign(btn.style, {
      position: 'fixed',
      bottom:   '80px',
      right:    '16px',
      width:    '48px',
      height:   '48px',
      lineHeight:'48px',
      textAlign:'center',
      fontSize: '24px',
      backgroundColor: 'rgba(200,200,200,0.3)',
      color:    '#000',
      borderRadius: '50%',
      zIndex:   999999,
      cursor:   'pointer',
      opacity:  '0.7',
      transition:'opacity 0.2s'
    });
    btn.addEventListener('mouseenter', ()=>{ btn.style.opacity='1'; });
    btn.addEventListener('mouseleave', ()=>{ btn.style.opacity='0.7'; });
    btn.addEventListener('click', ()=>{
      const id = getVideoId();
      if (!id) return alert('No video ID found.');
      window.location.href = buildIntent(id);
    });
    document.body.appendChild(btn);
  }

  function init() {
    if (document.body) {
      makeButton();
    } else {
      new MutationObserver((_, obs)=>{
        if (document.body) {
          obs.disconnect();
          makeButton();
        }
      }).observe(document.documentElement, { childList:true });
    }
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();