Eliminar anuncios de Twitch gratis

En este post os voy a explicar como eliminar los anuncios de Twitch de manera gratuita, sin suscribirte a ningún canal. Para ello vamos a utilizar un script.

  • Ahora hacemos click en la misma y agregamos un nuevo script:
  • Aquí eliminamos todo el código y copiamos el siguiente:
// ==UserScript==
// @name          twitchAdSkip
// @namespace     https://www.twitch.tv/
// @version       2.0
// @description   Script to skip ad placeholder (i.e. purple screen of doom when ads are blocked)
// @author        simple-hacker & Wilkolicious
// @match         https://www.twitch.tv/*
// @grant         none
// @require       https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.11.0/underscore-min.js
// @homepageURL   https://github.com/Wilkolicious/twitchAdSkip
// @updateURL     https://raw.githubusercontent.com/Wilkolicious/twitchAdSkip/main/twitchAdSkip.js
// @downloadURL   https://raw.githubusercontent.com/Wilkolicious/twitchAdSkip/main/twitchAdSkip.js
// ==/UserScript==

(function () {
  'use strict';

  const scriptName = 'twitchAdSkip';
  const adTestSel = '[data-test-selector="ad-banner-default-text"]';
  const ffzResetBtnSel = '[data-a-target="ffz-player-reset-button"]';
  const videoPlayerSel = '[data-a-target="video-player"]';
  const videoPlayervolSliderSel = '[data-a-target="player-volume-slider"]';
  const videoNodeSel = 'video';
  const postFixVolWaitTime = 2000;
  const nodeTypesToCheck = [Node.ELEMENT_NODE, Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE];

  //
  const maxRetriesFindVideoPlayer = 5;
  const maxRetriesVolListener = 5;
  const maxRetriesVideoPlayerObserver = 5;

  // Volume vals
  let videoNodeVolCurrent;
  let adLaunched = false;

  // Helpers //
  const log = function (logType, message) {
    return console[logType](`${scriptName}: ${message}`);
  };
  const getFfzResetButton = function () {
    return document.querySelector(ffzResetBtnSel);
  };
  const getElWithOptContext = function (selStr, context) {
    context = context || document;
    return context.querySelector(selStr);
  };
  const getVideoNodeEl = function (context) {
    return getElWithOptContext(videoNodeSel, context);
  };
  const getVideoPlayerVolSliderEl = function (context) {
    return getElWithOptContext(videoPlayervolSliderSel, context);
  };
  const getVideoPlayerEl = function (context) {
    return getElWithOptContext(videoPlayerSel, context);
  };

  const attachMO = async function (videoPlayerEl) {
    let resetButton = getFfzResetButton();

    const videoPlayerObserver = new MutationObserver(function (mutations) {
      for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
          const canCheckNode = nodeTypesToCheck.includes(node.nodeType);
          if (!canCheckNode) {
            continue;
          }

          const isAdNode = node.querySelector(adTestSel);
          if (!isAdNode) {
            continue;
          }

          log('info', `Found ad node at: ${adTestSel}`);

          // Is ad //
          adLaunched = true;
          if (!resetButton) {
            log('info', `FFZ reset button not loaded - attempting to load...`);

            // Attempt to load the resetButton now
            resetButton = getFfzResetButton();

            if (!resetButton) {
              log('error', `FFZ reset button could not be loaded - refreshing full page.`);

              // Not loaded for some reason
              window.location.reload();
            }
          }

          // Cache current vol props //
          log('info', 'Finding video node to post-fix volume.');
          // Actual video volume
          const videoNodeEl = getVideoNodeEl(videoPlayerEl);
          log('info', `Volume before reset: ${videoNodeVolCurrent}`);

          // Cosmetic vol slider
          const videoPlayerVolSliderEl = getVideoPlayerVolSliderEl(videoPlayerEl);
          const videoPlayerVolSliderCurrent = parseInt(videoPlayerVolSliderEl.value, 10).toFixed(2);

          log('info', `Triggering FFZ reset button...`);
          resetButton.dispatchEvent(new MouseEvent('dblclick', {
            bubbles: true,
            cancelable: true,
            view: window
          }));

          log('info', `Fixing volume to original value of '${videoNodeVolCurrent}' after interval of '${postFixVolWaitTime}' ms`);
          setTimeout(() => {
            // Does the video player element still exist after reset?
            if (!videoPlayerEl) {
              log('info', 'Video player element destroyed after reset - sourcing new element...');
              videoPlayerEl = getVideoPlayerEl();
            }

            // Does the video node still exist after reset?
            if (!videoNodeEl) {
              log('info', 'Video node destroyed after reset - sourcing new node...');
              videoNodeEl = getVideoNodeEl(videoPlayerEl);
            }

            // Fix video vol
            const preFixVol = videoNodeEl.volume;
            videoNodeEl.volume = videoNodeVolCurrent;
            log('info', `Post-fixed volume from reset val of '${preFixVol}' -> '${videoNodeVolCurrent}'`);

            // Fix video player vol slider
            // TODO: this may not work due to this input being tied to the js framework component
            if (!videoPlayerVolSliderEl) {
              videoPlayerVolSliderEl = getVideoPlayerVolSliderEl(videoPlayerEl);
            }
            videoPlayerVolSliderEl.value = videoPlayerVolSliderCurrent;

            adLaunched = false;
          }, postFixVolWaitTime);
        }
      }
    });

    videoPlayerObserver.observe(videoPlayerEl, {
      childList: true,
      subtree: true
    });
    log('info', 'Video player observer attached');
  };

  const listenForVolumeChanges = async function (videoPlayerEl) {
    const videoNodeEl = getVideoNodeEl(videoPlayerEl);

    if (!videoNodeEl) {
      throw new Error('Video player element not found.  If it is expected that there is no video on the current page (e.g. Twitch directory), then ignore this error.');
    }

    // Initial load val
    videoNodeVolCurrent = videoNodeEl.volume.toFixed(2);
    log('info', `Initial volume: '${videoNodeVolCurrent}'.`);

    const videoPlayerVolSliderEl = getVideoPlayerVolSliderEl(videoPlayerEl);

    if (!videoPlayerVolSliderEl) {
      throw new Error('Video player volume slider not found.  Perhaps application is in picture-in-picture mode?');
    }

    const setCurrentVolume = (event) => {
      // Ignore any vol changes for ads
      if (document.querySelector(adTestSel) || adLaunched) {
        return;
      }

      // Always find the video node element as Twitch app may have re-created tracked element
      videoNodeVolCurrent = getVideoNodeEl(videoPlayerEl).volume.toFixed(2);
      log('info', `Volume modified to: '${videoNodeVolCurrent}'.`);
    };

    // Standard volume change listeners
    videoPlayerVolSliderEl.addEventListener('keyup', (event) => {
      if (!event.key) {
        return;
      }

      if (!['ArrowUp', 'ArrowDown'].includes(event.key)) {
        return;
      }
      setCurrentVolume(event);
    });

    videoPlayerVolSliderEl.addEventListener('mouseup', setCurrentVolume);
    videoPlayerVolSliderEl.addEventListener('scroll', (event) => _.debounce(setCurrentVolume, 1000));

    // TODO: FFZ scrollup & scrolldown support
  };

  const retryWrap = function(fnToRetry, args, intervalInMs, maxRetries, actionDescription) {
    const retry = (fn, retries = 3) => fn()
      .catch((e) => {
        if (retries <= 0) {
          log('error', `${actionDescription} - failed after ${maxRetries} retries.`)
          return Promise.reject(e);
        }
        log('warn', `${actionDescription} - retrying another ${retries} time(s).`);
        return retry(fn, --retries)
      });

    const delay = ms => new Promise((resolve) => setTimeout(resolve, ms));
    const delayError = (fn, args, ms) => () => fn(...args).catch((e) => delay(ms).then((y) => Promise.reject(e)));
    return retry(delayError(fnToRetry, args, intervalInMs), maxRetries);
  };

  const spawnFindVideoPlayerEl = async function() {
    const actionDescription = 'Finding video player';
    log('info', `${actionDescription}...`);
    const findVideoPlayerEl = async () => {
      const videoPlayerEl = document.querySelector(videoPlayerSel);
      if (!videoPlayerEl) {
        return Promise.reject('Video player not found.');
      }
      return videoPlayerEl;
    };
    return await retryWrap(findVideoPlayerEl, [], 2000, maxRetriesFindVideoPlayer, actionDescription);
  };

  const spawnVolumeChangeListener = async function(videoPlayerEl) {
    const actionDescription = 'Listening for volume changes';
    log('info', `${actionDescription}...`);
    retryWrap(listenForVolumeChanges, [videoPlayerEl], 2000, maxRetriesVolListener, actionDescription);
  };

  const spawnVideoPlayerAdSkipObservers = async function(videoPlayerEl) {
    const actionDescription = 'Attaching MO';
    log('info', `${actionDescription}...`);
    retryWrap(attachMO, [videoPlayerEl], 2000, maxRetriesVideoPlayerObserver, actionDescription);
  };

  const spawnObservers = async function () {
    try {
      const videoPlayerEl = await spawnFindVideoPlayerEl();

      if (!videoPlayerEl) {
        throw new Error('Could not find video player.');
      }
      log('info', 'Success - video player found.');

      spawnVolumeChangeListener(videoPlayerEl);
      spawnVideoPlayerAdSkipObservers(videoPlayerEl);
    } catch (error) {
      log('error', error);
    }
  }

  log('info', 'Page loaded - attempting to spawn observers...');
  spawnObservers();

  log('info', 'Overloading history push state')
  var pushState = history.pushState;
  history.pushState = function () {
    pushState.apply(history, arguments);

    log('info', 'History change - attempting to spawn observers...')
    spawnObservers();
  };
})();
  • Y ya está, no veremos ningún anuncio más.

Este post lo he creado por pura información y curiosidad, no recomiendo utilizar este script ya que los streamers pueden dejar de recibir los ingresos de los anuncios que no vemos.

No olvides comentar si te ha servido de ayuda o por el contrario algo no te ha funcionado.

Deja un comentario

También te puede interesar