Skip to content

Instantly share code, notes, and snippets.

@iosifnicolae2
Last active December 7, 2025 19:52
Show Gist options
  • Select an option

  • Save iosifnicolae2/1a78b5ab1d166fd5d17e6ae0a0fe0901 to your computer and use it in GitHub Desktop.

Select an option

Save iosifnicolae2/1a78b5ab1d166fd5d17e6ae0a0fe0901 to your computer and use it in GitHub Desktop.
Youtube is Boring

How To Make Youtube Less Boring

Tutorial: https://www.youtube.com/watch?v=hIqMrPTeGTc
Paste the below code in your browser console (F12 > Console):

/**
 * YouTube "Not Interested" Automation Script
 *
 * This script marks all videos in the YouTube feed as "Not interested".
 *
 * Usage:
 * 1. Open YouTube homepage in your browser
 * 2. Open browser DevTools (F12 or Cmd+Option+I)
 * 3. Go to the Console tab
 * 4. Paste this entire script and press Enter
 *
 * The script will:
 * - Find all video cards in the current feed
 * - Click the "More actions" menu (three dots) on each video
 * - Select "Not interested" from the dropdown
 * - Wait between actions to avoid rate limiting
 * - Scroll down to load more videos (optional)
 */

(async function markAllAsNotInterested() {
  const CONFIG = {
    // Delay between processing each video (ms)
    delayBetweenVideos: 1500,
    // Delay after clicking menu before selecting option (ms)
    delayForMenuOpen: 500,
    // Delay after marking as not interested (ms)
    delayAfterAction: 800,
    // Whether to scroll and continue processing new videos
    continueScrolling: false,
    // Maximum number of videos to process (set to Infinity for unlimited)
    maxVideos: Infinity,
    // Whether to log detailed progress
    verbose: true,
  };

  let processedCount = 0;
  let skippedCount = 0;
  const processedVideoIds = new Set();

  function log(message, type = 'info') {
    const prefix = {
      info: '📌',
      success: '✅',
      error: '❌',
      warning: '⚠️',
      progress: '🔄',
    };
    console.log(`${prefix[type] || '📌'} ${message}`);
  }

  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * Get all video elements that haven't been processed yet
   */
  function getVideoElements() {
    // YouTube uses different selectors for different layouts
    // These selectors target the video renderer elements
    const selectors = [
      'ytd-rich-item-renderer',           // Grid layout videos
      'ytd-video-renderer',               // List layout videos
      'ytd-compact-video-renderer',       // Sidebar videos
    ];

    const videos = [];

    for (const selector of selectors) {
      const elements = document.querySelectorAll(selector);
      elements.forEach(el => {
        // Try to get video ID to track processed videos
        const link = el.querySelector('a#thumbnail, a.yt-simple-endpoint[href*="watch"]');
        const href = link?.href || '';
        const videoIdMatch = href.match(/[?&]v=([^&]+)/);
        const videoId = videoIdMatch ? videoIdMatch[1] : null;

        // Skip if already processed
        if (videoId && processedVideoIds.has(videoId)) {
          return;
        }

        // Skip Shorts (they have different handling)
        if (href.includes('/shorts/')) {
          return;
        }

        // Skip Mix playlists
        if (el.querySelector('[aria-label*="Mix"]') || href.includes('start_radio=1')) {
          return;
        }

        videos.push({ element: el, videoId, href });
      });
    }

    return videos;
  }

  /**
   * Find and click the "More actions" button on a video element
   */
  async function clickMoreActionsButton(videoElement) {
    // Try different selectors for the menu button
    const buttonSelectors = [
      'button[aria-label="More actions"]',
      'button[aria-label="Action menu"]',
      '#button[aria-label="More actions"]',
      'yt-icon-button#button',
      'ytd-menu-renderer button',
    ];

    for (const selector of buttonSelectors) {
      const button = videoElement.querySelector(selector);
      if (button) {
        // Scroll element into view
        button.scrollIntoView({ behavior: 'smooth', block: 'center' });
        await sleep(200);

        // Simulate hover to make button visible (YouTube hides it until hover)
        videoElement.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
        await sleep(100);

        button.click();
        return true;
      }
    }

    return false;
  }

  /**
   * Find and click "Not interested" in the popup menu
   */
  async function clickNotInterested() {
    // Wait for menu to appear
    await sleep(CONFIG.delayForMenuOpen);

    // Try different selectors for the menu items
    const menuSelectors = [
      'tp-yt-paper-listbox ytd-menu-service-item-renderer',
      'ytd-menu-popup-renderer ytd-menu-service-item-renderer',
      'ytd-menu-service-item-renderer',
      '[role="menuitem"]',
    ];

    for (const selector of menuSelectors) {
      const menuItems = document.querySelectorAll(selector);

      for (const item of menuItems) {
        const text = item.textContent?.toLowerCase() || '';
        if (text.includes('not interested')) {
          item.click();
          return true;
        }
      }
    }

    // Fallback: look for any element containing "Not interested"
    const allElements = document.querySelectorAll('*');
    for (const el of allElements) {
      if (el.textContent === 'Not interested' && el.closest('[role="menuitem"], ytd-menu-service-item-renderer')) {
        el.click();
        return true;
      }
    }

    return false;
  }

  /**
   * Close any open menus
   */
  async function closeMenu() {
    document.body.click();
    await sleep(100);

    // Press Escape as backup
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
    await sleep(100);
  }

  /**
   * Process a single video
   */
  async function processVideo(videoData) {
    const { element, videoId } = videoData;

    try {
      // Click the more actions button
      const menuOpened = await clickMoreActionsButton(element);

      if (!menuOpened) {
        if (CONFIG.verbose) {
          log(`Could not find menu button for video`, 'warning');
        }
        skippedCount++;
        return false;
      }

      // Click "Not interested"
      const clicked = await clickNotInterested();

      if (!clicked) {
        if (CONFIG.verbose) {
          log(`Could not find "Not interested" option`, 'warning');
        }
        await closeMenu();
        skippedCount++;
        return false;
      }

      // Mark as processed
      if (videoId) {
        processedVideoIds.add(videoId);
      }
      processedCount++;

      if (CONFIG.verbose) {
        log(`Marked video as not interested (${processedCount} total)`, 'success');
      }

      await sleep(CONFIG.delayAfterAction);
      return true;

    } catch (error) {
      log(`Error processing video: ${error.message}`, 'error');
      await closeMenu();
      skippedCount++;
      return false;
    }
  }

  /**
   * Scroll down to load more videos
   */
  async function scrollForMore() {
    window.scrollTo({
      top: document.documentElement.scrollHeight,
      behavior: 'smooth'
    });
    await sleep(2000); // Wait for new videos to load
  }

  /**
   * Main execution
   */
  async function main() {
    log('Starting YouTube "Not Interested" automation...', 'info');
    log(`Config: Max videos = ${CONFIG.maxVideos}, Continue scrolling = ${CONFIG.continueScrolling}`, 'info');

    let continueProcessing = true;
    let noNewVideosCount = 0;

    while (continueProcessing && processedCount < CONFIG.maxVideos) {
      const videos = getVideoElements();

      if (videos.length === 0) {
        if (CONFIG.continueScrolling) {
          noNewVideosCount++;
          if (noNewVideosCount >= 3) {
            log('No more new videos found after scrolling. Stopping.', 'info');
            break;
          }
          log('No unprocessed videos found. Scrolling for more...', 'progress');
          await scrollForMore();
          continue;
        } else {
          log('No more videos to process.', 'info');
          break;
        }
      }

      noNewVideosCount = 0;
      log(`Found ${videos.length} unprocessed videos`, 'progress');

      for (const videoData of videos) {
        if (processedCount >= CONFIG.maxVideos) {
          continueProcessing = false;
          break;
        }

        await processVideo(videoData);
        await sleep(CONFIG.delayBetweenVideos);
      }

      if (!CONFIG.continueScrolling) {
        continueProcessing = false;
      } else {
        await scrollForMore();
      }
    }

    log('='.repeat(50), 'info');
    log(`Finished! Processed: ${processedCount}, Skipped: ${skippedCount}`, 'success');
    log('='.repeat(50), 'info');
  }

  // Run the script
  await main();

})();

PS. I'm not responsible if your accound get banned (Up until now, I wasn't banned) . Thanks!

UPDATE!

You can check the "New to you" tab (make sure to scale the page to 80%) image

@iosifnicolae2
Copy link
Author

@rachmadaniHaryono I've updated the code to be compatible with Firefox.

@iosifnicolae2
Copy link
Author

iosifnicolae2 commented Jan 14, 2022

It might be useful to implement these two functionalities:

  1. Allow the script to remove the recommended videos from "Watch later" list.
  2. Create an extension where users can start & stop this algorithm

@rachmadaniHaryono
Copy link

rachmadaniHaryono commented Jan 14, 2022

@iosifnicolae2 can confirm the fix, thank you

markdown

javascript:(function()%7B(()%3D%3E%7B%0A%20%20%20%20markAllVideosAsNotBeingInteresting(%7B%0A%20%20%20%20%20%20%20%20iterations%3A%201%0A%20%20%20%20%7D)%3B%0A%7D)()%3B%0A%0Aasync%20function%20markAllVideosAsNotBeingInteresting(%7Biterations%7D)%20%7B%0A%20%20%20%20for(let%20i%3D0%3B%20i%3Citerations%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20await%20markCurrentVideosAsNotBeingInteresting()%3B%0A%20%20%20%20%20%20%20%20console.log(%60Iteration%20%24%7Bi%7D%20completed.%20Waiting%20300ms%60)%3B%0A%20%20%20%20%20%20%20%20await%20sleep(300)%3B%0A%20%20%20%20%7D%0A%20%20%20if(confirm(%22I'm%20done!%20Do%20you%20want%20to%20reload%20the%20page%22%2C%20%22Yes%22))%20%7B%0A%20%20%20%20location.reload()%3B%0A%20%20%20%7D%0A%7D%0A%0Aasync%20function%20markCurrentVideosAsNotBeingInteresting()%20%7B%0A%20%20%20%20const%20videoMenuButtons%20%3D%20document.querySelectorAll(%22yt-icon.ytd-menu-renderer%22)%3B%0A%0A%20%20%20%20for(let%20i%3D0%3B%20i%3CvideoMenuButtons.length%3B%20i%2B%2B)%20%7B%0A%20%20%20%20%20%20%20%20if(!videoMenuButtons%5Bi%5D)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20videoMenuButtons%5Bi%5D.scrollIntoView()%3B%0A%20%20%20%20%20%20%20%20await%20sleep(10)%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Open%20the%20video%20menu%0A%20%20%20%20%20%20%20%20videoMenuButtons%5Bi%5D.click()%3B%0A%0A%0A%20%20%20%20%20%20%20%20await%20sleep(50)%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Click%20on%20%22Not%20interested%22%20button%0A%20%20%20%20%20%20%20%20var%20notInterestedButton%20%3D%20document.querySelector(%22%23items%20%3E%20ytd-menu-service-item-renderer%3Anth-child(5)%20%3E%20tp-yt-paper-item%22)%3B%0A%20%20%20%20%20%20%20%20if(!notInterestedButton)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20continue%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20notInterestedButton.click()%3B%0A%0A%20%20%20%20%20%20%20%20console.log(%22One%20video%20has%20been%20marked.%20Waiting%20100ms%22)%3B%0A%20%20%20%20%20%20%20%20window.scrollBy(0%2C%2095)%3B%0A%20%20%20%20%20%20%20%20await%20sleep(100)%3B%0A%20%20%20%20%7D%0A%7D%0A%0A%2F%2F%20Utils%0Afunction%20sleep(ms)%20%7B%0A%20%20%20%20return%20new%20Promise(resolve%20%3D%3E%20setTimeout(resolve%2C%20ms))%3B%0A%7D%7D)()%3B

@rachmadaniHaryono
Copy link

@iosifnicolae2 unfortunately if blocktube is installed it will add not interested video channel to block list

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment