YouTube Enhancer

Блокировка рекламы, скрытие Shorts, авто-пропуск, запоминание скорости, панель настроек.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         YouTube Enhancer
// @namespace    https://gitlab.com/about-group/About-project
// @version      1.2.0
// @description  Блокировка рекламы, скрытие Shorts, авто-пропуск, запоминание скорости, панель настроек.
// @author       About-project
// @match        *://www.youtube.com/*
// @match        *://youtube.com/*
// @match        *://m.youtube.com/*
// @icon         https://www.youtube.com/favicon.ico
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  const DEFAULT_SETTINGS = {
    blockAds: true,
    blockShorts: false,
    autoSkipAds: true,
    rememberSpeed: true,
    preferredSpeed: 1.0,
    hideEndCards: false,
    hideComments: false,
    confirmClose: false,
  };

  function loadSettings() {
    try {
      const raw = GM_getValue('yt_enhancer_settings', null);
      return raw ? Object.assign({}, DEFAULT_SETTINGS, JSON.parse(raw)) : { ...DEFAULT_SETTINGS };
    } catch {
      return { ...DEFAULT_SETTINGS };
    }
  }

  function saveSettings(s) {
    GM_setValue('yt_enhancer_settings', JSON.stringify(s));
  }

  let settings = loadSettings();

  const AD_CSS = `
    .ad-showing .video-ads,
    .ad-showing .ytp-ad-module,
    .ytp-ad-overlay-container,
    .ytp-ad-text-overlay,
    .ytp-ad-overlay-slot,
    .ytp-ad-image-overlay,
    #player-ads,
    #masthead-ad,
    #panels > ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"],
    ytd-ad-slot-renderer,
    ytd-banner-promo-renderer,
    ytd-statement-banner-renderer,
    ytd-in-feed-ad-layout-renderer,
    ytd-promoted-sparkles-web-renderer,
    ytd-promoted-video-renderer,
    ytd-display-ad-renderer,
    ytd-companion-slot-renderer,
    ytd-action-companion-ad-renderer,
    tp-yt-paper-dialog:has(#dismiss-button),
    .ytd-mealbar-promo-renderer,
    ytd-popup-container:has(.ytd-enforcement-message-view-model),
    ytd-rich-item-renderer:has(.ytd-ad-slot-renderer),
    #related ytd-promoted-sparkles-web-renderer,
    #related ytd-promoted-video-renderer,
    ytd-popup-container:has(a[href*="/premium"]),
    tp-yt-paper-dialog:has(yt-upsell-dialog-renderer),
    yt-mealbar-promo-renderer {
      display: none !important;
    }
  `;

  const SHORTS_CSS = `
    ytd-rich-shelf-renderer[is-shorts],
    ytd-reel-shelf-renderer,
    ytd-mini-guide-entry-renderer[aria-label="Shorts"],
    ytd-guide-entry-renderer:has(a[title="Shorts"]),
    ytd-guide-entry-renderer:has(a[href*="/shorts"]),
    a[title="Shorts"],
    ytd-video-renderer:has(a[href*="/shorts/"]),
    ytd-grid-video-renderer:has(a[href*="/shorts/"]) {
      display: none !important;
    }
  `;

  const END_CARDS_CSS = `
    .ytp-ce-element,
    .ytp-cards-teaser,
    .ytp-ce-covering-overlay,
    .ytp-ce-element-shadow,
    .ytp-ce-covering-image,
    .ytp-ce-expanding-image,
    .ytp-ce-element.ytp-ce-video,
    .ytp-ce-element.ytp-ce-channel,
    .ytp-ce-element.ytp-ce-playlist {
      display: none !important;
    }
  `;

  const COMMENTS_CSS = `
    #comments, ytd-comments {
      display: none !important;
    }
  `;

  function injectStyle(id, css) {
    if (document.getElementById(id)) return;
    const el = document.createElement('style');
    el.id = id;
    el.textContent = css;
    (document.head || document.documentElement).appendChild(el);
  }

  function removeStyle(id) {
    document.getElementById(id)?.remove();
  }

  function applyStyles() {
    ['yt-enh-ads', 'yt-enh-shorts', 'yt-enh-endcards', 'yt-enh-comments'].forEach(removeStyle);
    if (settings.blockAds) injectStyle('yt-enh-ads', AD_CSS);
    if (settings.blockShorts) injectStyle('yt-enh-shorts', SHORTS_CSS);
    if (settings.hideEndCards) injectStyle('yt-enh-endcards', END_CARDS_CSS);
    if (settings.hideComments) injectStyle('yt-enh-comments', COMMENTS_CSS);
  }

  function el(tag, styles, text) {
    const node = document.createElement(tag);
    if (styles) node.setAttribute('style', styles);
    if (text != null) node.textContent = text;
    return node;
  }

  function showWelcome() {
    if (GM_getValue('yt_enhancer_welcome_seen', false)) return;

    function create() {
      if (!document.body) return;

      const overlay = el('div',
        'position:fixed!important;top:0!important;left:0!important;' +
        'width:100vw!important;height:100vh!important;z-index:2147483646!important;' +
        'background:rgba(0,0,0,.6)!important;display:flex!important;' +
        'align-items:center!important;justify-content:center!important;'
      );
      overlay.id = 'yt-enh-welcome-overlay';

      const box = el('div',
        'background:#1e1e2e!important;color:#cdd6f4!important;border-radius:18px!important;' +
        'padding:32px 36px!important;width:400px!important;max-width:90vw!important;' +
        'font-family:Segoe UI,Roboto,sans-serif!important;text-align:center!important;' +
        'box-shadow:0 12px 48px rgba(0,0,0,.6)!important;border:1px solid #45475a!important;' +
        'box-sizing:border-box!important;'
      );

      box.appendChild(el('div', 'font-size:48px!important;margin-bottom:12px!important;', '👋 Привет!'));
      box.appendChild(el('h2',
        'margin:0 0 8px!important;font-size:20px!important;color:#f38ba8!important;font-weight:600!important;',
        'Привет! Спасибо, что установили YouTube Enhancer'
      ));
      box.appendChild(el('p',
        'margin:0 0 6px!important;font-size:14px!important;line-height:1.5!important;color:#a6adc8!important;',
        'Присоединяйтесь к нашему Discord-серверу:'
      ));

      const features = el('div',
        'text-align:left!important;margin:14px 0 18px!important;padding:12px 16px!important;' +
        'background:#313244!important;border-radius:10px!important;' +
        'font-size:13px!important;line-height:1.7!important;color:#cdd6f4!important;'
      );
      [
        'Обновления и новые функции первыми',
        'Помощь и поддержка от сообщества',
        'Предлагайте свои идеи для скрипта',
        'Общайтесь с другими пользователями',
      ].forEach(line => features.appendChild(el('div', null, line)));
      box.appendChild(features);

      const btns = el('div', 'display:flex!important;gap:12px!important;justify-content:center!important;');

      const join = el('a',
        'padding:10px 24px!important;border-radius:10px!important;font-size:14px!important;' +
        'font-weight:600!important;cursor:pointer!important;text-decoration:none!important;' +
        'background:#5865F2!important;color:#fff!important;display:inline-block!important;',
        'Вступить в Discord'
      );
      join.href = 'https://discord.gg/UvsnYm2msa';
      join.target = '_blank';
      join.rel = 'noopener';
      join.addEventListener('click', () => dismiss());

      const later = el('button',
        'padding:10px 24px!important;border-radius:10px!important;font-size:14px!important;' +
        'font-weight:600!important;cursor:pointer!important;border:none!important;' +
        'background:#45475a!important;color:#cdd6f4!important;',
        'Позже'
      );
      later.addEventListener('click', () => dismiss());

      btns.appendChild(join);
      btns.appendChild(later);
      box.appendChild(btns);
      overlay.appendChild(box);
      document.documentElement.appendChild(overlay);

      function dismiss() {
        GM_setValue('yt_enhancer_welcome_seen', true);
        overlay.remove();
      }

      overlay.addEventListener('click', e => { if (e.target === overlay) dismiss(); });
    }

    if (document.body) create();
    else document.addEventListener('DOMContentLoaded', create);
  }

  function trySkipAd() {
    if (!settings.autoSkipAds) return;

    const skipBtns = document.querySelectorAll(
      '.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-skip-ad-button, ' +
      'button.ytp-ad-skip-button-modern, .ytp-ad-skip-button-container button'
    );
    skipBtns.forEach(b => { if (b.offsetParent !== null) b.click(); });

    const player = document.querySelector('.ad-showing video.html5-main-video');
    if (player && player.duration && isFinite(player.duration)) {
      player.currentTime = player.duration;
    }

    const closeOverlay = document.querySelector('.ytp-ad-overlay-close-button');
    if (closeOverlay) closeOverlay.click();
  }

  function applyPlaybackSpeed() {
    if (!settings.rememberSpeed) return;
    const video = document.querySelector('video.html5-main-video');
    if (video && video.playbackRate !== settings.preferredSpeed) {
      video.playbackRate = settings.preferredSpeed;
    }
  }

  function watchSpeedChange() {
    const video = document.querySelector('video.html5-main-video');
    if (!video || video.__ytEnhSpeedWatcher) return;
    video.__ytEnhSpeedWatcher = true;
    video.addEventListener('ratechange', () => {
      if (!document.querySelector('.ad-showing') && settings.rememberSpeed) {
        settings.preferredSpeed = video.playbackRate;
        saveSettings(settings);
      }
    });
  }

  window.addEventListener('beforeunload', e => {
    if (!settings.confirmClose) return;
    const video = document.querySelector('video.html5-main-video');
    if (video && !video.paused) {
      e.preventDefault();
      e.returnValue = '';
    }
  });

  function openSettingsPanel() {
    if (document.getElementById('yt-enh-panel')) return;

    const overlay = el('div',
      'position:fixed!important;top:0!important;left:0!important;' +
      'width:100vw!important;height:100vh!important;z-index:2147483646!important;' +
      'background:rgba(0,0,0,.5)!important;display:block!important;'
    );
    overlay.id = 'yt-enh-overlay';
    overlay.addEventListener('click', closeSettingsPanel);

    const panel = el('div',
      'position:fixed!important;top:50%!important;left:50%!important;' +
      'transform:translate(-50%,-50%)!important;z-index:2147483647!important;' +
      'background:#1e1e2e!important;color:#cdd6f4!important;' +
      'border-radius:16px!important;padding:24px 28px!important;' +
      'width:380px!important;max-height:80vh!important;overflow-y:auto!important;' +
      'font-family:Segoe UI,Roboto,sans-serif!important;font-size:14px!important;' +
      'box-shadow:0 8px 32px rgba(0,0,0,.55)!important;' +
      'border:1px solid #45475a!important;box-sizing:border-box!important;' +
      'display:block!important;visibility:visible!important;opacity:1!important;'
    );
    panel.id = 'yt-enh-panel';

    panel.appendChild(el('h2',
      'margin:0 0 18px!important;font-size:18px!important;color:#f38ba8!important;text-align:center!important;font-weight:600!important;',
      'YouTube Enhancer'
    ));

    const toggles = [
      ['blockAds', 'Блокировать рекламу'],
      ['autoSkipAds', 'Авто-пропуск рекламы'],
      ['blockShorts', 'Скрыть Shorts'],
      ['hideEndCards', 'Скрыть подсказки в конце видео'],
      ['hideComments', 'Скрыть комментарии'],
      ['rememberSpeed', 'Запоминать скорость'],
      ['confirmClose', 'Подтверждение закрытия вкладки'],
    ];

    toggles.forEach(([key, text]) => {
      const row = el('label',
        'display:flex!important;align-items:center!important;justify-content:space-between!important;' +
        'padding:8px 0!important;border-bottom:1px solid #313244!important;cursor:pointer!important;color:#cdd6f4!important;'
      );
      row.appendChild(el('span', 'color:#cdd6f4!important;', text));
      const cb = el('input', 'width:18px!important;height:18px!important;accent-color:#f38ba8!important;cursor:pointer!important;margin:0!important;');
      cb.type = 'checkbox';
      cb.checked = !!settings[key];
      cb.addEventListener('change', () => { settings[key] = cb.checked; });
      row.appendChild(cb);
      panel.appendChild(row);
    });

    const speedRow = el('div',
      'display:flex!important;align-items:center!important;justify-content:space-between!important;' +
      'padding:10px 0!important;border-bottom:1px solid #313244!important;color:#cdd6f4!important;'
    );
    speedRow.appendChild(el('span', 'color:#cdd6f4!important;', 'Скорость по умолчанию'));
    const sel = el('select',
      'background:#313244!important;color:#cdd6f4!important;border:none!important;' +
      'border-radius:6px!important;padding:4px 8px!important;font-size:14px!important;'
    );
    [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3].forEach(s => {
      const opt = el('option', null, s + 'x');
      opt.value = s;
      if (settings.preferredSpeed === s) opt.selected = true;
      sel.appendChild(opt);
    });
    sel.addEventListener('change', () => { settings.preferredSpeed = parseFloat(sel.value); });
    speedRow.appendChild(sel);
    panel.appendChild(speedRow);

    const save = el('button',
      'display:block!important;margin:18px auto 0!important;background:#f38ba8!important;' +
      'color:#1e1e2e!important;border:none!important;border-radius:8px!important;' +
      'padding:8px 28px!important;font-size:14px!important;font-weight:600!important;cursor:pointer!important;',
      'Сохранить и закрыть'
    );
    save.addEventListener('click', () => {
      saveSettings(settings);
      closeSettingsPanel();
      applyStyles();
      applyPlaybackSpeed();
    });
    panel.appendChild(save);

    document.documentElement.appendChild(overlay);
    document.documentElement.appendChild(panel);
  }

  function closeSettingsPanel() {
    document.getElementById('yt-enh-panel')?.remove();
    document.getElementById('yt-enh-overlay')?.remove();
  }

  if (typeof GM_registerMenuCommand === 'function') {
    GM_registerMenuCommand('Настройки YouTube Enhancer', openSettingsPanel);
  }

  window.ytEnhOpenSettings = openSettingsPanel;

  document.addEventListener('keydown', e => {
    if (e.altKey && (e.code === 'KeyS' || e.key === 's' || e.key === 'S' || e.key === 'ы' || e.key === 'Ы')) {
      e.preventDefault();
      e.stopPropagation();
      openSettingsPanel();
    }
  }, true);

  function tick() {
    trySkipAd();
    applyPlaybackSpeed();
    watchSpeedChange();
  }

  applyStyles();
  showWelcome();

  const observer = new MutationObserver(tick);
  function startObserver() {
    observer.observe(document.body || document.documentElement, { childList: true, subtree: true });
  }
  if (document.body) startObserver();
  else document.addEventListener('DOMContentLoaded', startObserver);

  setInterval(tick, 1000);

  document.addEventListener('yt-navigate-finish', () => {
    applyStyles();
    tick();
  });
})();